Как найти элемент DOM по его текстовому содержимому? - PullRequest
2 голосов
/ 22 сентября 2019

Я пытаюсь добавить новые элементы SVG в некоторые узлы.Для этого узлы, к которым должны быть добавлены элементы, должны быть найдены строкой, содержащейся в текстовом содержимом, например, найти любой узел, который имеет "id0" внутри тега <text>.

Вот примермоей HTML-иерархии:

<code><pre>
 <svg>
  <g>
   <g>
    <text> id3 </text>
    <text> 73% </text>
    <svg> ... </svg>
   </g>
   <g>
    <svg> ... </svg>
   </g>
   <g>
    <text> id0 </text>
    <text> 11% </text>
    <svg> ... </svg>
   </g>
   <g>
    <text> id1 </text>
    <text> 66% </text>
    <svg> ... </svg>
   </g>
   <g>
    <svg> ... </svg>
   </g>
  </g>
 </svg>

Я определенно не знаю правильное решение, но я думаю, что это что-то вроде этого:

d3.select('svg').select('g').selectAll('g').each(function (d, i) {})
                .select('g').select('text').filter(function () {
                  return (d3.select(this).text() === 'id0')
                })
                .select(function () {
                  return this.parentElement;
                })
                .append('svg')
                .attr('width', 400)
                .attr('height', 400)

Еслитег <text> содержит "id0", затем вернитесь к родительскому узлу и добавьте к нему элемент SVG.Но в строке return this.parentElement; возникает ошибка:

Свойство parentElement не существует для типа 'Window'.

Подобные ошибки возникают при использовании parentElement или parent.

1 Ответ

1 голос
/ 24 сентября 2019

В D3 нет встроенного способа выбора элемента по его текстовому содержимому, что связано с тем, что D3 внутренне использует Element.querySelector() и Element.querySelectorAll()выбрать элементы из DOM.Эти методы принимают строку CSS Selector в качестве отдельного параметра, который определяется спецификацией Selector Level 3 .К сожалению, нет способа выбрать элемент на основе его содержимого (это когда-то было возможно с помощью псевдокласса :contains(), который, однако, отсутствует).

Поэтому, чтобы построить ваш выбор, вы должны прибегнуть к передаче функции в .select(), которая выбирает и возвращает интересующий вас элемент <text>. Есть разные способы сделать это, но я бы хотелпредложить не столь очевидный, но элегантный подход.Это использует малоизвестный интерфейс NodeIterator, который можно использовать для создания итератора по списку узлов из DOM, которые соответствуют критериям вашего фильтра.

NodeIteratorэкземпляр создается путем вызова Document.createNodeIterator() , который принимает три аргумента:

  1. Корень поддерева DOM, из которого выполняется поиск.
  2. Aбитовая маска, определяющая, какие типы узлов вас интересуют;для ваших целей это будет NodeFilter.SHOW_TEXT.
  3. Объект, реализующий метод .acceptNode() интерфейса NodeFilter.Этот метод представлен каждому узлу указанного типа в порядке документа и должен возвращать NodeFilter.FILTER_ACCEPT для любого совпадающего узла и NodeFilter.FILTER_REJECT любых других узлов.На этом этапе ваша реализация будет искать совпадение значения id с текстовым содержимым фактического текстового элемента.

Затем вы можете вызвать .nextNode() на созданном узлеитератор для обхода списка совпадающих узлов.Для вашей задачи это может быть что-то вроде следующего:

document.createNodeIterator(
  this,                  // The root node of the searched DOM sub-tree.
  NodeFilter.SHOW_TEXT,  // Look for text nodes only.
  {
    acceptNode(node) {   // The filter method of interface NodeFilter
      return new RegExp(value).test(node.textContent)  // Check if text contains string
        ? NodeFilter.FILTER_ACCEPT                     // Found: accept node
        : NodeFilter.FILTER_REJECT;                    // Not found: reject and continue
  }
})
.nextNode()              // Get first node from iterator.
.parentElement;          // Found node is a "pure" text node, get parent <text> element.

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

Для рабочей демонстрации взгляните наследующий фрагмент:

function nodeIterator(value) {
  return function() {
    return document.createNodeIterator(
      this,                  // The root node of the searched DOM sub-tree.
      NodeFilter.SHOW_TEXT,  // Look for text nodes only.
      {
        acceptNode(node) {   // The filter method of interface NodeFilter
          return new RegExp(value).test(node.textContent)  // Check if text contains string
            ? NodeFilter.FILTER_ACCEPT                     // Found: accept node
            : NodeFilter.FILTER_REJECT;                    // Not found: reject and continue
      }
    })
    .nextNode()              // Get first node from iterator.
    .parentElement;          // Found node is a "pure" text node, get parent <text> element.
  }
}

const filter = nodeIterator("id0");
const sel = d3.select("svg").select(filter);

// Manipulate the selection:...
// sel.append("g")
//   .attr("transform", "...");

console.log(sel.node());
<script src="https://d3js.org/d3.v5.js"></script>

<svg>
  <g>
    <g>
      <text> id3 </text>
      <text> 73% </text>
      <svg></svg>
    </g>
    <g>
      <svg></svg>
    </g>
    <g>
      <text> id0 </text>
      <text> 11% </text>
      <svg></svg>
    </g>
    <g>
      <text> id1 </text>
      <text> 66% </text>
      <svg></svg>
    </g>
    <g>
      <svg></svg>
    </g>
  </g>
</svg>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...