- PVSM.RU - https://www.pvsm.ru -

RainyJs — как Angular, только для Ajax

Я не сторонник велосипедов. Прежде чем начать разрабатывать свое решение тривиальной на мой взгляд задачи, всегда трачу уйму времени на поиски уже существующих библиотек или модулей. И далеко не потому, что мой код заведомо будет хуже стороннего. Просто, зачем придумывать то, что уже создано, проверено и отлажено. Гораздо лучше потратить время на создание чего-нибудь нового, непридуманного доселе. Однако в этот раз мне все-таки пришлось садиться за разработку самому. В статье речь пойдет об удобной js-библиотеке, позволяющей «связывать» данные.

С чего все началось или чего не хватает Angular

Прежде всего перечислю задачи, с которыми мне пришлось столкнуться и которые не получилось решить существующими библиотеками. Примеры будут приводиться для Angular, но тоже самое в полной мере относится и к Angular Light, и к другим многочисленным библиотекам. Итак, поехали…

01) Одностороннее связывание данных

Это странно, но вот как реализовать одностороннее связывание данных я понять так и не смог. В своей работе в основном использую фреймворк Yii2, и вот вам простейшая задача: сгенерировать поле ввода (input) и связать его с блоком (div). Казалось бы, проще-простого, ан-нет: при генерации inputa, хотя в нем и установлено его значение, но angular данное значение не учитывает и берет значение «модели», которое при загрузке страницы является пустым.

Как вариант, можно было бы использовать директиву «ng-init» (или «al-init» для Angular Light), и для простейших случаев проблема, действительно, будет решена. Но есть одно но: если вы используете компоненты динамического обновления данных (такие как Editable) получается следующее: вы изменяете значение, сохраняете его => требуется обновить привязку => а при обновлении связывания, вновь берется значение из «ng-init», которое уже не является актуальным. Беда…

02) Модель-Представление-Контроллер

Сейчас в меня полетят камни, но я против использования MVC-схемы при отображении html-страниц.

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

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

03) Динамическая загрузка содержимого

Именно данная проблема и стала решающим фактором для начала разработки собственного решения. На самом деле предыдущие пункты, в принципе, можно решить через костыли и груды ненужного кода. А вот с обновлением связей после загрузки содержимого все печально.

Связывание данных в Angular, как вы знаете, производится лишь при загрузке страницы. Но что делать, если содержимое генерируется динамически или загружается через ajax-запросы, т.е. если требуется выполнить связывание уже после первоначальной загрузки? Единственный способ, который удалось обнаружить — это использование $scope.$apply() в контроллере. Казалось бы, выход найден? Отнюдь. Дело в том, что в содержимом, пришедшем с сервера и загруженном на страницу, также могут быть (и будут) элементы, требующие связывания данных. Как быть в этом случае? Не пихать же все возможные алгоритмы сайта в один большой мега-контроллер.
Или вот еще пример: как поступать, когда в загруженном содержимом присутствуют блоки, содержимое которых также загружается или генерируется динамически? Я далеко не спец в Angular, и возможно, есть способы выполнить данные задачи. Но скорей всего это будет далеко не просто и очень громоздко.

RainyJs — установка и быстрый старт

Библиотека является полностью автономной и в сжатом виде весит всего около 7Кб.

Для использования просто скачайте файл rainy.js [1] и подключите его на свою страницу.
Первоначальное связывание данных, как и в Angular, выполняется автоматически при завершении загрузки страницы. Но также связывание данных выполняется и при ajax-загрузке содержимого, причем в этом случае обрабатывается только непосредственно загруженный фрагмент страницы.

Для «прямого» обновления связей (например, после динамического создания элемента) можно вызвать функцию rainy(elem), где в качестве параметра можно передать селектор, dom-элемент, либо даже jQuery-объект. Если вызвать данную функцию без параметров, то будет выполнено обновление связей на всей странице.

Базовые директивы связывания данных

Сейчас библиотека содержит всего 12 директив, что делает ее достаточно простой в освоении.

Основными директивами являются «rxname» и «rxdata». Первая устанавливается в элемент-источник и задает имя переменной, вторая устанавливается в элемент-приемник и указывает имя переменной, из которой требуется брать данные. Если данные требуется получать из нескольких источников, то в «rxdata» помещается список имен переменных через пробел.

Пример использования приведен ниже:

	<label>Ваше имя:</label><br>
	<input rxname="var01" value="World"><br>
	Hello, <span rxdata="var01"></span>!

[Редактировать] [2]

Префиксы и постфиксы в отображении

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

  • {{rxname}} — наименование переменной источника, которая была изменена
  • {{value}} — результирующее значение, которое было бы отображено без форматирования
  • {{var01}} — значение переменной с именем «var01» (используется, если несколько источников)

Пример использования приведен ниже:

	<label>Укажите процент ставки:</label><br>
	<input rxname="var02" style="width:150px;" type="range">
	<div rxdata="var02" rxview="Вы выбрали: {{value}}%"></div>

[Редактировать] [3]

Отображение/Скрытие блока данных

Еще одной часто встречающейся задачей является отображение/скрытие текстового блока по флажку или по некоторому условию. Для этих целей отведена отдельная директива «rxshow». В качестве ее значения можно поместить либо наименование переменной, либо js-код условия срабатывания.

Пример использования приведен ниже:

	<input rxname="var03" type="checkbox">
	<label>Установи флажок для отображения блока</label><br>
	<div rxdata="var03" rxshow="value">
	Этот текст будет виден только при установленном флажке.
	</div>

[Редактировать] [4]

Выполнение любого кода над элементом

Следующей директивой, о которой пойдет речь, является «rxcode». Она предназначена для более сложных вариантов связывания данных и представляет из себя js-код, который выполнится в элементе-приемнике перед обновлением его содержимого. Здесь можно как переопределить устанавливаемое значение, просто вернув новое, так и вообще отменить изменение, вернув undefined.

Также данная директива используется для выполнения абсолютно любого кода над элементом. Например, изменить класс элемента или сделать его недоступным можно именно через нее. Знаю-знаю, в angular для этого отведены отдельные директивы и для изменения класса достаточно лишь установить значение атрибута. Признаюсь, и у меня тоже были мысли по этому поводу, но в итоге решил все же не плодить лишних параметров, ведь простота — залог успеха.

Внутри «rxcode» доступны следующие переменные:

  • sender — dom-элемент, из которого пришло событие
  • rxname — наименование переменной, значение которой было изменено
  • values — массив значений переменных, указанных в rxdata
  • value — значение первой переменной из массива [values]
  • self — текущий dom-элемент (приемник события)

Пример использования приведен ниже:

	<label>При отрицательном значении текст будет красным:</label><br />
	<input rxname="var05" value="2" type="number"><br>
	<div rxdata="var05" rxcode="
		if(value < 0){ self.classList.add('red'); }
			else { self.classList.remove('red'); }
		return 'Введено значение: ' + (value||0);
	"></div>

[Редактировать] [5]

Ajax-загрузка содержимого элемента

И вот, наконец, мы подошли к самому главному, для чего, собственно, библиотека и создавалась. Здесь также всего лишь две директивы, которые можно установить в приемник: «rxajax» и «rxload».

В [rxajax] следует указать путь к скрипту, возвращающему новое содержимое, т.е. шаблон запроса. Данный запрос отправляется методом GET, а для передачи данных используется JSONP-формат. В тексте запроса можно использовать те же подстановочные шаблоны, что и в директиве «rxview».

Директива «rxload» может содержать js-код, который будет выполнен после загрузки в элемент нового содержимого. Стоит заметить, что данная директива может использоваться не только при ajax-загрузке контента, но также и для любых других элементов. Это может быть очень полезно, например, при использовании jQuery-плагинов, поскольку большинство из них «подключают себя к элементам» только при первоначальной загрузки страницы.

Пример использования «rxajax» приведен ниже:

	<select rxname="var06">
		<option disabled="">Выбери браузер...</option>
		<option selected="" value="1">Mozilla Firefox</option>
		<option value="2">Google Chrome</option>
		<option value="3">Internet Explorer</option>
		<option value="4">Opera ReMix</option>
	</select>
	<div class="bold" rxdata="var06"
		rxajax="http://x-rainy.org/getajax.php?select=browser&value={{value}}"
		rxview="Загрузка...">
	</div>

[Редактировать] [6]

Еще несколько примеров использования

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

	Двустороннее связывание данных:<br />
	<input rxname="var51a" rxdata="var51b" /><br />
	<input rxname="var51b" rxdata="var51a" /><br />

[Редактировать] [7]

Порой флажок «Выделить все» бывает просто необходим. Теперь это проще простого:

	<input type="checkbox" rxname="var44"> Выделить все<br />
	<input type="checkbox" rxdata="var44"> Флажочек №1<br />
	<input type="checkbox" rxdata="var44"> Флажочек №2<br />
	<input type="checkbox" rxdata="var44"> Флажочек №3<br />

[Редактировать] [8]

И еще один довольно интересный пример: при изменении цены или количества автоматически пересчитывается сумма, а при изменении суммы автоматически устанавливается цена:

	<div><label>Кол-во: </label><input type="number" rxname="m_count" value="0" /></div>
	<div><label>Цена: </label><input type="number" rxname="m_price" value="0"
		rxdata="m_count m_summa"
		rxcode="
		if(rxname.toLowerCase() !== 'm_summa')return undefined;
		return ((values['m_count']||0) != 0)
			? (values['m_summa']||0) / values['m_count'] : 0;
		"/>
	</div>
	<div><label>Сумма: </label><input type="number" rxname="m_summa" value="0"
		rxdata="m_count m_price"
		rxcode="return (values['m_count']||0) * (values['m_price']||0);"/>
	</div>

[Редактировать] [9]

Вместо заключения

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

Ссылки на внешние ресурсы

x-rainy.org [10] — официальный сайт библиотеки

Автор: kroshanin

Источник [11]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/javascript/111089

Ссылки в тексте:

[1] rainy.js: http://x-rainy.org/rainy.js

[2] [Редактировать]: http://jsbin.com/zonate/edit?html,output

[3] [Редактировать]: http://jsbin.com/limire/edit?html,output

[4] [Редактировать]: http://jsbin.com/remate/edit?html,output

[5] [Редактировать]: http://jsbin.com/gimaba/edit?html,output

[6] [Редактировать]: http://jsbin.com/lelefoc/edit?html,output

[7] [Редактировать]: http://jsbin.com/canize/edit?html,output

[8] [Редактировать]: http://jsbin.com/cayudi/edit?html,output

[9] [Редактировать]: http://jsbin.com/yoloqu/edit?html,output

[10] x-rainy.org: http://x-rainy.org

[11] Источник: https://habrahabr.ru/post/276445/