在Jquery中获取元素的唯一选择器

时间:2011-04-18 17:52:59

标签: javascript jquery html css-selectors

我想创建类似于跟踪用户所有操作的记录器。为此,我需要识别用户与之交互的元素,以便我可以在以后的会话中引用这些元素。

在伪代码中说,我希望能够做类似以下的事情

示例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

这样可以保存该选择器字符串并在以后使用它,以重放用户所做的操作。

这怎么可能?

更新

得到了我的回答:Getting a jQuery selector for an element

15 个答案:

答案 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;
  };

参考:http://snippets.dzone.com/posts/show/4349

答案 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)

纯JavaScript解决方案

注意::此方法使用Array.fromArray.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;
    });
});

工作示例:http://jsfiddle.net/peeter/YRmr5/

使用*选择器(非常慢)并阻止事件冒泡时,您可能会遇到问题,但如果没有更多的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;
}