How can I group a collection of elements by their adjacency? - javascript

Let's say I have a list that looks like this:
<ul>
<li id="q"></li>
<li id="w"></li>
<li id="e"></li>
<li id="r"></li>
<li id="t"></li>
<li id="y"></li>
<li id="u"></li>
<li id="i"></li>
<li id="o"></li>
</ul>
I need to do something like this:
function get_important_elements() {
// completely contrived;
// elements are guaranteed to be contained within same ul
// but nothing else unique in common (class, attrs, etc)
return $('#q, #w, #r, #u, #i, #o');
}
function group_adjacent($elems) {
return $elems; //:(
}
$(function () {
var $filtered_list = get_important_elements();
var groups = group_adjacent($filtered_list);
// groups should be
// (shown by ID here, should contained actual elements contained
// within jQuery objects):
// [$([q, w]), $([r]), $([u, i, o])]
});
How could I go about this?
Note that the IDs and classes used in the list are 100% contrived. In the real code upon which I'm basing this, I have a collection of li elements that are a subset of the lis contained in a single ul. This subset was determined by their contents, which are not important to the question at hand, not by class. They only share a class in the example for ease of getting my point across.

function group_adjacent($elems) {
var rArr = [],
currArr = $([]);
$elems.each(function() {
var $this = $(this);
currArr = currArr.add($this);
if (!$elems.filter($this.next()).length) {
rArr.push(currArr);
currArr = $([]);
}
});
return rArr;
}
http://jsfiddle.net/adamjford/5q8fZ/3/

Using plain script, you can get a collection of all the LIs and loop over it. If an element has the true class put it in a group array. If the next element has the class, put it in the same array. If it doesn't, start a new array. e.g.
function groupLis(){
var el, els = document.getElementsByTagName('li');
var group = [], groups = [group];
for (var i=0, iLen=els.length; i<iLen; i++) {
el = els[i];
if (hasClass(el, 'true')) {
if (!group) {
group = [];
groups.push(group);
}
group.push(el);
} else if (group && group.length != 0) {
group = null;
}
}
return groups;
}
// Helper function
function hasClass(el, cName) {
var re = new RegExp('(^|\\s+)' + cName + '(\\s+|$)');
return el && re.test(el.className);
}
Edit
Ok, here is an answer for the revised question: given an array of elements, group them into arrays of adjacent siblings.
Note that it doesn't care if the elements are the same type, only that they are adjacent siblings (or not).
// el is a DOM node
// Returns the next element sibling, or undefined if here isn't one
function getNextSiblingElement(el) {
while ((el = el.nextSibling)) {
if (el.nodeType == 1) {
return el;
}
}
}
// els is an array of DOM elements
// Returns an array of sibling element arrays
function groupEls2(els) {
var el = els[0],
group = [el],
groups = [group];
for (var i=1, iLen=els.length; i<iLen; i++) {
el = els[i];
el == getNextSiblingElement(els[i-1])? group.push(el) : groups.push((group = [el]));
}
return groups;
}

function group_adjacent($elems) {
var temp = new Array();
var i = 0;
var j = 0;
var last = null;
for(i = 0; i < $elems.length; i++) {
if(last == $elems[i].previousSibling) {
temp[i][j++] = $elems[i];
}
else {
j = 0;
temp[i] = new Array();
temp[i][j++] = $elems[i];
}
last = $elems[i];
}
return temp;
}

Related

Accessing all elements in array

I'm learning javascript and to practice traversing the DOM I have created a method that returns an array of parent elements based on the 'nodeName'. I have done this so that I could select all of a certain element (that is a parent of another) and style them or change them, etc.
Element.prototype.specificParent = function(nodeName1) {
nodeName1 = nodeName1.toUpperCase();
var x = this;
var matches = [];
var allParents = [];
while(x.parentNode !== null) {
allParents.push(x.parentNode);
x = x.parentNode;
}
for(i = 0; i < allParents.length; i++) {
if(allParents[i].nodeName === nodeName1) {
matches.push(allParents[i]);
}
}
return matches;
}
This, kind of, has my desired effect. However, to access all the elements in the returned array I would need to use something like a for loop, because I can only access the elements like this:
var parents = document.getElementById("startHere").specificParent("div"); //gets all parent div elements and returns the array
//I can then style them individually:
parents[0].style.background = "black";
parents[1].style.background = "black";
//etc, etc, etc
//or I could use a for loop to style them all:
for(i = 0; i < parents.length; i++) {
parents[i].style.background = "black";
}
What I want to do is this:
var parents = document.getElementById("startHere").specificParent("div");
parents.style.background = "black"; //changing all the elements in the array
Is there a way to change the "specificParent" method so that it allows this?
This may seem like a pointless exercise but I am learning!
Thanks
Probably the simplest way is to use arrays API.
If you can use ES6, it would look like so:
parents.forEach(parent => parent.style.background = "black")
In ES5 slightly less clear:
parents.forEach(function(parent) { parent.style.background = "black"; })
Based on your comments you can do this:
Element.prototype.specificParent = function(nodeName1) {
nodeName1 = nodeName1.toUpperCase();
var x = this;
var matches = [];
var allParents = [];
while(x.parentNode !== null) {
allParents.push(x.parentNode);
x = x.parentNode;
}
for(i = 0; i < allParents.length; i++) {
if(allParents[i].nodeName === nodeName1) {
matches.push(allParents[i]);
}
}
function setStyle(styleKey, styleValue) {
matches.forEach(function(parent) { parent.style[styleKey]= styleValue; });
}
return {
elements : matches,
setStyle : setStyle
};
}
And use it like so:
var parents = document.getElementById("startHere").specificParent("div");
parents.setStyle("background", "black");

Find all elements in page by passing class name in javascript function without using getElementsBy* or any lib function

I am trying to find all elements in the page by passing class name in this function, I getting all elements as arrays object but I need only those elements those are having my className only.
var custSearch = function (element, className) {
var elementsArray = [];
// add spaces
var q = ' ' + className + ' ';
(function recFind (node) {
// Looping through all the child nodes
for (var i = 0; i < node.childNodes.length; i++) {
var currentNode = node.childNodes[i];
var currentClass = currentNode.className;
// check if current class match with param class
if ((' '+currentClass+' ').indexOf(q)) {
elementsArray.push(currentNode);
}
currentNode.childNodes && recFind(currentNode);
}
})(element);
return elementsArray;
};
custSearch(document, 'spch');
I don't want to use getElementsByClassName function but I want similar result, this above function should give me exact result but I am not finding what I am doing wrong, Can someone tell me if I am making any logical error?
HTML is like this
<div class="spch s2fp-h" style="display:none" id="spch"><div class="spchc" id="spchc"><div class="_o3"><div class="_AM"><span class="_CMb" id="spchl"></span><span class="button" id="spchb"><div class="_wPb"><span class="_AUb"></span><div class="_Fjd"><span class="_oXb"></span><span class="_dWb"></span></div></div></span></div><div class="_gjb"><span class="spcht" id="spchi" style="color:#777"></span><span class="spcht" id="spchf" style="color:#000"></span></div><div class="google-logo"></div></div><div class="_ypc"><div class="_zpc"></div></div></div><div class="close-button" id="spchx">×</div></div>
Try the following:
function myGetElementsByClassName(className) {
var nodes = document.getElementsByTagName('*');
var out = [];
for( var i=0, len=nodes.length; i<len; i++) {
if( nodes[i].classList.contains( className ) ) {
out.push( nodes[i] );
}
}
return out;
}

HTML path from the element to the root JS

For example I have HTML like this
<body>
<div>
something.
</div>
<div>
something else
</div>
<div>
<a> something. </a>
<a> ELEMENT </a>
</div>
</body>
Is there a way to get path from the root to the ELEMENT by using JS, something like this:
body[0]/div[2]/a[1]
So, for the ELEMENT there is need to look on the parent node, check if there exist siblings with the same tag and then correctly assign value and do it recursively to root.
So, for the ELEMENT it is the second (a[1]) child of parent root div which is third (div[2]) child of body.
Is there any way this can be done with JS?
One approach is the following:
function findIndexOfLike(node) {
// creating an Array of the filtered children of the node's
// parent element (using Array.prototype.filter() with
// Function.prototype.call() to apply the Array method
// to the Array-like collection):
var children = Array.prototype.filter.call(node.parentNode.children, function(child) {
// keeping only those elements that are of the same
// tagName:
return node.tagName === child.tagName;
});
// Using Array.prototype.indexOf() to find the index of
// the node from the array of children; and returning that:
return children.indexOf(node);
}
function createIndexedPathTo(node) {
// an empty Array to contain the path:
var path = [],
// initialising the 'current' variable, which we'll
// use to move upwards through the document:
current = node;
// while the node contained in the 'current' variable is
// not the <body> element:
while (current.tagName.toLowerCase() !== 'body') {
// we push the lower-cased tagName of the 'current' node,
// along with its index, to the array:
path.push(current.tagName.toLowerCase() + '[' + findIndexOfLike(current) + ']');
// move the 'current' variable to the parentNode of
// the current element (to move 'up'):
current = current.parentNode;
}
// there can be only one <body> element, but since
// you seem to want it listed we add it here:
path.push('body[0]');
// now we reverse the array, and join it together,
// with the '/' character, to form a string, returning
// that formed string:
return path.reverse().join('/');
}
// calling the function, passing it a DOM Node from which to start:
var route = createIndexedPathTo(document.querySelector('a:nth-child(2)'));
// setting the 'data-routeto' attribute of the <body>
// in order to display that route/path in the document
// using CSS generated content:
document.body.dataset.routeto = route;
function findIndexOfLike(node) {
var children = Array.prototype.filter.call(node.parentNode.children, function(child) {
return node.tagName === child.tagName;
});
return children.indexOf(node);
}
function createIndexedPathTo(node) {
var path = [],
current = node;
while (current.tagName.toLowerCase() !== 'body') {
path.push(current.tagName.toLowerCase() + '[' + findIndexOfLike(current) + ']');
current = current.parentNode;
}
path.push('body[0]');
return path.reverse().join('/');
}
var route = createIndexedPathTo(document.querySelector('a:nth-child(2)'));
document.body.dataset.routeto = route;
body::before {
display: block;
content: 'Route to "Example" element: ' attr(data-routeto);
color: #999;
}
<div>something.</div>
<div>something else</div>
<div> <a> something. </a>
<a id="demo"> ELEMENT </a>
</div>
External JS Fiddle demo, for experimentation.
References:
Array.prototype.filter().
Array.prototype.indexOf().
Array.prototype.join().
Array.prototype.push().
Array.prototype.reverse().
document.querySelector().
Function.prototype.call().
Node.parentNode.
Node.tagName.
String.prototype.toLowerCase().
while (...) {...} statement.
This might be what you were looking for. Else I am sorry I missunderstood your question.
<html>
<head>
<script>
//Recursive function to get element path until html from passed element e;
function getPath(e, d){
d = (d || []);
//if (!e || e.tagName === 'BODY'){ //Body is obivous in most cases tho.
if (!e || !e.parentNode){
return d.join('/');
}
else{
//j is needed since <head> is previous sibling to <body> :s
for (var i = 0, n = e, j = 0; n = n.previousElementSibling; i++) if (n.tagName === e.tagName) j++;
//Here we add the element to the current path \o/
d.push(e.tagName.toLowerCase() + '[' + j.toString() + ']');
return getPath(e.parentNode, d);
};
};
</script>
</head>
<body onclick = 'alert(getPath(this));'>
<div>something.</div>
<div>something else</div>
<div>
<a onclick = 'alert(getPath(this));'>something.</a>
<a onclick = 'alert(getPath(this));'>ELEMENT</a>
</div>
</body>
</html>
javascript: (function() {
if ( typeof document.getElementsByTagName === 'function') {
var elemTag = document.getElementsByTagName('input');
for (var i = 0; i < elemTag.length; i++) {
elemTag[i].addEventListener('mouseup', getPath);
}
var elemTag2 = document.getElementsByTagName('a');
for (var j = 0; j < elemTag2.length; j++) {
elemTag2[j].addEventListener('mouseup', getPath);
}
var elemTag3 = document.getElementsByTagName('select');
for (var p = 0; p < elemTag3.length; p++) {
elemTag3[p].addEventListener('mouseup', getPath);
}
var elemTag4 = document.getElementsByTagName('button');
for (var m = 0; m < elemTag4.length; m++) {
elemTag4[m].addEventListener('mouseup', getPath);
}
var elemTag5 = document.getElementsByTagName('img');
for (var l = 0; l < elemTag5.length; l++) {
elemTag5[l].addEventListener('mouseup', getPath);
}
}
function getPath() {
var domPathArr = [],
elm,
entry;
elm = this;
if ( typeof getIndex === "function" && elm) {
entry = elm.tagName.toLowerCase() + "[" + getIndex(elm) + "]";
if (entry) {
domPathArr.push(entry);
for ( elm = this.parentNode; elm; elm = elm.parentNode) {
entry = elm.tagName.toLowerCase();
if (entry === "html") {
break;
}
if (elm) {
entry += "[" + getIndex(elm) + "]" + "/";
}
domPathArr.push(entry);
}
}
}
domPathArr.reverse();
console.log(domPathArr.join(' '));
}
function getIndex(elm) {
var count = 0;
if (elm) {
for (var siblingElm = elm.previousSibling; siblingElm; siblingElm = siblingElm.previousSibling) {
if (siblingElm.nodeName == elm.nodeName)
count++;
}
}
return count;
}
})();

how can i list array that is inside another array?

I'm processing a form onchange event, in this form i got a few inputs, selects and textareas, I was wondering if something like this would work for grabbing the input values in this form
var lat = {
listen: function () {
var input = document.getElementsByTagName('input');
var select = document.getElementsByTagName('select');
var textarea = document.getElementsByTagName('textarea');
var elements = [input,select,textarea];
console.log(elements.length);
for (var a = 0 ; a < elements.length ; a++) {
for ( var b = 0 ; elements[a].length ; b++) {
console.log(elements[a][b].value);
}
}
}
};
Get silly wit it
http://jsfiddle.net/jkabxpkw/1/
var values = document.getElementById('form') ? (function (array) {
var elements = document.getElementById('form').children;
for (var element in elements) {
if (elements.hasOwnProperty(element)) {
if (elements[element].value) {
array.push(elements[element].value);
}
}
}
return array;
}([])) : null;
Yes, you can map and reduce like so:
var elements = [
document.getElementsByTagName('input'),
document.getElementsByTagName('select'),
document.getElementsByTagName('textarea')
].map(function(i) {
// converts the HTMLCollection elements to an array
return Array.prototype.slice.call(i);
}).reduce(function(result, value) {
return result.concat(value);
}, []).forEach(function(element) {
console.log(element.value);
});

How to access multiple textbox by getElementsByName

I have written following code in html:
<input type="text" id="id_1" name="text_1">
<input type="text" id="id_2" name="text_2">
<input type="text" id="id_3" name="text_3">
Here I have to get all textBoxes in an array in javascript function whose id starts with "id". So, that I can get above two textBoxes in an array.
How to get all textBoxes whose id start with "id"?
var nodeList = document.querySelector("input[name^='text_'")
A nodeList should be sufficiently like an array for your purposes.
Note that support for querySelector might not be sufficient for your purposes (in which you will need to getElementsByTagName and then filter the results in a loop).
Alternatively you could use a library which provides its own selector engine. In YUI 3 you could:
var listOfYUIObjects = Y.all("input[name^='text_'");
Mootools, Prototype, jQuery and a host of other libraries provide similar functionality.
var ele = document.getElementsByTagName("input");
var matchingEle = [];
var eleName = '';
for (var i = 0; i < ele.length; ++i) {
el = ele[i];
eleName = el.getAttribute("name");
if (eleName && eleName.indexOf("text_") == 0) {
matchingEle.push(el);
}
}
You could use a generic function that filters a list of elements based on a pattern. This is useful if you want to do a similar thing in future but with different criteria on the properties.
http://jsfiddle.net/3ZKkh/
function filter(elements, pattern) {
var i, j, match, e, f = [];
for (i = 0; i < elements.length; i += 1) {
e = elements[i];
match = true;
for (j in pattern) {
if (pattern.hasOwnProperty(j)) {
if (!(j in e && pattern[j](e[j]))) {
match = false;
break;
}
}
}
if (match) {
f.push(e);
}
}
return f;
}
var pattern = {
'type': function (t) {
return t.toLowerCase() === 'text';
},
'name': function (t) {
return t.toLowerCase().search('text') === 0;
}
};
console.log(filter(document.getElementsByTagName("input"), pattern));
var list = document.getElementsByTagName('input'); //Array containing all the input controls
var textBoxArray = []; //target Array
for (var i = 0; i < list.length; i++)
{
var node = list[i];
if (node.getAttribute('type') == 'text' && node.getAttribute("id").substring(0, 1) == "id")
{
/*
insert matching textboxes into target array
*/
textBoxArray.push(node);
}
}

Categories

Resources