Создание улучшенной JavaScript библиотеки для DOM.

В настоящее время для работы с объектной моделью документа (DOM) де факто используется библиотека jQuery.

Эта библиотека может быть использована совместно с популярными клиентскими MV* фреймворками (например, Backbone). Кроме того, существует множество специализированных плагинов и достаточно больших сообществ. По мере стремительного роста интереса разработчиков к развитию JavaScript, многие стали задумываться над тем, как в действительности работает родной API (программный интерфейс приложения) и о том, когда мы можем его использовать вместо подключения дополнительной библиотеки.

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

Даниэль Бахнер (Daniel Buchner) создал SelectorListener и идею «живых расширений». Я начал задумываться над созданием набора функций, которые позволили бы нам строить ненавязчивые DOM компоненты. Очевидно, что для этого необходимо было использовать качественно лучший подход, чем тот, который мы использовали до сих пор. Цель заключалась в том, чтобы пересмотреть существующие API и решения, а также построить понятную, удобную для тестирования и занимающую минимальный объем памяти библиотеку.

Добавление в библиотеку полезных функций.

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

  • живые расширения;
  • родная анимация;
  • встроенный микрошаблон;
  • поддержка интернационализации (привязка программно-аппаратных средств к условиям и стандартам страны пользователя)

Живые расширения.

jQuery придерживается концепции, которая называется «живые расширения». Благодаря тому, что используется идея делегирования событий, у разработчиков появляется возможность обрабатывать существующие и будущие элементы. Однако в большинстве случаев требуется больший уровень гибкости. Например, делегированные события не отвечают, когда DOM должен быть видоизменен в определенном порядке, чтобы инициализировать виджет. Именно для решения подобных проблем и были созданы живые расширения.

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

Live extensions make is easier to handle any future elements.

Живые расширения позволяют обрабатывать любые будущие элементы без необходимости ссылаться на функцию инициализации. (Источник изображения).

Давайте рассмотрим простой пример. Предположим, что наша задача заключается в реализации полностью настраиваемой всплывающей подсказки. Псевдо-селектор:hover в этом случае нам не поможет, поскольку положение окна с подсказкой будет меняться курсором мышки. Делегирование события также не является хорошим решением; листинг в mouseover и mouseleave для всех элементов в дереве документа является слишком накладным. Единственным подходящим решением является использование живых расширений!

DOM.extend("[title]", {
  constructor: function() {
    var tooltip = DOM.create("span.custom-title");

    // set the title's textContent and hide it initially
    tooltip.set("textContent", this.get("title")).hide();

    this
      // remove legacy title
      .set("title", null)
      // store reference for quicker access
      .data("tooltip", tooltip)
      // register event handlers
      .on("mouseenter", this.onMouseEnter, ["clientX", "clientY"])
      .on("mouseleave", this.onMouseLeave)
      // insert the title element into DOM
      .append(tooltip);
  },
  onMouseEnter: function(x, y) {
    this.data("tooltip").style({left: x, top: y}).show();
  },
  onMouseLeave: function() {
    this.data("tooltip").hide();
  }
});

Мы можем стилизировать элемент .custom-title в CSS:

.custom-title {
  position: fixed; /* required */
  border: 1px solid #faebcc;
  background: #faf8f0;
}

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

Живые расширения являются автономными; именно этим объясняется тот факт, что вам не требуется вызывать функцию инициализации для того, чтобы работать с будущим контентом. Таким образом, они могут быть объединены с любой DOM библиотекой и позволяют упростить логику вашего приложения, благодаря разделению кода пользовательского интерфейса на множество мелких независимых частей.

И последняя, но не менее важная информация касается Веб компонентов. Один из разделов спецификации под названием «Decorators» (декораторы) направлен на решение похожей проблемы. В настоящее время используется реализация на основе разметки со специальным синтаксисом для присоединения слушателей (процесс, обеспечивающий, напр., контроль правильности выражений, вводимых с клавиатуры) событий к дочерним элементам. Но это все еще рано проект:

«Декораторы, в отличие от других частей Веб компонентов, пока еще не имеют спецификации».

Родная анимация.

Благодаря Apple, на сегодняшний день CSS имеет хорошую поддержку анимации. Ранее реализация анимации, как правило, осуществлялась в JavaScript с помощью setInterval и setTimeout. Это была полезная функция — но теперь это больше признак плохой практики. Родная анимация всегда будет более плавной: она, как правило, выполняется быстрее, требует меньших ресурсов и не портит общую картину, если не поддерживается браузером.

Оптимальным вариантом будет отсутствие в DOM animate метода: только show, hide и toggle. Для ввода скрытого состояния элемента в CSS, библиотека использует стандартизированный aria-hidden атрибут.

Чтобы наглядно продемонстрировать, как это все работает, давайте добавим простой эффект анимации к всплывающей подсказке, которую мы реализовали ранее:

.custom-title {
  position: fixed; /* required */
  border: 1px solid #faebcc;
  background: #faf8f0;
  /* animation code */
  opacity: 1;
  -webkit-transition: opacity 0.5s;
  transition: opacity 0.5s;
}

.custom-title[aria-hidden=true] {
  opacity: 0;
}

Внутренние элементы show() и hide() устанавливают для атрибута aria-hidden значение false или true. Это позволяет CSS выполнять обработку анимации и переходов.

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

Встроенный микрошаблон.

Строки HTML разметки зачастую раздражают наличием большого количества слов. Просматривая альтернативные варианты, я остановил свой выбор на Emmet. Сегодня, Emmet – это довольно популярный плагин для текстовых редакторов, который обладает удобным и компактным синтаксисом. Возьмите эту HTML разметку:

body.append("<ul><li class='list-item'></li><li class='list-item'></li><li class='list-item'></li></ul>");

и сравните ее с эквивалентным микрошаблоном:

body.append("ul>li.list-item*3");

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

Поддержка интернационализации.

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

Говоря по простому, переключение языка напоминает изменение «представления» контента. В CSS2 такую модель помогают описать несколько псевдо-селекторов::lang и :before. Давайте рассмотрим представленный ниже код:

[data-i18n="hello"]:before {
  content: "Hello Maksim!";
}

[data-i18n="hello"]:lang(ru):before {
  content: "Привет Максим!";
}

Используемый в этом случае прием достаточно прост: Значение свойства content изменяется в соответствии с действующим языком, который определяется lang атрибутом из html элемента. Используя атрибуты данных, такие как data-i18n, мы можем обслуживать текстовый контент в HTML:

[data-i18n]:before {
  content: attr(data-i18n);
}

[data-i18n="Hello Maksim!"]:lang(ru):before {
  content: "Привет Максим!";
}

Естественно, что такой CSS код будет не совсем привлекательным. Улучшенный dom будет имеет два помощника: i18n и DOM.importStrings. Первый используется для обновления data-i18n атрибута с соответствующим значением, а второй локализует строки для конкретного языка.

label.i18n("Hello Maksim!");
// the label displays "Hello Maksim!"
DOM.importStrings("ru",  "Hello Maksim!", "Привет Максим!");
// now if the page is set to ru language,
// the label will display "Привет Максим!"
label.set("lang", "ru");
// now the label will display "Привет Максим!"
// despite the web page's language

Также могут быть использованы параметризованные строки. Просто добавьте переменную ${param} в ключевую строку:

label.i18n("Hello ${user}!", {user: "Maksim"});
// the label will display "Hello Maksim!"

Как сделать родной API более элегантным.

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

  • геттер и сеттер,
  • обработку событий,
  • поддержку функциональных методов.

Геттер и сеттер.

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

<a href="/chemerisuk/better-dom" id="foo" data-test="test">better-dom</a>

Чтобы объяснить, почему «DOM является настолько неудобным», давайте посмотрим на следующий фрагмент:

var link = document.getElementById("foo");

link.href; // => "https://github.com/chemerisuk/better-dom"
link.getAttribute("href"); // => "/chemerisuk/better-dom"
link["data-test"]; // => undefined
link.getAttribute("data-test"); // => "test"

link.href = "abc";
link.href; // => "https://github.com/abc"
link.getAttribute("href"); // => "abc"

Значение атрибута эквивалентно соответствующей строке в HTML, в то время как свойство элемента с таким же именем может имет …

Если вы хотите прочитать полностью статью, посетите сайт наших спонсоров

Comments are closed.