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;
}
})();
Related
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;
}
I want to add span tag with the specific class to all the numbers in my website using JavaScript. However I got the following:
<script>
var regex = /(\d+)/,
replacement = '<span class="font-arial">$1</span>';
function replaceText(el) {
if (el.nodeType === 3) {
if (regex.test(el.data)) {
var temp_div = document.createElement('div');
temp_div.innerHTML = el.data.replace(regex, replacement);
var nodes = temp_div.childNodes;
while (nodes[0]) {
el.parentNode.insertBefore(nodes[0],el);
}
el.parentNode.removeChild(el);
}
} else if (el.nodeType === 1) {
for (var i = 0; i < el.childNodes.length; i++) {
replaceText(el.childNodes[i]);
}
}
}
replaceText(document.body);
</script>
But the problem is as below example:
If the number is: 45 7320272536
It put like this:
<span class="arial">
<span class="arial">45</span>
</span>
<span class="arial">
<span class="arial">7320272536</span>
</span>
I want like this:
<span class="arial">45</span>
<span class="arial">7320272536</span>
It can be simpler. You don't need to loop over child nodes one more time. Instead you can replace all number occurrences at once if you use global match flag g for regexp object:
/(\d+)/g
So after cleaning the code with replaceChild method, your code becomes:
var regex = /(\d+)/g,
replacement = '<span class="font-arial">$1</span>';
function replaceText(el) {
if (el.nodeType === 3) {
if (regex.test(el.data)) {
var temp_div = document.createElement('div');
temp_div.innerHTML = el.data.replace(regex, replacement);
el.parentNode.replaceChild(temp_div, el);
}
} else if (el.nodeType === 1) {
for (var i = 0; i < el.childNodes.length; i++) {
replaceText(el.childNodes[i]);
}
}
}
replaceText(document.body);
Demo: http://jsfiddle.net/a09vac2t/
Here is a working sample.
<!doctype html>
<html>
<head>
<title>replace test</title>
<script>
var regex = /(\d+)/;
replacement = '<span style="border:1px solid red">$1</span>';
function replaceText(el) {
//alert('Testing: ' + el.data);
//alert('Node type: ' + el.nodeType);
if (el.nodeType === 3) {
if (regex.test(el.data)) {
//alert('Matches');
var temp_div = document.createElement('div');
temp_div.innerHTML = el.data.replace(regex, replacement);
//alert('Making it ' + temp_div.innerHTML);
var nodes = temp_div.childNodes;
while (nodes[0]) {
//alert(nodes.length + ": " + nodes[0].data);
el.parentNode.insertBefore(nodes[0],el);
}
el.parentNode.removeChild(el);
}
} else if (el.nodeType === 1) {
//alert('Looping children: ' + el.childNodes.length);
var rootChildrenCopy = toArray(el.childNodes).slice(0);
for (var i = 0; i < rootChildrenCopy.length; i++) {
replaceText(rootChildrenCopy[i]);
}
}
}
function toArray(obj) {
var array = [];
// iterate backwards ensuring that length is an UInt32
for (var i = obj.length >>> 0; i--;) {
array[i] = obj[i];
}
return array;
}
</script>
</head>
<body onload="replaceText(document.getElementById('abc'))">
<p>This is a number</p>
<div id='abc'>Here is a 45 num</div>
</body>
</html>
Element.childNodes will change when you insert nodes, so you need to save a copy of it when looping. Array.prototype.slice is good for this.
In addition, you need to change your regex to replace all occurrences instead of just the first with the global flag.
I'm aware of [name^="value"] selector but is there a analogous selector (or technique) that queries all attributes starting with the given value?
I'm looking for something like $("[*^='http://www.something.com']")(that does not exist).
It would match all elements which contains at least one attribute with a value that begins with http://www.something.com.
Say:
<img src="http://www.something.com/image.jpg" />
Something
<link rel="stylesheet" href="http://www.something.com/css/style.css" type="text/css">
Attribute name could be anything, not just src and href, even non standard attributes.
Is there a known way to do it?
I've put together some of the ideas from other answers and wrote a custom selector.
Selector
$.expr[':'].hasAttrStartingWithValue = function (obj, index, meta) {
var startsWithAttrValue = false;
var value = meta[3];
for (var i = 0; i < obj.attributes.length; i++) {
var attr = obj.attributes[i];
// attr.value starts with value
if (attr.specified && attr.value.lastIndexOf(value, 0) === 0) {
startsWithAttrValue = true;
break;
}
}
return startsWithAttrValue;
};
It has not been properly tested for cross-browsing and correctness, and I'm sure that it can be further optimized, but it seems to be working well with IE 11, FF 24 and Chrome 32.
Usage
// Logs every occurrence of a value in any attribute of the page
$(":hasAttrStartingWithValue('http://www.something.com')").each(function (i, e) {
console.log(i + " - " + e.outerHTML);
});
// Matches only children of test
$("#test :hasAttrStartingWithValue('http://www.something.com')")
.css('background-color', 'red');
Working Fiddle.
function strstr (haystack, needle, bool) {
var pos = 0;
haystack += '';
pos = haystack.indexOf(needle);
if (pos == -1) {
return false;
} else {
if (bool) {
return haystack.substr(0, pos);
} else {
return haystack.slice(pos);
}
}
}
$( document ).ready(function(){
$('*').each(function() {
$.each(this.attributes, function() {
// this.attributes is not a plain object, but an array
// of attribute nodes, which contain both the name and value
if(this.specified) {
if( strstr(this.value,'http://') )
alert(this.name+'+'+this.value);
}
});
});
});
Alert All attributes And values...
Custom this code...
jsfiddle
If you want to do it for img, a, and link tags, then you could do it like this:
var ref = '"http://www.something.com"';
var elems = [].slice.call(document.querySelectorAll('img[src='+ref+'], a[href='+ref+'], link[href='+ref+']'));
//do something with the elems array
If you want to go the other route...
JS Fiddle of Working Abomination in Vanilla JS
Code that makes me sad (query everything in sight, loops in loops, regex in loops, etc.):
var rx = /(^http:\/\/www.something.com)/;
var loopAgain = function () {
for (var j = 0, leng = attrs.length; j < leng; j++) {
if (rx.test(attrs[j].value)) {
return true;
}
return false;
}
};
var allTheThings = [].slice.call(document.querySelectorAll('*'));
for (var i = 0, len = allTheThings.length; i < len; i++) {
var attrs = allTheThings[i].attributes;
if (loopAgain()) {
console.log(allTheThings[i]);
}
}
I need to rewrite this code in pure JavaScript, i.e. without jQuery. It gets the content of a div and adds it after the first image in another div.
$(document).ready(function() {
var teksti = $('#inside1').html();
$('<div id="inside1">' + teksti + '</div><div style="clear:both;"></div>').insertAfter("#artikull > p > img:first");
});
If you only need to support modern browsers, it's not too complicated:
// $(document).ready(function() {
document.addEventListener("DOMContentLoaded", function() {
// var teksti = $('#inside1').html();
var teksti = document.getElementById('inside1').innerHTML;
// $('<div id="inside1">' + teksti + '</div><div style="clear:both;"></div>')
// .insertAfter("#artikull > p > img:first");
document
.querySelector('#artikull > p > img')
.insertAdjacentHTML(
'afterend',
'<div id="inside1">' + teksti + '</div><div style="clear:both">'
);
// });
});
The roughly equivalent lines from the original jQuery are in the comments.
I'm a little confused, though; the code you've presented will create an element with the same ID as the original one and ends up with a couple of divs in a p—resulting in a somewhat deformed DOM. Wouldn't you prefer to simply wrap the existing element and move it instead of creating a new one with exactly the same content, and shouldn't you move it somewhere that accepts block-level children?
function adddiv(){
var teksti = document.getElementById('inside1').innerHTML;
var div = document.getElementById('artikull');
var imazh = div.getElementsByTagName("img")[0];
imazh.outerHTML = imazh.outerHTML + teksti;
}
The code below should be compatible in modern browsers and IE from version 9.
document.addEventListener("DOMContentLoaded", function (e) {
var insideHTML = '<div id="inside1">' + window.document.getElementById('inside1').innerHTML + '</div><div style="clear:both;"></div>',
artikullElement = window.document.getElementById('artikull'),
pElements = (function (nodes) {
var results = [],
node;
for (var i = 0, iLen = nodes.length; i < iLen; i++) {
node = nodes[i];
// Get all children P tags
if (node.nodeValue === 1 && node.tagName === "P")
results.push(node);
}
return results;
})(artikullElement.childNodes),
imgElement = (function (nodes) {
var node;
for (var i = 0, iLen = nodes.length; i < iLen; i++) {
// Get the first child tag image found in any of the P tags
if (node.nodeValue === 1 && node.tagName === "IMG")
return node;
}
})(pElements);
if (imgElement) {
if (imgElement.insertAdjacentHTML)
imgElement.insertAdjacentHTML('beforeEnd', insideHTML);
else {
var range = window.document.createRange(),
docFragment = range.createContextualFragment(insideHTML);
imgElement.parentNode.insertBefore(docFragment, imgElement.nextSibling);
}
}
});
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;
}