Статьи

В центре внимания: jQuery replaceText

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

Сегодня мы рассмотрим отличный плагин replaceText jQuery. Заинтересованы? Давайте начнем после прыжка.


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

Эта серия статей призвана решить эту проблему, представив читателю действительно хорошо написанный и полезный код — будь то плагин, эффект или технология. Кроме того, если он достаточно мал, мы попытаемся разобрать код и понять, как он работает вуду. Если он намного больше, мы попытаемся создать мини-проект, чтобы изучить веревки и, надеюсь, понять, как использовать его в реальном мире.


replaceText Сообщение в блоге

Мы начинаем, сосредотачиваясь на превосходном плагине replaceText Бена Алмана. Вот небольшая информация:

  • Тип: плагин
  • Технология: JavaScript [Построен на библиотеке jQuery]
  • Автор: Бен Алман
  • Функция: ненавязчивый, лаконичный способ заменить текстовое содержимое

Замена контента на вашей странице звучит очень просто. В конце концов, родной метод JavaScript replace кажется, делает то же самое. Если вы чувствуете себя особенно ленивым, jQuery также делает неприлично легким замену всего содержимого контейнера.

1
2
3
4
5
6
// Using just replace
$(«#container»).text().replace(/text/g,’replacement text’)
 
// Replacing the *entire* content of the container
var lazyFool =»entire content with text replaced externally»;
$(«#container»).html(lazyFool);

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

Основная проблема с этими подходами состоит в том, что они сглаживают структуру DOM, эффективно испаряя каждый нетекстовый узел, который содержит контейнер. Если вам удастся заменить сам html, используя innerHTML или html jQuery, вы все равно отцепите каждый обработчик событий, связанный с любым из его дочерних элементов, что является полным прерывателем сделки. Это основная проблема, которую этот плагин ищет для решения.


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

Текстовые узлы появляются в DOM точно так же, как обычные узлы, за исключением того, что они не могут содержать дочерние узлы. Содержащийся в них текст может быть получен с помощью nodeValue или data .

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

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


Как и большинство хорошо написанных плагинов jQuery, он чрезвычайно прост в использовании. Он использует следующий синтаксис:

$ (контейнер) .replaceText (текст, замена);

Например, если вам нужно заменить все вхождения слова ‘val’ на ‘value’, например, вам нужно создать экземпляр плагина следующим образом:

1
$(«#container»).replaceText( «val», «value» );

Да, это действительно так просто. Плагин позаботится обо всем за вас.

Если вы из тех, кто сходит с ума от регулярных выражений, вы тоже можете это сделать!

1
$(«#container»).replaceText( /(val)/gi, «value» );

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


Поскольку плагин состоит всего из 25 строк кода, при отсутствии комментариев и тому подобного, мы сделаем краткий обзор источника, объясняющего, какой фрагмент кода выполняет то, что и для какой цели.

Вот источник, для вашей справки. Мы рассмотрим каждую часть подробно ниже.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$.fn.replaceText = function( search, replace, text_only ) {
  return this.each(function(){
    var node = this.firstChild,
      val,
      new_val,
      remove = [];
    if ( node ) {
      do {
        if ( node.nodeType === 3 ) {
          val = node.nodeValue;
          new_val = val.replace( search, replace );
          if ( new_val !== val ) {
            if ( !text_only && /</.test( new_val ) ) {
              $(node).before( new_val );
              remove.push( node );
            } else {
              node.nodeValue = new_val;
            }
          }
        }
      } while ( node = node.nextSibling );
    }
    remove.length && $(remove).remove();
  });
};

Хорошо, давайте сделаем код на умеренно высоком уровне.

1
$.fn.replaceText = function( search, replace, text_only ) {};

Шаг 1 — Общая оболочка для плагина jQuery. Автор справедливо воздерживается от добавления vapid-опций, поскольку предоставляемая функциональность достаточно проста, чтобы ее оправдать. Параметры должны быть text_onlytext_only будет обработан чуть позже.

1
return this.each(function(){});

Шаг 2 — this.each гарантирует, что плагин ведет себя, когда плагин передается в коллекции элементов.

1
2
3
4
var node = this.firstChild,
        val,
        new_val,
        remove = [];

Шаг 3 — Обязательное объявление переменных, которые мы собираемся использовать.

  • node содержит первый дочерний элемент узла.
  • val содержит текущее значение узла.
  • new_val содержит обновленное значение узла.
  • remove это массив, который будет содержать узел, который необходимо удалить из DOM. Я подробно расскажу об этом чуть позже.
1
if ( node ) {}

Шаг 4 — Мы проверяем, существует ли на самом деле узел, то есть переданный контейнер имеет дочерние элементы. Помните, что node содержит первый дочерний элемент переданного элемента.

1
do{} while ( node = node.nextSibling );

Шаг 5 — Цикл, по сути, проходит через дочерние узлы, заканчивая, когда цикл находится в конечном узле.

1
if ( node.nodeType === 3 ) {}

Шаг 6 — Это интересная часть. Мы обращаемся к свойству nodeType [только для чтения] узла, чтобы определить тип его узла. Значение 3 подразумевает, что это текстовый узел, поэтому мы можем продолжить. Если это облегчает вам жизнь, вы можете переписать это так: if ( node.nodeType == Node.TEXT_NODE ) {} .

1
2
val = node.nodeValue;
new_val = val.replace( search, replace );

Шаг 7 — Мы сохраняем текущее значение текстового узла, сначала вверх. Далее мы быстро заменяем экземпляры ключевого слова заменой на собственный метод JavaScript replace . Результаты сохраняются в переменной new_val .

1
if ( new_val !== val ) {}

Шаг 8 — Продолжать, только если значение изменилось!

1
2
3
4
if ( !text_only && /</.test( new_val ) ) {
   $(node).before( new_val );
   remove.push( node );
}

Шаг 9а. Запомните параметр text_only . Это входит в игру здесь. Это используется, чтобы указать, должен ли контейнер рассматриваться как тот, который содержит узлы элементов внутри. Код также выполняет быструю внутреннюю проверку, чтобы увидеть, содержит ли он содержимое HTML. Это делается путем поиска открывающего тега в содержимом new_val .

Если да, текстовый узел вставляется перед текущим узлом, а текущий узел добавляется в массив remove который будет обработан позже.

1
2
3
else {
         node.nodeValue = new_val;
        }

Шаг 9b — Если это просто текст, напрямую введите новый текст в узел, не проходя через шумиху по DOM.

1
remove.length && $(remove).remove();

Шаг 10 — Наконец, после завершения цикла мы быстро удаляем накопленные узлы из DOM. Причина, по которой мы делаем это после завершения цикла, заключается в том, что удаление узла в середине работы приведет к повреждению самого цикла.


Небольшой проект, который мы собираемся построить сегодня, довольно прост. Вот список наших требований:

  • Основное требование : применение эффекта выделения к тексту, извлеченному из пользовательского ввода. Плагин должен полностью об этом позаботиться.
  • Вторичное требование : Снятие выделения на лету, по мере необходимости. Мы будем подбирать крошечный фрагмент кода, чтобы помочь с этим. Не готово к производству, но должно хорошо работать в наших целях

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

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


01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html lang=»en-GB»>
    <head>
        <title>Deconstruction: jQuery replaceText</title>
        <link rel=»stylesheet» href=»style.css» />
    </head>
 
    <body>
        <div id=»container»>
            <h1>Deconstruction: jQuery replaceText</h1>
        <div>by Siddharth for the lovely folks at Nettuts+</div>
         
        <p>This page uses the popular replaceText plugin by Ben Alman.
         
        <form id=»search»><input id=»keyword» type=»text» /><a id=»apply-highlight» href=»#»>Apply highlight</a><a id=»remove-highlight» href=»#»>Remove highlight</a></form>
        <p id=»haiz»> <— Assorted text here —></div>
    <script src=»js/jquery.js»></script>
    <script src=»js/tapas.js»></script>
 
    </body>
</html>

HTML должен быть довольно объяснительным. Все, что я сделал, это создал текстовый ввод, две ссылки для применения и удаления выделения, а также абзац, содержащий некоторый различный текст.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
body{
    font-family: «Myriad Pro», «Lucida Grande», «Verdana», sans-serif;
    font-size: 16px;
}
 
p{
    margin: 20px 0 40px 0;
}
 
 
h1{
    font-size: 36px;
    padding: 0;
    margin: 7px 0;
}
 
h2{
    font-size: 24px;
}
 
#container{
    width: 900px;
    margin-left: auto;
    margin-right: auto;
    padding: 50px 0 0 0;
    position: relative;
}
 
#haiz {
    padding: 20px;
    background: #EFEFEF;
    -moz-border-radius:15px;
    -webkit-border-radius: 15px;
    border: 1px solid #C9C9C9;
}
 
#search {
    width: 600px;
    margin: 40px auto;
    text-align: center;
}
 
#keyword {
    width: 150px;
    height: 30px;
    padding: 0 10px;
    border: 1px solid #C9C9C9;
    -moz-border-radius:5px;
    -webkit-border-radius: 5px;
    background: #F0F0F0;
    font-size: 18px;
}
 
#apply-highlight, #remove-highlight {
    padding-left: 40px;
}
 
.highlight {
    background-color: yellow;
}

Опять же, довольно понятны и довольно просты. Единственное, на что стоит обратить внимание — это класс с именем highlight который я определяю. Это будет применено к тексту, который нам нужно выделить.

На этом этапе ваша страница должна выглядеть так:

Учебное изображение

Первая задача дня — быстро соединить нашу ссылку с их обработчиками, чтобы текст был выделен и не выделен соответствующим образом.

1
2
3
4
5
var searchInput = $(«#keyword»),
      searchTerm,
      searchRegex;
$(«#apply-highlight»).click(highLight);
$(«#remove-highlight»).bind(«click», function(){$(«#haiz»).removeHighlight();});

Должно быть довольно просто. Я объявляю несколько переменных для последующего использования и прикрепляю ссылки к их обработчикам. highLight и removeHighlight — чрезвычайно простые функции, которые мы рассмотрим ниже.

1
2
3
4
5
function highLight() {
   searchTerm = searchInput.val();
   searchRegex = new RegExp(searchTerm, ‘g’);
   $(«#haiz *»).replaceText( searchRegex, ‘<span class=»highlight»>’+searchTerm+’
}
  • Я решил создать ванильную функцию, а не плагин jQuery, потому что я ленив как куча камней. Мы начнем с захвата значения поля ввода.
  • Затем мы создаем объект регулярного выражения, используя ключевое слово поиска.
  • Наконец, мы replaceText плагин replaceText , передавая соответствующие значения. Я решил напрямую включить searchTerm в разметку для краткости.
1
2
3
4
5
6
7
jQuery.fn.removeHighlight = function() {
   return this.find(«span.highlight»).each(function() {
      with (this.parentNode) {
         replaceChild(this.firstChild, this);
      }
 })
};

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

Я просто ищу каждый тег span с классом highlight и заменяю весь узел значением, которое он содержит.

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


И мы сделали. Мы взглянули на невероятно полезный плагин, просмотрели исходный код и, наконец, закончили с созданием мини-проекта с ним.