Представление живых расширений для улучшенного DOM: что это такое и как они работают.

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

Функции живых расширений.

Обработка событий является одним из ключевых принципов работы с DOM (объектная модель документов, стандарт консорциума WWW, определяющий способы манипулирования объектами и изображениями на одной веб-странице). События являются основным средством получения обратной связи от пользовательского взаимодействия.

Простая привязка события.

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

link.addEventListener("click", function(e) {
  // do something when the link is clicked
}, false);

Первый аргумент указывает на тип события, второй аргумент является слушателем, а третий аргумент определяет фазу события (так называемое «выделение пузырьков» или «сбор данных»). Причина, по которой используется последний аргумент, заключается в том, что большинство DOM событий проходит по DOM дереву из узла документов к целевому узлу (фаза сбора данных), а затем обратно к узлу документов (фаза выделения пузырьков). Этот процесс называется «потоком событий» и сопровождается появлением нескольких полезных свойств.

Живые и делегированные события.

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

list.addEventListener("click", function(e) {
  if (e.target.tagName === "LI") {
    // do something when a child <li> element is clicked
  }
}, false);

Имея все обработчики событий в одном родителе, мы можем обновить innerHTML свойство этого элемента, не теряя способность прослушивать события для новых элементов. В jQuery подобная функция получила название «Живые события», и она быстро стала популярной из-за своей способности фильтровать события селектором CSS. Позже, ее заменили делегированные события благодаря своей гибкости и возможности привязывать слушателя к любому элементу в дереве документов.

Но даже делегирование события не может исключить появление следующих проблем:

  • Когда после добавления нового элемента в дерево документа (который приведен в соответствие с конкретным селектором) требуется DOM мутация (предполагаемое резкое изменение поведения программы ).
  • Когда элемент должен быть инициализирован в избыточном событии, таком как scroll или mousemove.
  • Или в не пузырящемся событии, например, load, error и тому подобное.

Это именно те проблемы, которые призваны решить живые расширения.

Живые расширения используют аргументы.

Взгляните на следующую диаграмму, которая объясняет функции живых расширений:

live-extension-respo

1. DOM мутации для существующих и будущих элементов.

Представьте, что вам необходимо создать многоразовый виджет для работы с датами. В HTML5 для этой цели есть элемент <input type="date">, который соответствует существующему стандарту. Этот элемент может быть использован для создания полифила. Но проблема в том, что этот элемент ведет себя каждый раз по-разному в зависимости от используемого браузера:

dateinputs
Элемент вывода даты в разных браузерах.

Единственный способ заставить этот элемент вести себя последовательно, это установить тип атрибута в значение "text". Это действие позволит отменить выполнение наследования и позволит инициировать выполнение JavaScript кода. Попробуйте определить живое расширение в соответствии с приведенным ниже примером:

DOM.extend("input[type=date]", {
  constructor: function() {
    // cancel browser-specific implementation
    this.set("type", "text");
    // make your own styleable datepicker,
    // attach additional event handlers etc.
  }
});

2.Обратный вызов информационного запроса.

Я настоятельно рекомендую вам прочитать статью Пола Хейса (Paul Hayes) под названием «Использование CSS переходов совместно с информационными запросами и JavaScript».

«Общей проблемой адаптивного дизайна является связь информационных запросов CSS3 и JavaScript. Например, на большом экране вы можете выполнить пересмотр стилизации, но в то же время может быть полезным и использование JavaScript и различного контента, например, изображений более высокого качества».

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

DOM.extend(".rwd-menu", {
  constructor: function() {
    var viewportWidth = DOM.find("html").get("clientWidth");

    if (viewportWidth < 768) {
      // hide <ul> and construct Emmet abbreviation for a
      // <select> element that should be used on small screens
      this.hide().after("select>" +
        this.children("li").reduce(function(memo, item) {
          var text = item.get("textContent"),
            href = item.find("a").get("href");

memo.push("option[data-url=" + href + "]>live-extension-respo














{" + text + "}");
          return memo;
        }, []).join("^"));

      // redirect to stored url when an <option> is selected
      this.next().on("change", this.onMenuChanged.bind(this));
    }
  },
  onMenuChanged: function(select) {
    var selectedIndex = select.get("selectedIndex");
    // redirect to the appropriate data-url attribute value
    location.href = target.child(selectedIndex).data("url");
  }
});

3.Элемент информационных запросов.

В 2011 году Энди Юм (Andy Hume) реализовал скрипт для применения стилей в зависимости от размеров конкретного элемента (не окон просмотра, как для информационных запросов). Позже, этот метод был назван «элемент информационного запроса»:

«Информационные запросы действительно хорошо работают при настройке основных шаблонов сайта, но они менее подходят для изменения стилей на более детальном уровне».

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

DOM.extend(".signup-form", {
  constructor: function() {
    var currentWidth = this.offset().width;
    // add extra class depending on current width
    if (currentWidth < 150) {
      this.addClass("small-signup-form");
    } else if (currentWidth > 300) {
      this.addClass("wide-signup-form");
    }
  }
});

4.Эффективное прикрепление глобального слушателя к часто встречающимся событиям.

DOM.extend(".detectable", {
  constructor: function() {
    // mousemove bubbles but it’s usually a very bad
    // idea to listen to such event on a document level
    // but live extensions help to solve the issue
    this.on("mousemove", this.onMouseMove, ["pageX", "pageY"]);
  },
  onMouseMove: function(x, y) {
    // just output current coordinates into console
    console.log("mouse position: x=" + x + ", y=" + y);
  }
});

5.Листинг не пузырящейся (не восходящей цепочки) событий на уровне документа.

DOM.extend("img.safe-img", {
  constructor: function() {
    // error event doesn’t bubble so it’s not
    // possible to do the same using live events
    this.on("error", this.onError);
  },
  onError: function() {
    // show a predefined png if an image download fails
    this.src = "/img/download-failed.png"
  }
});

Беглый взгляд на историю.

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

HTML компоненты.

Internet Explorer начал поддерживать DHTML (динамический язык описания структуры гипертекста) начиная с версии IE 5.5:

«DHTML – это компоненты, которые инкапсулируют специфическую функциональность или поведение на странице. Когда на странице используется стандартный HTML элемент, то усиливается установленное по умолчанию поведение».

Чтобы установить для будущих элементов требуемое поведение, в Internet Explorer используется файл *.htc со специальным синтаксисом. Далее представлен пример, который наглядно демонстрирует то, как мы организовали работу элемента :hover вместо <a>:

<PUBLIC:COMPONENT URN="urn:msdn-microsoft-com:workshop" >
  <PUBLIC:ATTACH EVENT="onmouseover" ONEVENT="Hilite()" />
  <PUBLIC:ATTACH EVENT="onmouseout"  ONEVENT="Restore()"  />
  <SCRIPT LANGUAGE="JScript">
  var normalColor, normalSpacing;

  function Hilite() {
    normalColor  = currentStyle.color;  
    normalSpacing= currentStyle.letterSpacing;

    runtimeStyle.color  = "red";
    runtimeStyle.letterSpacing = 2;
  }

  function Restore() {
    runtimeStyle.color  = normalColor;
    runtimeStyle.letterSpacing = normalSpacing;
  }
</SCRIPT>
</PUBLIC:COMPONENT>

Если вы решите использовать вышеупомянутый код в hilite.htc файле, то можете получить к нему доступ через CSS свойство behavior:

li {
  behavior: url(hilite.htc);
}

Я был действительно очень удивлен, когда обнаружил, что компоненты HTML поддерживают возможность создания пользовательских тегов (начиная с версии 5.5), сто существуют единичные ограничения домена и множество других вещей, которые вы, вероятно, никогда не использовали раньше. Несмотря на то, что компания Microsoft внесла предложение о добавлении компонентов в W3C (консорциум World-Wide Web), другие производители браузеров решили не поддерживать эту функцию. В результате компоненты HTML были удалены из Internet Explorer 10.

Оформители.

В своей предыдущей статье я упоминал об Оформителях (decorators), которые являются частью веб-компонентов. Вот как можно реализовать индикатор состояния открыто/закрыто <details> element с использованием оформителей:

<decorator id="details-closed">
  <script>
    function clicked(event) {
      event.target.setAttribute('open', 'open');
    }
    [{selector: '#summary', type: 'click', handler: clicked}];
  </script>
  <template>
    <a id="summary">
      &blacktriangleright; <content select="summary"></content>
    </a>
  </template>
</decorator>

<decorator id="details-open">
  <script>
  function clicked(event) {
    event.target.removeAttribute('open');
  }
  [{selector: '#summary', type: 'click', handler: clicked}];
  </script>
  <template>
    <a id="summary">
      &blacktriangledown; <content select="summary"></content>
    </a>
    <content></content>
  </template>
</decorator>

Оформители также можно применять с использованием специального свойства decorator в CSS:

details {
  decorator: url(#details-closed);
}

details[open] {
  decorator: url(#details-open);
}

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

API для живых расширений.

При создании прикладного интерфейса для живых расширений, я решил следовать следующим правилам:

  1. Живые расширения должны быть объявлены в JavaScript.
    Я твердо уверен в том, что все объекты, которые так или иначе влияют на поведение элемента, должны быть представлены в файле JavaScript (Обратите внимание, что улучшенный dom использует новое CSS правило, но оно включает в себя только детали реализации).
  2. Прикладной интерфейс должен быть простыми в использовании.
    Вам стоит отказаться от использования узкоспециализированных форматов файлов или новых HTML элементов: для начала разработки живого расширения необходимо минимально возможное количество знаний, связывающих конструктора и обработчика события (говоря простым языком, вы должны сделать минимально возможный барьер для доступа).

В результате, остается только два метода, которые стоит рассмотреть: DOM.extend и DOM.mock.

DOM.extend

Метод DOM.extend используется для объявления живого расширения. Он использует CSS селектор в качестве первого аргумента, который будет отвечать за используемые элементы. Общая рекомендация: попытайтесь сделать все как можно проще.

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

Comments are closed.