Страницы

Поиск по вопросам

воскресенье, 1 декабря 2019 г.

Реализация highlight

#javascript #html #jquery


Нужно реализовать подсветку текста в div при вводе в input без использования сторонних
плагинов. В существующем коде надо решить две проблемы:


При замене html перестают работать события на , при этом элементы
могут быть разные. Можно ли делать реинициализацию(?) после замены.
Как избежать замены в тэгах? Начните для примера вводить class.


Приветствуется лаконичное и легкое решение на jQuery/JavaScript. 



$(".hint").on("click", function() {
  alert('ok');
});

$("input").on("input", function() {

  var text = $(this).val();
  $('#text').html($('#text').html().replace(RegExp("(|<\/highlight>)",
"gi"), ''));

  if (!text) return;

  var re = $('#text').html().replace(RegExp("(" + text + ")", "gi"), '$1');
  $('#text').html(re);

});
highlight {
  background: yellow;
}

a {
  text-decoration: underline;
  cursor: pointer;
}




Ответы

Ответ 1



На ответ не тянет, но так, информация к размышлению, возможно. Возьмите исходный html. Разбейте по тэгам. Сложите текст (исключая тэги), на места тэгов воткните какой-нибудь символ разделитель. в поисковый шаблон между каждым символом вставьте необязательный этот разделитель, и замените регуляркой, обернув в . Замените разделитель обратно на тэги. Описанное выше реализовано в коде ниже. $("#text, #result").on("click", ".hint", function() { alert('ok'); }); $("input").on("input", function() { console.clear() var text = $(this).val(); var html = $("#text").html().split(/(<.*?>)/); var filtered = html.filter(function(v, idx){ return idx % 2 == 0; }).join("|"); var x = text.split('').join("(\\|?)"); var re = new RegExp("(" + x + ")"); var hl = filtered.replace(re, "$1"); var z = -1; var result = hl.replace(/\|/g, () => html[z += 2] ); $("#result").html(result); }); highlight { background: yellow; } a { text-decoration: underline; cursor: pointer; }
Съешь еще этих мягких французских булок
Оно даже в ФФ вроде подсвечивает, однако получаемый html будет не валидный, например при вводе их фр вы получите их фр поэтому требуется еще провести замену всех тэгов, которые найдены между на

Ответ 2



Поиск любого текста, в том числе пересекающего различные элементы. Чтобы сильно не усложнять код, отформатировал html так, чтобы между блочными элементами был ровно один пробел. И вообще чтобы более одного пробела подряд не встречалось, иначе будет затруднительно угадать разметку при вводе поисковой строки. function getTextNodes(el) { return [...el.childNodes].flatMap( x => x.nodeType === Node.ELEMENT_NODE ? getTextNodes(x) : x.nodeType === Node.TEXT_NODE ? [x] : [] ) } document.querySelector("input").addEventListener('input', function (e) { var text = e.target.value; var container = document.querySelector(".search-content"); for (var mark of container.querySelectorAll("mark")) { mark.outerHTML = mark.innerHTML; } if (!text) { return; } var mark = document.createElement('mark'); mark.textContent = text; var nodes = getTextNodes(container) NODE: for (var q=0; q 1) { var df = document.createDocumentFragment(), last; df.appendChild(document.createTextNode(parts[0])); for(var w=1; w= text.length) { break; } else if (text.slice(p).startsWith(cur)) { p += cur.length; } else { continue NODE; } } if (cur.startsWith(text.slice(p))) { var r = text.length - p; cur = node.nodeValue; var df = document.createDocumentFragment(); df.appendChild(document.createTextNode(cur.slice(0, -l))); var m = document.createElement('mark'); m.textContent = cur.slice(-l); df.appendChild(m); node.parentElement.replaceChild(df, node); while (++q < w) { var m = document.createElement('mark'); nodes[q].parentElement.replaceChild(m, nodes[q]); m.appendChild(nodes[q]); } var df = document.createDocumentFragment(); var m = document.createElement('mark'); m.textContent = text.slice(p); df.appendChild(m); df.appendChild(last = document.createTextNode(nodes[w].nodeValue.slice(r))); nodes[w].parentElement.replaceChild(df, nodes[w]); nodes[w] = last; --q; break; } } } } }) input { line-height: 30px; font-size: 20px; border: 1px solid #333; width: 100%; box-sizing: border-box; padding: 0 5px; position: sticky; top: 8px; }

Айзек Азимова: Конец вечности

Техник Эндрю Харлан вошел в капсулу. Капсула находилась внутри колодца, образованного редкими вертикальными прутьями. Прутья плотно облегали круглые стенки капсулы и, уходя вверх, терялись в непроницаемой дымке в шести футах над головой Харлана. Харлан повернул рукоятки управления и плавно нажал на пусковой рычаг. Капсула осталась неподвижной. Харлана это не удивило. Капсула не должна была двигаться ни вверх, ни вниз, ни вправо, ни влево, ни вперед, ни назад. Только промежутки между прутьями словно растаяли, затянувшись серой пеленой, которая была твердой, но все-таки нематериальной. Харлан почувствовал легкую дрожь в желудке и слабое головокружение и по этим признакам понял, что капсула со всем своим содержимым стремительно мчится в будущее сквозь Вечность. Он вошел в капсулу в 575-м Столетии. Этот Сектор Вечности стал его домом два года назад. Никогда до этого ему не приходилось забираться в будущее так далеко. Но сейчас он направляется в 2456-е Столетие. Месяц назад при одной только мысли об этом Харлану стало бы не по себе. Его родное 95-е Столетие осталось далеко в прошлом. Это был век патриархальных традиций, в котором атомная энергия находилась под запретом, а всем строительным материалам предпочитали дерево. Век славился своими напитками, которые в обмен на семена клевера вывозились почти во все другие Столетия. Хотя Эндрю Харлан не был дома с тех пор, как он в пятнадцать лет стал Учеником и прошел специальную подготовку, его никогда не оставляла тоска по родным Временам. Между 95-м и 2456-м Столетиями пролегло почти двести сорок тысяч лет, а это ощутимый промежуток даже для закаленного Вечного. При обычных обстоятельствах все было бы именно так. Однако сейчас Харлану было не до абстрактных размышлений. Рулоны перфолент оттягивали его карманы, планы тяжким грузом лежали на сердце, мысли были скованы страхом и неуверенностью. Он машинально остановил капсулу в нужном Столетии. Странно, что Техник способен волноваться.

Тестовый список для поиска
  • Пункт 1
  • Пункт 2
  • Пункт 3
  • Пункт 4 Подсписок
    • Пункт 4.1
    • Пункт 4.2
    • Пункт 4.3


Ответ 3



Алгоритм такой: получаем список всех дочерних элементов идем по этому списку, если: это текстовый элемент textNode, то проверяем наличие highlight если это обычный элемент, то переходим к 1 пункту в контексте этого элемента class Highlight { constructor(input, content) { this.input_ = input; this.content_ = content; this.query_ = ''; this.cache_ = []; this.init_(); } // Проверяем является ли нода текстовой isTextNode_(node) { return node.nodeName === '#text'; } // Проверяет, если совпадение в тексте hasMatch_(node) { return node.textContent.includes(this.query_); } highlightNodes_(element) { const children = element.childNodes; // children не подойдет, так как не возвращает textNode for(let child of children) { if(this.isTextNode_(child) && this.hasMatch_(child)) { this.highlightByQuery_(child); } else { this.highlightNodes_(child); } } } // Будет заниматься подсветкой highlightByQuery_(node) { const queryRegexp = new RegExp(this.query_, 'ig'); // тут можно использовать html const markedNode = document.createElement('span'); markedNode.innerHTML = node.textContent .replace(queryRegexp, str => `${str}`); node.replaceWith(markedNode); // Кешируем результат, что бы потом сбросить this.cache_.push(markedNode); } resetSearch_() { for(let node of this.cache_) { const regExp = /\<\/?mark\>/g; const content = node.innerHTML.replace(regExp, ''); const textNode = document.createTextNode(content); node.replaceWith(textNode); } this.cache_ = []; this.query_ = ''; } init_() { this.input_.onchange = this.search.bind(this); } search() { this.resetSearch_(); this.query_ = this.input_.value; if(!this.query_.trim()) return; this.highlightNodes_(this.content_); } } // Собственно сам поиск const content = document.querySelector('.search-content'); const queryInput = document.querySelector('input'); const highlight = new Highlight(queryInput, content); p { line-height: 20px; padding: 0 10px; } .controls { margin: 0 0 10px 0; display: flex; justify-content: center; } input { line-height: 30px; font-size: 20px; border: 1px solid #333; padding: 0 5px; } button { background: #fff; border: 1px solid #333; margin-left: 10px; } mark { }

Айзек Азимова: Конец вечности

Полный роман можно прочитать здесь

Техник Эндрю Харлан вошел в капсулу. Капсула находилась внутри колодца, образованного редкими вертикальными прутьями. Прутья плотно облегали круглые стенки капсулы и, уходя вверх, терялись в непроницаемой дымке в шести футах над головой Харлана. Харлан повернул рукоятки управления и плавно нажал на пусковой рычаг. Капсула осталась неподвижной. Харлана это не удивило. Капсула не должна была двигаться ни вверх, ни вниз, ни вправо, ни влево, ни вперед, ни назад. Только промежутки между прутьями словно растаяли, затянувшись серой пеленой, которая была твердой, но все-таки нематериальной. Харлан почувствовал легкую дрожь в желудке и слабое головокружение и по этим признакам понял, что капсула со всем своим содержимым стремительно мчится в будущее сквозь Вечность. Он вошел в капсулу в 575-м Столетии. Этот Сектор Вечности стал его домом два года назад. Никогда до этого ему не приходилось забираться в будущее так далеко. Но сейчас он направляется в 2456-е Столетие. Месяц назад при одной только мысли об этом Харлану стало бы не по себе. Его родное 95-е Столетие осталось далеко в прошлом. Это был век патриархальных традиций, в котором атомная энергия находилась под запретом, а всем строительным материалам предпочитали дерево. Век славился своими напитками, которые в обмен на семена клевера вывозились почти во все другие Столетия. Хотя Эндрю Харлан не был дома с тех пор, как он в пятнадцать лет стал Учеником и прошел специальную подготовку, его никогда не оставляла тоска по родным Временам. Между 95-м и 2456-м Столетиями пролегло почти двести сорок тысяч лет, а это ощутимый промежуток даже для закаленного Вечного. При обычных обстоятельствах все было бы именно так. Однако сейчас Харлану было не до абстрактных размышлений. Рулоны перфолент оттягивали его карманы, планы тяжким грузом лежали на сердце, мысли были скованы страхом и неуверенностью. Он машинально остановил капсулу в нужном Столетии. Странно, что Техник способен волноваться.

    Тестовый список для поиска
  • Пункт 1
  • Пункт 2
  • Пункт 3
  • Пункт 4
      Подсписок
    • Пункт 4.1
    • Пункт 4.2
    • Пункт 4.3
Плюсы: не нужно беспокоиться про всякие обработчики, потому что работа идет с textNode, там ничего не повесить Минусы: если ввести Запрос c таким html: Запрос, то не подсветит

Ответ 4



Обработка текста содержащего html элементы в общем случае весьма не тривиальная задача. Так как то, что видит пользователь и то, как это отображено в разметке может разительно отличаться из-за стилей, и специальной работой с пробельными символами в браузере. В данном случае можно использовать следующий алгоритм: взять текстовое содержимое нужного элемента, это примерно текст, который видит пользователь. найти в нем индексы подстрок, совпадающих с введенной строкой. html-элементы можно представить в виде дерева, в котором в листовыми узлами являются текстовые элементы исходя из пункта 3, обход дерева в глубину, позволит пройтись по всему тексту от начала до конца, попутно позволяя считать индексы каждого символа в результирующей строке при обходе, можно собрать текстовые узлы, текст которых полностью либо частично входит в искомые индексы теперь осталось пройтись по выбранным узлам и в соответствии с нужными индексами заменить в них куски строк, обернув их в нужный элемент, который можно подсветить. Чтобы данный алгоритм можно было применять несколько раз - перед его применением нужно удалить элементы добавленные на прошлом ходу, чтобы устранить их дублирование. Небольшой пример: $('.hint').click(function() { console.log(this.textContent) }); function* deep(container) { // обход дерева в глубину for (var nodes = Array.from(container.childNodes); nodes.length;) { var node = nodes.shift(); // если попался элемент - добавляем дочение узлы в начало проверки if (node.nodeType == Node.ELEMENT_NODE) { nodes.unshift(...node.childNodes); continue; } // если попался текстовый узел - возвращаем if (node.nodeType == Node.TEXT_NODE) yield node; } } // ищем в контейнере текстовые узлы, в которых полностью или частично расположены искомые строки function find(container, entries) { var finded = []; var nodes = deep(container); var node = nodes.next().value; var curNodeStartIndex = 0; for (var entry of entries) { // для каждого интервала [startIndex, endIndex) соответствующего искомой строке while (curNodeStartIndex + node.textContent.length <= entry.startIndex) { // пропускаем узлы, пока не найдем узел, на который приходится startIndex curNodeStartIndex += node.textContent.length; node = nodes.next().value; } //сохраняем индекс символа в выбранном узле var start = entry.startIndex - curNodeStartIndex; while (curNodeStartIndex + node.textContent.length < entry.endIndex) { // добавляем в список новые узлы, пока не найдем узел, на который приходится endIndex curNodeStartIndex += node.textContent.length; finded.push({ node, start: start - node.textContent.length, end: node.textContent.length }); start = 0; node = nodes.next().value; } //сохраняем индекс символа в выбранном узле var end = entry.endIndex - curNodeStartIndex; finded.push({ node, start: start - node.textContent.length, end: (end - node.textContent.length) || node.textContent.length }); } return finded; } function highlight(container, textToFind, ignorecase) { var body = ignorecase ? container.textContent.toLowerCase() : container.textContent; textToFind = ignorecase ? textToFind.toLowerCase() : textToFind; // если передали пустую строку, либо общая длина текста меньше искомого фрагмента, то можно ничего не делать if (textToFind.length == 0 || body.length < textToFind.length) return; // ищем интервалы вхождений var points = [] for ( var findIndex = body.indexOf(textToFind, findIndex + 1); findIndex != -1; findIndex = body.indexOf(textToFind, findIndex + textToFind.length + 1)) { points.push({ startIndex: findIndex, endIndex: findIndex + textToFind.length }); } var nodes = find(container, points); // для каждого найденного узла вставляем в найденном месте элемент дял подсветки nodes.forEach(node => { var before = node.node.textContent.slice(0, node.start); var hl = document.createElement('mark'); hl.textContent = node.node.textContent.slice(node.start, node.end); var after = node.node.textContent.slice(node.end); node.node.textContent = after; node.node.parentNode.insertBefore(hl, node.node); hl.insertAdjacentText('beforebegin', before); }); } function removeHighlight(container) { // бежим по всем элементам подсветки и заменяем их текстовым содержимым for (hl of container.querySelectorAll('mark')) { hl.replaceWith(hl.textContent); } // сливаем все идущие подряд текстовые узлы в один. container.normalize(); } (function($) { $.fn.highlight = function(options) { var defaultOptions = { text: '', ignorecase: true } options = $.extend(defaultOptions, options); return this.each(function() { removeHighlight(this); highlight(this, options.text, options.ignorecase); }); }; })(jQuery); $('input').on('input', function() { $('#text').highlight({ text: this.value }); }).triggerHandler('input'); $('textarea').on('input', function() { console.dir(this.value); $('#text2').highlight({ text: this.value }); }).triggerHandler('input'); .text { margin: 0 0 20px 0; } hl { background-color: yellow; } textarea { position: sticky; top: 8px; width: 100%; }
Съешь еще этих мягких французских булок

Айзек Азимова: Конец вечности

Полный роман можно прочитать здесь

Техник Эндрю Харлан вошел в капсулу. Капсула находилась внутри колодца, образованного редкими вертикальными прутьями. Прутья плотно облегали круглые стенки капсулы и, уходя вверх, терялись в непроницаемой дымке в шести футах над головой Харлана. Харлан повернул рукоятки управления и плавно нажал на пусковой рычаг. Капсула осталась неподвижной. Харлана это не удивило. Капсула не должна была двигаться ни вверх, ни вниз, ни вправо, ни влево, ни вперед, ни назад. Только промежутки между прутьями словно растаяли, затянувшись серой пеленой, которая была твердой, но все-таки нематериальной. Харлан почувствовал легкую дрожь в желудке и слабое головокружение и по этим признакам понял, что капсула со всем своим содержимым стремительно мчится в будущее сквозь Вечность. Он вошел в капсулу в 575-м Столетии. Этот Сектор Вечности стал его домом два года назад. Никогда до этого ему не приходилось забираться в будущее так далеко. Но сейчас он направляется в 2456-е Столетие. Месяц назад при одной только мысли об этом Харлану стало бы не по себе. Его родное 95-е Столетие осталось далеко в прошлом. Это был век патриархальных традиций, в котором атомная энергия находилась под запретом, а всем строительным материалам предпочитали дерево. Век славился своими напитками, которые в обмен на семена клевера вывозились почти во все другие Столетия. Хотя Эндрю Харлан не был дома с тех пор, как он в пятнадцать лет стал Учеником и прошел специальную подготовку, его никогда не оставляла тоска по родным Временам. Между 95-м и 2456-м Столетиями пролегло почти двести сорок тысяч лет, а это ощутимый промежуток даже для закаленного Вечного. При обычных обстоятельствах все было бы именно так. Однако сейчас Харлану было не до абстрактных размышлений. Рулоны перфолент оттягивали его карманы, планы тяжким грузом лежали на сердце, мысли были скованы страхом и неуверенностью. Он машинально остановил капсулу в нужном Столетии. Странно, что Техник способен волноваться.

    Тестовый список для поиска
  • Пункт 1
  • Пункт 2
  • Пункт 3
  • Пункт 4
      Подсписок
    • Пункт 4.1
    • Пункт 4.2
    • Пункт 4.3


Ответ 5



Все же в мои сниппеты попал собственный код переписанный на JS. Проблему с тегами мне пожалуй проще решать на стороне сервера оборачивая чистый текст определенными тэгами, например span, и делая вызов навроде highlight('#text>span') function highlight(s,e){ var e=document.querySelectorAll(e); for ( var i=0; i|<\/highlight>)", "gi"), ''); // remove highlight if(s) e[i].innerHTML=e[i].innerHTML.replace(RegExp("("+s+ ")", "gi"), '$1'); // highlight } } с универсальными вариантами вызовов highlight

Комментариев нет:

Отправить комментарий