Чтобы сделать полную Polyfill For The HTML5 Подробности Элемента

HTML5 введены несколько новых тегов, одним из которых является <details>.

Этот элемент является решением для общего компонента пользовательского интерфейса: сборно-разборные блок.Почти каждый рамки, в том числе загрузки и JQuery UI, имеет свой собственный плагин для аналогичного решения, но никто не соответствовать спецификации __64 HTML5 | вероятно, потому что большинство из них были задолго до <details> получил указано и, следовательно, представляют собой различные подходы.Стандартный элемент позволяет каждому использовать ту же разметку для конкретного типа контента.Вот почему создание стабильной polyfill делает sense 1 .

Disclaimer: Это довольно техническая статья, и, хотя я пытался свести к минимуму фрагменты кода, статья содержит еще совсем немногие из них.Так что, будьте готовы

Существующие решения Incomplete

Я not 2 Первый person 3 чтобы попытаться реализовать такую ​​polyfill.К сожалению, все другие решения демонстрируют той или иной проблемы:

  1. Нет поддержки для будущего contentПоддержка для будущего содержания является чрезвычайно ценным для приложений более одной страницы.Без него, вам придется вызвать функцию инициализации каждый раз, когда вы добавляете контент на странице.В принципе, разработчик хочет иметь возможность отказаться от <details> в DOM и сделать с ней, и не нужно возиться с JavaScript, чтобы получить это происходит .
  2. Не верно polyfill для open attributeПо specification 4 <details> поставляется с open атрибут, который используется для переключения видимости содержимого <details> .
  3. toggle событие missingЭто событие уведомление о том, details элемент изменил свое open состояние.В идеале, она должна быть событием ванили DOM .

В этом статья we’ будем использовать лучше dom 5 сделать вещи проще.Основной причиной этого является живая extensions 6 особенность, которая решает проблему вызова функции инициализации для динамического контента.(Для получения дополнительной информации, прочитайте мою подробную статью о живой extensions 7 ). Кроме того, лучше дом наряды живые расширения с набор инструментов, которые делают (пока) не существует в ванили DOM но пригодится при реализации polyfill как этот .

1-details-element-in-Safari-8-opt 8
details элемент в Safari 8 ( Просмотр большой version 18 14 9 )

Проверьте живая demo 10 .

Let’ с более внимательно взглянуть на все препятствия, которые мы должны преодолеть, чтобы сделать <details> доступны в браузерах, которые don’ т поддержать его .

Будущее Содержимое Support

Для начала, мы должны объявить живой расширение для "details" селектора.Что делать, если браузер уже поддерживает элемент изначально?Тогда мы должны будем добавить функцию обнаружения.Это легко с необязательный второй аргумент condition, который предотвращает логику от выполнения, если его значение равно false:

// Invoke extension only if there is no native support
var open = DOM.create("details").get("open");

DOM.extend("details", typeof open !== "boolean", {
  constructor: function() {
    console.log("initialize <details>…");
  }
});

Как вы можете видеть, мы пытаемся обнаружить встроенную поддержку путем проверки open собственности, которая, очевидно, существует только в браузерах, которые распознают <details> .

Что отличает DOM.extend 11 кроме простого вызова как document.querySelectorAll, что constructor функция выполняется для будущего содержания, тоже.И, да, он работает с любой библиотекой для манипулирования DOM:

// You can use better-dom…
DOM.find("body").append(
  "<details><summary>TITLE</summary><p>TEXT</p></details>");
// => logs "initialize <details>…"

// or any other DOM library, like jQuery…
$("body").append(
  "<details><summary>TITLE</summary><p>TEXT</p></details>");
// => logs "initialize <details>…"

// or even vanilla DOM.
document.body.insertAdjacentElement("beforeend",
  "<details><summary>TITLE</summary><p>TEXT</p></details>");
// => logs "initialize <details>…"

В следующих разделах мы заменим console.log колл с реальной реализации .

Реализация <summary> Behavior

<details> элемент может длится около 47 | как элемент детского .

Первый сводный дочерний элемент деталей, если он присутствует, представляет собой обзор деталями.Если нет резюме дочерний элемент отсутствует, то агент пользователя должен обеспечивать свою собственную легенду (например, “Подробности”) .

Давайте добавим поддержку мыши.Нажмите на <summary> элемента должен переключать open атрибут родителя <details> элемента.Это, как он выглядит с помощью более-DOM:

DOM.extend("details", typeof open !== "boolean", {
  constructor: function() {
    this
      .children("summary:first-child")
      .forEach(this.doInitSummary);
  },
  doInitSummary: function(summary) {
    summary.on("click", this.doToggleOpen);
  },
  doToggleOpen: function() {
    // We’ll cover the open property value later.
    this.set("open", !this.get("open"));
  }
});

children Метод возвращает массив JavaScript элементов (не массив объектов, как и в ванили DOM).Поэтому, если не <summary> найдено, то doInitSummary функция не выполняется.Кроме того, doInitSummary и doToggleOpen являются Частный functions 12 , они всегда вызываются для текущего элемента.Таким образом, мы можем перейти this.doInitSummary к Array#forEach без дополнительных крышек, и все будет выполнить правильно есть .

Имея поддержка клавиатуры в дополнение к поддержке мыши тоже хорошо.Но сначала, давайте сделаем <summary> в фокусируемых элемент.Типовое решение состоит в установке tabindex приписать 0:

doInitSummary: function(summary) {
  // Makes summary focusable
  summary.set("tabindex", 0);
  …
}

Теперь пользователь нажимает клавишу пробела или клавишу “Enter” должна переключать состояние <details>.В лучшем-дом, там нет прямого доступа к объекту события.Вместо этого, мы должны объявить, какие свойства, чтобы захватить с помощью дополнительного аргумента массива:

doInitSummary: function(summary) {
  …
  summary.on("keydown", ["which"], this.onKeyDown);
}

Обратите внимание, что мы можем использовать уже существующий doToggleOpen функции;для keydown случае, это просто делает дополнительную проверку на первого аргумента.Для обработчика событий, нажмите кнопку, его значение всегда равно undefined, и результат будет такой:

doInitSummary: function(summary) {
  summary
    .set("tabindex", 0)
    .on("click", this.doToggleOpen)
    .on("keydown", ["which"], this.doToggleOpen);
},
doToggleOpen: function(key) {
  if (!key || key === 13 || key === 32) {
    this.set("open", !this.get("open"));
    // Cancel form submission on the ENTER key.
    return false;
  }
}

Теперь у нас есть щелчков клавиатуры и доступной <details> элемент .

<summary> Элемент Край Cases

<summary> элемент представляет несколько крайние случаи, что мы должны принять во внимание:

1. При <summary> Это ребенок, но не первый Child

2-summary-element-is-not-the-first-child-opt 13
Что браузеру Chrome выдает, когда summary элемент не первый ребенок.( Просмотр большой version 18 14 9 )

Производители браузеров пытались исправить такую ​​ недействительным markup перемещая <summary> на должность первого ребенка визуально, даже если элемент не находится в таком положении в потоке DOM.Я был смущен таким поведением, поэтому я попросили в W3C clarification 15 .W3C подтвердил, что <summary> должно быть первым ребенком <details>.Если вы посмотрите разметку на скриншоте выше, на Nu разметки Checker 16 , она не будет выполнена со следующим сообщением об ошибке:

Ошибка: резюме Элемент не допускается в качестве ребенка деталей элементов в этом контексте.[...] Условий, в которых могут быть использованы Краткое элемент: КакПервый ребенок из деталей элемента .

Мой подход состоит в перемещении <summary> элемент в позиции первого ребенка.Другими словами, polyfill фиксирует недопустимое разметку для вас:

doInitSummary: function(summary) {
  // Make sure that summary is the first child
  if (this.child(0) !== summary) {
    this.prepend(summary);
  }
  …
}

2. При <summary> элемент не Present

3-summary-element-does-not-exist-opt 17
Что выходы браузера Chrome, когда summary элемент отсутствует ( Просмотр большой version 18 14 9 )

Как вы можете видеть на скриншоте выше, производители браузеров вставить “Детали”, как легенда в <summary> в этом случае.Разметки остается нетронутым.К сожалению, мы не можем достичь такой же без доступа к тень DOM 19 , которые, к сожалению слабый support 20 в настоящее время.Тем не менее, мы можем установить <summary> вручную в соответствии со стандартами:

constructor: function() {
  …
  var summaries = this.children("summary");
  // If no child summary element is present, then the
  // user agent should provide its own legend (e.g. "Details").
  this.doInitSummary(
    summaries[0] || DOM.create("summary>`Details`"));
}

Поддержка open Property

Если вы попытаетесь код ниже в браузерах, которые поддерживают <details> изначально и в других, что нет, вы получите разные результаты:

details.open = true;
// <details> changes state in Chrome and Safari
details.open = false;
// <details> state changes back in Chrome and Safari

В Chrome и Safari, изменяя при этом значение open вызывает добавление или удаление атрибута.Другие браузеры не реагировать на это, потому что они не поддерживают open недвижимость на <details> элемент .

Недвижимость отличаются от обычных значений.Они имеют пару получения и установки функций, которые вызываются каждый раз, когда вы читаете или присвоить новое значение в поле.И JavaScript была в API для объявления свойств, начиная с версии 1.5 .

Хорошей новостью является то, что один старый браузер, мы собираемся использовать с нашим polyfill, Internet Explorer (IE) 8, имеет partial поддержка для Object.defineProperty функции.Ограничение состоит в том функция работает только на элементах DOM.Но это именно то, что нам нужно, верно

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

Object.defineProperty(element, "foo", {
  …
  set: function(value) {
    // The line below triggers infinite recursion in IE 8.
    this.setAttribute("foo", value);
  }
});

Таким образом, вы не можете изменить свойство без изменения атрибута есть.Это ограничение не позволяет разработчикам использовать каталог Object.defineProperty В течение довольно длительногоВремя .

Хорошей новостью является то, что я нашел решение .

Fix Для бесконечной рекурсии в IE 8

Прежде чем описывать решение, я хотел бы дать некоторые фон на одной особенностью HTML и CSS парсера браузеров.В случае, если вы не знали, эти анализаторы случай-insensitive.Например, правила ниже будет производить тот же результат (т.е. база красный текст на странице):

body { color: red; }
/* The rule below will produce the same result. */
BODY { color: red; }

То же самое касается атрибутов:

el.setAttribute("foo", "1");
el.setAttribute("FOO", "2");
el.getAttribute("foo"); // => "2"
el.getAttribute("FOO"); // => "2"

Кроме того, вы не можете иметь в верхний регистр и нижний регистр атрибутов с тем же именем.Но вы можете иметь как на объект JavaScript, потому что JavaScript регистр sensitive:

var obj = {foo: "1", FOO: "2"};
obj.foo; // => "1"
obj.FOO; // => "2"

Некоторое время назад, я обнаружил, что IE 8 поддерживает устаревший устаревшие аргумент lFlags 21 методов атрибутов, который позволяет изменять атрибуты в регистрозависимой образом:

Comments are closed.