Делая полный Polyfill For The HTML5 Подробности Элемента

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

Этот элемент является решением для общего компонента пользовательского интерфейса: сборно-разборные блок.Почти каждый рамки, в том числе загрузку и JQuery UI, имеет свой собственный плагин для аналогичного решения, но ни один не соответствует спецификации 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 состояние.В идеале, это должно быть событие ванили ДОМ .

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

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

Проверьте живая demo __35 | 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> элемент может принять <summary> как элемент детского .

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

Давайте добавим поддержку мыши.Кликните на <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 58 ~ и | являются частный 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 __33 | 18 14 9 )

Производители браузеров пытались исправить такую ​​ недействительным markup перемещая <summary> на должность первого ребенка визуально, даже если элемент не находится в таком положении в потоке DOM.Я был смущен таким поведением, поэтому я попросили W3C для clarification __52 | 15 .W3C подтвердил, что <summary> должно быть первым ребенком <details>.Если вы посмотрите разметку на скриншоте выше, на Ню разметки 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__8 __34 | | 19__8 , который, к сожалению, имеет слабая support __40 | | 20__8 в настоящее время.Тем не менее, мы можем установить <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__55 поддержки для 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.