我想创建类似于跟踪用户所有操作的记录器。为此,我需要识别用户与之交互的元素,以便我可以在以后的会话中引用这些元素。
在伪代码中说,我希望能够做类似以下的事情
示例HTML(可能有任何复杂性):
<html>
<body>
<div class="example">
<p>foo</p>
<span><a href="bar">bar</a></span>
</div>
</body>
</html>
用户点击某些内容,例如链接。现在我需要识别被点击的元素并将其位置保存在DOM树中以供以后使用:
(any element).onclick(function() {
uniqueSelector = $(this).getUniqueSelector();
})
现在,uniqueSelector应该是这样的(我不介意它是xpath还是css选择器样式):
html > body > div.example > span > a
这样可以保存该选择器字符串并在以后使用它,以重放用户所做的操作。
这怎么可能?
答案 0 :(得分:36)
我自己会回答这个问题,因为我找到了一个必须修改的解决方案。以下脚本正在运行,基于script of Blixt:
jQuery.fn.extend({
getPath: function () {
var path, node = this;
while (node.length) {
var realNode = node[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 > 1) {
name += ':nth-child(' + index + ')';
}
}
path = name + (path ? '>' + path : '');
node = parent;
}
return path;
}
});
答案 1 :(得分:16)
与@Alp中的解决方案相同,但与多个jQuery元素兼容。
jQuery('.some-selector')
可以生成一个或多个DOM元素。不幸的是,@ Alp的解决方案仅适用于第一个解决方案。我的解决方案将它们与,
连接起来。
如果您只想处理第一个元素,请执行以下操作:
jQuery('.some-selector').first().getPath();
// or
jQuery('.some-selector:first').getPath();
改进版
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(',');
}
});
答案 2 :(得分:5)
(any element).onclick(function() {
uniqueSelector = $(this).getUniqueSelector();
})
this
IS 唯一的选择器和该点击元素的路径。为什么不使用它?您可以使用jquery的$.data()
方法来设置jquery选择器。或者,只需按下您将来需要使用的元素:
var elements = [];
(any element).onclick(function() {
elements.push(this);
})
如果您确实需要xpath,可以使用以下代码计算它:
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;
};
答案 3 :(得分:4)
我认为更好的解决方案是生成随机ID,然后根据该ID访问元素:
分配唯一ID:
// or some other id-generating algorithm
$(this).attr('id', new Date().getTime());
根据唯一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 + '"]');
答案 4 :(得分:2)
这个答案不符合原始问题描述,但它确实回答了标题问题。我来到这个问题寻找一种方法来获得一个元素的唯一选择器,但我并不需要选择器在页面加载之间有效。所以,我的答案在页面加载之间不起作用。
我觉得修改DOM不是idel,但它是一种很好的方法来构建一个没有代码槽的唯一选择器。在阅读@Eli的回答后,我明白了这个想法:
使用唯一值分配自定义属性。
$(element).attr('secondary_id', new Date().getTime())
var secondary_id = $(element).attr('secondary_id');
然后使用该唯一ID构建CSS选择器。
var selector = '[secondary_id='+secondary_id+']';
然后你有一个选择器来选择你的元素。
var found_again = $(selector);
许多人想检查以确保该元素上还没有secondary_id
属性。
if ($(element).attr('secondary_id')) {
$(element).attr('secondary_id', (new Date()).getTime());
}
var secondary_id = $(element).attr('secondary_id');
全部放在一起
$.fn.getSelector = function(){
var e = $(this);
// the `id` attribute *should* be unique.
if (e.attr('id')) { return '#'+e.attr('id') }
if (e.attr('secondary_id')) {
return '[secondary_id='+e.attr('secondary_id')+']'
}
$(element).attr('secondary_id', (new Date()).getTime());
return '[secondary_id='+e.attr('secondary_id')+']'
};
var selector = $('*').first().getSelector();
答案 5 :(得分:2)
虽然问题是针对jQuery的,但在ES6中,很容易获得类似于@Alp的Vanilla JavaScript的东西(我还添加了几行,跟踪nameCount
,以最大程度地减少{ {1}}):
nth-child
答案 6 :(得分:2)
注意::此方法使用Array.from和Array.prototype.filter,它们都需要在IE11中进行填充。
function getUniqueSelector(node) {
let selector = "";
while (node.parentElement) {
const siblings = Array.from(node.parentElement.children).filter(
e => e.tagName === node.tagName
);
selector =
(siblings.indexOf(node)
? `${node.tagName}:nth-of-type(${siblings.indexOf(node) + 1})`
: `${node.tagName}`) + `${selector ? " > " : ""}${selector}`;
node = node.parentElement;
}
return `html > ${selector.toLowerCase()}`;
}
用法
getUniqueSelector(document.getElementsByClassName('SectionFour')[0]);
getUniqueSelector(document.getElementById('content'));
答案 7 :(得分:2)
我为自己找到了一些改进的解决方案。我将添加到路径选择器#id,.className并将路径的长度减少到#id:
$.fn.extend({
getSelectorPath: function () {
var path,
node = this,
realNode,
name,
parent,
index,
sameTagSiblings,
allSiblings,
className,
classSelector,
nestingLevel = true;
while (node.length && nestingLevel) {
realNode = node[0];
name = realNode.localName;
if (!name) break;
name = name.toLowerCase();
parent = node.parent();
sameTagSiblings = parent.children(name);
if (realNode.id) {
name += "#" + node[0].id;
nestingLevel = false;
} else if (realNode.className.length) {
className = realNode.className.split(' ');
classSelector = '';
className.forEach(function (item) {
classSelector += '.' + item;
});
name += classSelector;
} else if (sameTagSiblings.length > 1) {
allSiblings = parent.children();
index = allSiblings.index(realNode) + 1;
if (index > 1) {
name += ':nth-child(' + index + ')';
}
}
path = name + (path ? '>' + path : '');
node = parent;
}
return path;
}
});
答案 8 :(得分:1)
您还可以查看findCssSelector。代码在我的other answer。
答案 9 :(得分:1)
我的Vanilla JavaScript函数:
function getUniqueSelector(element) {
if (element.id) {
return '#' + element.id;
} else if (element.tagName === 'BODY') {
return 'BODY';
} else {
return `${ui.selector(element.parentElement)} > ${element.tagName}:nth-child(${myIndexOf(element)})`;
}
}
function myIndexOf( element ) {
let index = 1;
// noinspection JSAssignmentUsedAsCondition
while (e = e.previousElementSibling) index++;
return index;
}
答案 10 :(得分:0)
你可以这样做:
$(".track").click(function() {
recordEvent($(this).attr("id"));
});
它将onclick
事件处理程序附加到具有track
类的每个对象。每次单击一个对象时,其id都会被送入recordEvent()
函数。您可以使此功能记录每个对象的时间和ID或任何您想要的内容。
答案 11 :(得分:0)
$(document).ready(function() {
$("*").click(function(e) {
var path = [];
$.each($(this).parents(), function(index, value) {
var id = $(value).attr("id");
var class = $(value).attr("class");
var element = $(value).get(0).tagName
path.push(element + (id.length > 0 ? " #" + id : (class.length > 0 ? " .": "") + class));
});
console.log(path.reverse().join(">"));
return false;
});
});
使用*选择器(非常慢)并阻止事件冒泡时,您可能会遇到问题,但如果没有更多的HTML代码,则无法提供帮助。
答案 12 :(得分:-1)
您可以做类似的事情(未经测试)
function GetPathToElement(jElem)
{
var tmpParent = jElem;
var result = '';
while(tmpParent != null)
{
var tagName = tmpParent.get().tagName;
var className = tmpParent.get().className;
var id = tmpParent.get().id;
if( id != '') result = '#' + id + result;
if( className !='') result = '.' + className + result;
result = '>' + tagName + result;
tmpParent = tmpParent.parent();
}
return result;
}
这个函数将保存元素的“路径”,现在再次找到该元素它将是几乎不可能的html方式,因为在这个函数中我不保存每个元素的sibbling索引,我只保存id(s)和类。
因此,除非你的html文档的每个元素都有一个ID,否则这种方法不起作用。
答案 13 :(得分:-1)
使用jquery和typescript功能编程获取dom路径
function elementDomPath( element: any, selectMany: boolean, depth: number ) {
const elementType = element.get(0).tagName.toLowerCase();
if (elementType === 'body') {
return '';
}
const id = element.attr('id');
const className = element.attr('class');
const name = elementType + ((id && `#${id}`) || (className && `.${className.split(' ').filter((a: any) => a.trim().length)[0]}`) || '');
const parent = elementType === 'html' ? undefined : element.parent();
const index = (id || !parent || selectMany) ? '' : ':nth-child(' + (Array.from(element[0].parentNode.children).indexOf(element[0]) + 1) + ')';
return !parent ? 'html' : (
elementDomPath(parent, selectMany, depth + 1) +
' ' +
name +
index
);
}
答案 14 :(得分:-1)
将 js 元素(节点)传递给这个函数.. 工作一点.. 尝试发表您的评论
function getTargetElement_cleanSelector(element){
let returnCssSelector = '';
if(element != undefined){
returnCssSelector += element.tagName //.toLowerCase()
if(element.className != ''){
returnCssSelector += ('.'+ element.className.split(' ').join('.'))
}
if(element.id != ''){
returnCssSelector += ( '#' + element.id )
}
if(document.querySelectorAll(returnCssSelector).length == 1){
return returnCssSelector;
}
if(element.name != undefined && element.name.length > 0){
returnCssSelector += ( '[name="'+ element.name +'"]' )
}
if(document.querySelectorAll(returnCssSelector).length == 1){
return returnCssSelector;
}
console.log(returnCssSelector)
let current_parent = element.parentNode;
let unique_selector_for_parent = getTargetElement_cleanSelector(current_parent);
returnCssSelector = ( unique_selector_for_parent + ' > ' + returnCssSelector )
console.log(returnCssSelector)
if(document.querySelectorAll(returnCssSelector).length == 1){
return returnCssSelector;
}
}
return returnCssSelector;
}