Каждую неделю мы будем ультра-сфокусированно смотреть на интересный и полезный эффект, плагин, хакер, библиотеку или даже изящную технологию. Затем мы попытаемся либо разобрать код, либо создать забавный маленький проект с ним.
Сегодня мы рассмотрим отличный плагин 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_only
— text_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. Причина, по которой мы делаем это после завершения цикла, заключается в том, что удаление узла в середине работы приведет к повреждению самого цикла.
проект
Небольшой проект, который мы собираемся построить сегодня, довольно прост. Вот список наших требований:
- Основное требование : применение эффекта выделения к тексту, извлеченному из пользовательского ввода. Плагин должен полностью об этом позаботиться.
- Вторичное требование : Снятие выделения на лету, по мере необходимости. Мы будем подбирать крошечный фрагмент кода, чтобы помочь с этим. Не готово к производству, но должно хорошо работать в наших целях
Примечание. Это скорее подтверждение концепции, чем то, что вы можете просто развернуть без изменений. Очевидно, что в целях предотвращения того, чтобы статья стала непривлекательной, я пропустил ряд разделов, имеющих первостепенное значение для готового кода, например, для проверки.
Фактический фокус здесь должен быть на самом плагине и методах разработки, которые он содержит. Помните, это скорее бета-демонстрация, демонстрирующая что-то классное, что можно сделать с помощью этого плагина. Всегда дезинфицируйте и проверяйте ваш вклад!
Основа: HTML и CSS
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
который я определяю. Это будет применено к тексту, который нам нужно выделить.
На этом этапе ваша страница должна выглядеть так:
Взаимодействие: JavaScript
Первая задача дня — быстро соединить нашу ссылку с их обработчиками, чтобы текст был выделен и не выделен соответствующим образом.
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
и заменяю весь узел значением, которое он содержит.
Прежде чем подготовить вилы, помните, что это только для демонстрационных целей. Для вашего собственного приложения вам понадобится гораздо более сложный, незаметный метод.
Завершение
И мы сделали. Мы взглянули на невероятно полезный плагин, просмотрели исходный код и, наконец, закончили с созданием мини-проекта с ним.