Руководство по написанию JS скриптов для front-end разработчиков под Drupal 7

в 15:55, , рубрики: drupal, javascript, метки: ,

Существуют разные способы создания верстки под Drupal. Кто-то верстает уже затемленные страницы, кто-то пытается обойтись стандартными темами, но как правило, сначала верстальщик верстает страницы по дизайну, и на выходе получается набор html файлов — слайсов. Затем девелоперы интегрируют эти файлы по кусочкам при теминге.
Причем в процессе интеграции находятся ошибки, какие-то модификации, поэтому верстка и скрипты к ней относящиеся, должны быть доступными для правок и тестирования.
Именно о последнем способе в основном будет идти речь, я опишу типичные ошибки и бест-практики по их решению при написании JS-скриптов для D7. Думаю это будет интересно как верстальщикам под Drupal, так и разработчикам модулей. В случае верстальщиков основным принципом, которым нужно руководствоваться будет факт, что ваш скрипт будет работать в окружении Drupal, и это накладывает ряд ограничений, в идеале скрипт должен подключаться к Drupal и работать без каких-либо дополнительных модификаций, при этом работать на слайсах вне Drupal.

Имя переменной jQuery

В Drupal 7, в отличие от 6 версии нельзя просто так взять, и обратиться к методам jQuery через $, потому что эта переменная просто не объявлена.
Часто приходится сталкиваться с ситуацией, когда верстальщик написал скрипты, которые на верстке работают, либо вы просто взяли готовый скрипт, а при интеграции начинаются проблемы. Скорее всего проблема в использовании переменной $. Пугаться не надо — ходить и везде заменять $ на jQuery не нужно.
Допустим есть скрипт:

$(function () {
  $('div.menu-expanded').hide();
  $(....);
});

Чтобы он заработал при подключении в Drupal его нужно переоформить так:

(function ($) {
  $(function () {
    $('div.menu-expanded').hide();
    $(...);
  });
}) (jQuery);

Таким образом, просто обернув весь код без изменений, с помощью стандартного механизма выделения scope, объект jQuery стал доступен по имени $.
Лучше сразу объяснить вашему верстальщику такой прием, чтобы в будущем избежать проблем и не лазить по его скриптам с исправлениями.

Drupal.behaviors

В Drupal есть специальный способ обработки события document ready, Drupal.behaviors, который предоставляет ряд преимуществ. В D6 он уже был, просто немного поменялся способ написания.
Например, есть такой скрипт:

$(function (){
  $('a.tooltip').someTooltipPlugin();
});

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

(function ($) {
  Drupal.behaviors.yourName = {
    attach : function(context, settings) {
      // Your code here.
    }
  };
})(jQuery);

Таким способом мы обозначили свой бихевиор — просто добавили новое свойство в объект Drupal.behaviors. Его преимущество в том, что помимо вызова при загрузке всей страницы, Drupal вызовет все бихевиоры например, при получении ответа ajax запроса (в случае если это стандартный запрос, например обновление формы или вью).
Все бихевиоры можно вызвать вручную так:

Drupal.attachBehaviors(document, {});

Drupal пройдет по всем свойствам Drupal.behaviors и вызовет метод attach.
Обратите внимание на первый аргумент (про второй аргумент я решил не рассказывать так как он в моем понимании чисто front-end разработчикам не будет полезен). Сюда нужно передавать содержимое (DOM-элемент или селектор), к которому нужно применить(«приаттачить») бихевиор. Это значение будет доступно в качестве аргумента context в каждом бихевиоре. Нужно это для того, что бы ограничить область действия кода внутри бихевиора.
Если взять код из предыдущего примера, то обработчик будет добавляться ко всем ссылкам на странице при каждом вызове бихевиора. Но если мы перепишем код так:

$('a...', context).someTooltipPlugin();

То сначала при загрузке страницы будут обработаны все ссылки с классом на всей странице, потому что первый вызов бихевиоров происходит с объектом document в качестве контекста.
Затем при каждом вызове бихевиоров, но только внутри вновь полученного контента.
Например, если у нас есть список с ajax пагинацией, то при переходе на вторую страницу нас интересует для повторной обработки только содержимое второй страницы, а не всего документа.
Если вы пишите свой модуль вы просто обязаны ваш функционал завернуть в бихевиор, что бы дать возможность сторонним разработчикам «приаттачить» вашу логику-поведение к их контенту. Причем в любой момент, а не только при загрузке страницы.
Нюанс для верстальщиков. В случае, когда сначала готовится верстка, а потом ее планируют прикручивать к Drupal — код нужно оформлять сразу с использованием бихевиоров.
Естественно на верстке бихевиоры работать без родных скриптов Drupal не будут, будут сыпаться ошибки об обращении к несуществующим переменным и т.д.
Чтобы этого избежать, я предлагаю подключать скрипт drupal.js, который лежит в ядре Drupal на верстке.
Таким образом мы сможем писать основные скрипты в пригодном для Drupal виде, и при применении верстки просто копировать их.
Все что нужно знать верстальщику — вместо обычного document ready писать бихевиоры. И внутри всегда использовать context при построении селекторов.

jQuery.once

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

$('a.tooltip:not(.processed)', context).addClass('processed').someTooltipPlugin();

Такая конструкция внутри бихевиора гарантирует, что при повторном выполнении кода, обработчик добавился единожды.
Такой же механизм заложен в плагин jQuery.once, который включен в ядро D7 и по умолчанию доступен на любой странице.
С использованием плагина конструкцию выше можно заменить так:

$(context).once('myIdentifier', function () {
  // Your code here.
});

Где myIdentifier любое уникальное значение, которое будет использовано в качестве того самого processed класса.
Можно заменить более короткой конструкцией:

$(context).once(function () {
  // Your code here.
});

В данном случае плагин сам сформирует уникальный класс.

Base url

Довольно распространенная ошибка — забывают учесть base url. Не раз встречал ее в проверенных контриб модулях.
Распознать ее легко — слеш в начале пути:
var url = '/ajax/my-module/some-action';
Обычно разрабатывают на одном окружении, и просто хардкодят слеш, а когда бейс урл меняется — часть скриптов перестает работать… Чаще всего ошибка встречается при формировании пути для ajax запросов и при задании пути для атрибутов тегов a и img.
Правильным будет использовать специальную переменную, которая объявляется ядром:

var url = Drupal.settings.basePath + 'ajax/my-module/some-action';

Пути ajax запросов

Небольшая рекомендация (у нас в компании это просто стандарт) — при формировании пути ajax запросов, начинать их со слова ajax — это позволит при необходимости легко отделить все ajax запросы от обычных. Например исключить их разом из кеша, искать в логах и т.д. Затем имя модуля через дефис — это сразу скажет где искать обработчик этого запроса. Дефисы нужны для читаемости урла, даже если никто его не увидит, лучше придерживаться одного формата, ведь вы не станете строить алиасы нод с подчеркиванием?
Пример «правильного» пути по данной логике будет — ajax/my-module/some-action.

Вывод строк

Есть ряд функций, которые следует использовать при выводе текста на JS.
Я просто перечислю основные, без углубления — все они являются аналогами PHP функций ядра:

// Вывод переведенной строки.
Drupal.t('text');
// Вывод "безопасного" значения строки.
Drupal.checkPlain(name);
// Склонение окончаний фразы, в зависимости от множественного/единственного числа переменной.
Drupal.formatPlural(count, singular, plural, args, options);

Объявлены они все в drupal.js, соответственно работать без него не будут.

Автор: IRuslan

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js