Get unique selector of element in Jquery
I want to create something like a recorder whichs tracks all actions of a user. For that, i need to identify elements the user interacts with, so that i can refer to these elements in a later session.
Spoken in pseudo-code, i want to be able to do something like the following
Sample HTML (could be of any complexity):
<html>
<body>
<div class="example">
<p>foo</p>
<span><a href="bar">bar</a></span>
</div>
</body>
</html>
User clicks on something, like the link. Now i need to identify the clicked element and save its location in the DOM tree for later usage:
(any element).onclick(function() {
uniqueSelector = $(this).getUniqueSelector();
})
Now, uniqueSelector should be something like (i don't mind if it is xpath or css selector style):
html > body > div.example > span > a
This would provide the possibility to save that selector string and use it at a later time, to replay the actions the user made.
How is that possible?
Update
Got my answer: Getting a jQuery selector for an element
I'll answer this myself, because i found a solution which i had to modify. The following script is working and is based on a script of Blixt:
jQuery.fn.extend({
getPath: function () {
var path, node = this;
while (node.length) {
var realNode = node[0], name = realNode.name;
if (!name) break;
name = name.toLowerCase();
var parent = node.parent();
var sameTagSiblings = parent.children(name);
if (sameTagSiblings.length > 1) {
var allSiblings = parent.children();
var index = allSiblings.index(realNode) + 1;
if (index > 1) {
name += ':nth-child(' + index + ')';
}
}
path = name + (path ? '>' + path : '');
node = parent;
}
return path;
}
});
Same solution like that one from @Alp but compatible with multiple jQuery elements.
jQuery('.some-selector')
can result in one or many DOM elements. @Alp's solution works unfortunately only with the first one. My solution concatenates all them with ,
.
If you want just handle the first element do it like this:
jQuery('.some-selector').first().getPath();
// or
jQuery('.some-selector:first').getPath();
Improved version
jQuery.fn.extend({
getPath: function() {
var pathes = [];
this.each(function(index, element) {
var path, $node = jQuery(element);
while ($node.length) {
var realNode = $node.get(0), name = realNode.localName;
if (!name) { break; }
name = name.toLowerCase();
var parent = $node.parent();
var sameTagSiblings = parent.children(name);
if (sameTagSiblings.length > 1)
{
var allSiblings = parent.children();
var index = allSiblings.index(realNode) + 1;
if (index > 0) {
name += ':nth-child(' + index + ')';
}
}
path = name + (path ? ' > ' + path : '');
$node = parent;
}
pathes.push(path);
});
return pathes.join(',');
}
});
I think a better solution would be to generate a random id and then access an element based on that id:
Assigning unique id:
// or some other id-generating algorithm
$(this).attr('id', new Date().getTime());
Selecting based on the unique id:
// getting unique id
var uniqueId = $(this).getUniqueId();
// or you could just get the id:
var uniqueId = $(this).attr('id');
// selecting by id:
var element = $('#' + uniqueId);
// if you decide to use another attribute other than id:
var element = $('[data-unique-id="' + uniqueId + '"]');
(any element).onclick(function() {
uniqueSelector = $(this).getUniqueSelector();
})
this
IS the unique selector and path to that clicked element. Why not use that? You can utilise jquery's $.data()
method to set the jquery selector. Alternatively just push the elements you need to use in the future:
var elements = [];
(any element).onclick(function() {
elements.push(this);
})
If you really need the xpath, you can calculate it using the following code:
function getXPath(node, path) {
path = path || [];
if(node.parentNode) {
path = getXPath(node.parentNode, path);
}
if(node.previousSibling) {
var count = 1;
var sibling = node.previousSibling
do {
if(sibling.nodeType == 1 && sibling.nodeName == node.nodeName) {count++;}
sibling = sibling.previousSibling;
} while(sibling);
if(count == 1) {count = null;}
} else if(node.nextSibling) {
var sibling = node.nextSibling;
do {
if(sibling.nodeType == 1 && sibling.nodeName == node.nodeName) {
var count = 1;
sibling = null;
} else {
var count = null;
sibling = sibling.previousSibling;
}
} while(sibling);
}
if(node.nodeType == 1) {
path.push(node.nodeName.toLowerCase() + (node.id ? "[@id='"+node.id+"']" : count > 0 ? "["+count+"]" : ''));
}
return path;
};
Reference: http://snippets.dzone.com/posts/show/4349