Проверить, соответствует ли селектор заданному элементу - PullRequest
34 голосов
/ 22 июля 2010

Есть ли способ проверить, будет ли селектор соответствовать данному элементу DOM? Желательно, без использования внешней библиотеки, такой как Sizzle. Это для библиотеки, и я хотел бы минимизировать количество сторонних плагинов, необходимых для «основной» библиотеки. Если в конечном итоге потребуется Sizzle, я просто добавлю это как плагин в библиотеку для тех, кому нужна эта функция.

Например, я мог бы сделать что-то вроде:

var element = <input name="el" />

matches("input[name=el]", element) == true

РЕДАКТИРОВАТЬ : Подумав об этом больше, я нашел решение, которое технически работает, но не кажется оптимальным с точки зрения эффективности:

function matchesSelector(selector, element) { 
    var nodeList = document.querySelectorAll(selector); 
    for ( var e in nodeList ) {
        return nodeList[e] === element; 
    }
    return false; 
}

По сути, функция запрашивает весь документ с помощью данного селектора, а затем выполняет итерации по нодлисту. Если данный элемент находится в nodeList, то он возвращает true, а если нет, то возвращает false.

Если кто-нибудь может придумать более эффективный ответ, я бы с радостью отметил его ответ как ответ.

РЕДАКТИРОВАТЬ : Флавиус Стеф указал мне на браузерное решение для Firefox 3.6+, mozMatchesSelector . Я также нашел эквивалент для Chrome (совместимость версий неизвестна, и он может работать или не работать в Safari или других браузерах webkit): webkitMatchesSelector, что в основном совпадает с реализацией Firefox. Я не нашел никакой собственной реализации для браузеров IE.

Для приведенного выше примера использование будет:

element.(moz|webkit)MatchesSelector("input[name=el]")

Похоже, что W3C также учел это в спецификации Selectors API Level 2 (пока еще в черновом варианте). matchesSelector будет методом для DOM Elements после утверждения.

W3C Использование: element.matchesSelector(selector)

Поскольку эта спецификация все еще является черновиком, и популярным браузерам потребуется время для того, чтобы реализовать методы, как только она станет стандартом, может пройти некоторое время, пока она действительно не станет пригодной для использования. Хорошей новостью является то, что если вы используете какую-либо из популярных платформ, скорее всего, они, вероятно, реализуют эту функцию для вас, не беспокоясь о кросс-браузерной совместимости. Хотя это не помогает тем из нас, кто не может включать сторонние библиотеки.

Каркасы или библиотеки, которые реализуют эту функциональность:

http://www.prototypejs.org/api/element/match

http://developer.yahoo.com/yui/docs/YAHOO.util.Selector.html

http://docs.jquery.com/Traversing/is

http://extjs.com/deploy/dev/docs/output/Ext.DomQuery.html#Ext.DomQuery-methods

http://base2.googlecode.com/svn/doc/base2.html#/doc/!base2.DOM.Element.matchesSelector

http://wiki.github.com/jeresig/sizzle/

Ответы [ 7 ]

32 голосов
/ 16 мая 2013

Для удобства тех, кто посещает эту страницу после многих лет, эта функциональность теперь реализована во всех современных браузерах как element.matches без префикса поставщика (за исключением ms для браузеров MS, отличных от Edge 15, и * 1003).* для Android / KitKat).Смотри http://caniuse.com/matchesselector.

8 голосов
/ 12 января 2013

Для наилучшая производительность , используйте реализации браузера ((moz|webkit|o|ms)matchesSelector), где это возможно. Когда вы не можете сделать это, вот ручная реализация.

Важным примером для рассмотрения является тестирование селекторов для элементов, не прикрепленных к документу.

Вот подход, который обрабатывает эту ситуацию. Если выясняется, что рассматриваемый element не прикреплен к документу, ползти вверх по дереву, чтобы найти самого старшего предка (последний ненулевой parentNode) и поместить его в DocumentFragment. Затем из этого DocumentFragment позвоните querySelectorAll и посмотрите, находится ли ваш element в результирующем NodeList.

Вот код.

Документ

Вот структура документа, с которой мы будем работать. Мы возьмем .element и проверим, соответствует ли он селекторам li и .container *.

<!DOCTYPE html>
<html>
  <body>
    <article class="container">
      <section>
        <h1>Header 1</h1>
        <ul>
          <li>one</li>
          <li>two</li>
          <li>three</li>
        </ul>
      </section>
      <section>
        <h1>Header 2</h1>
        <ul>
          <li>one</li>
          <li>two</li>
          <li class="element">three</li>
        </ul>
      </section>
      <footer>Footer</footer>
    </article>
  </body>
</html>

Поиск с помощью document.querySelectorAll

Вот функция matchesSelector, которая использует document.querySelectorAll.

// uses document.querySelectorAll
function matchesSelector(selector, element) {
  var all = document.querySelectorAll(selector);
  for (var i = 0; i < all.length; i++) {
    if (all[i] === element) {
      return true;
    }
  }
  return false;
}

Это работает, пока этот элемент находится в document.

// this works because the element is in the document
console.log("Part 1");
var element = document.querySelector(".element");
console.log(matchesSelector("li", element)); // true
console.log(matchesSelector(".container *", element)); // true

Однако произойдет сбой, если элемент будет удален из document.

// but they don't work if we remove the article from the document
console.log("Part 2");
var article = document.querySelector("article");
article.parentNode.removeChild(article);
console.log(matchesSelector("li", element)); // false
console.log(matchesSelector(".container *", element)); // false

Поиск в пределах DocumentFragment

Исправление требует поиска любого поддерева, в котором находится element. Вот обновленная функция с именем matchesSelector2.

// uses a DocumentFragment if element is not attached to the document
function matchesSelector2(selector, element) {
  if (document.contains(element)) {
    return matchesSelector(selector, element);
  }
  var node = element;
  var root = document.createDocumentFragment();
  while (node.parentNode) {
    node = node.parentNode;
  }
  root.appendChild(node);
  var all = root.querySelectorAll(selector);
  for (var i = 0; i < all.length; i++) {
    if (all[i] === element) {
      root.removeChild(node);
      return true;
    }
  }
  root.removeChild(node);
  return false;
}

Теперь мы видим, что matchSelector2 работает, даже если элемент находится в поддереве, отделенном от документа.

// but they will work if we use matchesSelector2
console.log("Part 3");
console.log(matchesSelector2("li", element)); // true
console.log(matchesSelector2(".container *", element)); // true

Это можно увидеть на jsfiddle .

Собираем все вместе

Вот последняя реализация, которую я придумал:

function is(element, selector) {
  var node = element;
  var result = false;
  var root, frag;

  // crawl up the tree
  while (node.parentNode) {
    node = node.parentNode;
  }

  // root must be either a Document or a DocumentFragment
  if (node instanceof Document || node instanceof DocumentFragment) {
    root = node;
  } else {
    root = frag = document.createDocumentFragment();
    frag.appendChild(node);
  }

  // see if selector matches
  var matches = root.querySelectorAll(selector);
  for (var i = 0; i < matches.length; i++) {
    if (this === matches.item(i)) {
      result = true;
      break;
    }
  }

  // detach from DocumentFragment and return result
  while (frag && frag.firstChild) {
    frag.removeChild(frag.firstChild);
  }
  return result;
}

Важным примечанием является то, что реализация jQuery является намного быстрее. Первая оптимизация, на которую я хотел бы обратить внимание, - это избегать ползания по дереву, если нам не нужно Для этого вы можете посмотреть на самую правую часть селектора и проверить, соответствует ли это элементу. Тем не менее, имейте в виду, что если на самом деле селектор состоит из нескольких селекторов, разделенных запятыми, вам придется протестировать каждый из них. На данный момент вы создаете синтаксический анализатор CSS-селекторов, так что вы также можете использовать библиотеку.

5 голосов
/ 22 июля 2010

В отсутствие xMatchesSelector я думаю попробовать добавить стиль с запрошенным селектором к объекту styleSheet вместе с некоторым произвольным правилом и значением, которое вряд ли уже используется.Затем проверьте computed/currentStyle элемента, чтобы увидеть, унаследовал ли он добавленное правило CSS.Примерно так для IE:

function ieMatchesSelector(selector, element) {
  var styleSheet = document.styleSheets[document.styleSheets.length-1];

  //arbitrary value, probably should first check 
  //on the off chance that it is already in use
  var expected = 91929;

  styleSheet.addRule(selector, 'z-index: '+expected+' !important;', -1);

  var result = element.currentStyle.zIndex == expected;

  styleSheet.removeRule(styleSheet.rules.length-1);

  return result;
}

Возможно, в этом методе полно сумок, набитых готками.Вероятно, лучше всего найти какое-то непонятное запатентованное правило CSS, которое с меньшей вероятностью будет иметь визуальный эффект, чем z-index, но, поскольку оно удаляется почти сразу после его установки, единственным побочным эффектом в этом случае должен быть кратковременный миг.Кроме того, более неясное правило с меньшей вероятностью будет переопределено более конкретным селектором, правилами атрибутов стиля или другими! Важными правилами (если IE даже поддерживает это).В любом случае, стоит попробовать хотя бы.

3 голосов
/ 22 июля 2010

API селекторов W3C (http://www.w3.org/TR/selectors-api/) указывает document.querySelectorAll(). Это поддерживается не во всех браузерах, поэтому вам придется поискать в тех, которые его поддерживают: http://www.google.com/search?q=browsers+implementing+selector+api

1 голос
/ 05 декабря 2012

Я занимаюсь этой проблемой сейчас. Я должен поддерживать IE8 с нативным Javascript, который представляет собой любопытную проблему: IE8 поддерживает как querySelector, так и querySelectorAll, но не совпадает с Seelect. Если ваша ситуация похожа, вот вариант для вас рассмотреть:

Когда вам передадут узел DOM и селектор, сделайте поверхностную копию узла, а также его родителя. Это сохранит все их атрибуты, но не создаст копии их соответствующих дочерних элементов.

Присоедините клонированный узел к клонированному родителю. Используйте querySelector на клонированном родительском объекте - единственное, что ему нужно для поиска, - это единственный дочерний узел, который у него есть, поэтому этот процесс занимает постоянное время. Он либо вернет дочерний узел, либо не вернет.

Это будет выглядеть примерно так:

function matchesSelector(node, selector)
{
   var dummyNode = node.cloneNode(false);
   var dummyParent = node.parent.cloneNode(false);
   dummyParent.appendChild(dummyNode);
   return dummyNode === dummyParent.querySelector(selector);
}

Возможно, стоит создать полную цепочку мелко скопированных родителей вплоть до корневого узла и запросить (в основном пустой) фиктивный корень, если вы хотите проверить связь вашего узла с его предками.

Вне головы, я не уверен, для какой части селекторов это будет работать, но я думаю, это было бы хорошо для любого, кто не беспокоился о детях тестируемого узла. YMMV.

- РЕДАКТИРОВАТЬ -

Я решил написать функцию для мелкого копирования всего с проверяемого узла в корневой. Используя это, гораздо больше селекторов можно использовать. (Впрочем, ничего общего с братьями и сестрами.)

function clonedToRoot(node)
{
    dummyNode = node.cloneNode(false);
    if(node.parentNode === document)
    {
        return {'root' : dummyNode, 'leaf' : dummyNode};
    }
    parent = clonedToRoot(node.parentNode).root;
    parent.appendChild(dummyNode);
    return {'root' : parent, 'leaf' : dummyNode};
}

function matchesSelector(node, selector)
{
    testTree = clonedToRoot(node)
    return testTree.leaf === testTree.root.querySelector(selector)
}

Я бы хотел, чтобы эксперт объяснил, какие виды селекторов существуют, но это не охватит!

0 голосов
/ 22 июля 2010

Современные браузеры могут делать это с функцией document.querySelectorAll.

http://www.w3.org/TR/selectors-api/

0 голосов
/ 22 июля 2010

Просто использовать идентификатор для вашего элемента? HTML-идентификаторы должны быть уникальными ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...