Стилизация элементов форм и попытка обойти подводные камни

от автора

Приветствую читатели, стилизованные элементы формы на сайтах сейчас прочно вошли в реалии современного дизайна и кто бы что не говорил, во многих случаях смотрятся они действительно приятно.
Многие из вас уже обзавелись плагинчиками типа chosen и прочим добром, некоторые из них весьма качественно написаны. В этой статье я не буду рассказывать про очередной плагин, а лишь хочу обратить ваше внимание на один из способов расширения возможностей плагина, возможно кому-то он поможет.
Я являюсь сторонником того, чтобы подключая плагины для стилизации форм, мы должны продолжать работать с элементами форм в нативном стиле и никак не зависеть от плагина стилизации. Я практически ни у одного (можно сказать ни у одного) плагина не видел корректную обработку изменения поведения элементов при изменении атрибутов DOM элементов, а именно — обработку изменения атрибута disabled (min, max, maxlength), всегда надо было учитывать апи плагины, верстку элемента и тд, а это неудобно если вы в будущем захотите поменять плагин или верстку. Как вариант — подумать заранее и для всего сделать обертки и работать с ними как с внутренним апи. Но есть и альтернативный путь.

Изучив проблему с атрибутами невольно приходит на ум — «а не начать ли нам отлавливать изменения атрибутов элемента?» Обратив внимание на MutationObserver и проконсультировавшись с caniuse, понимаю, что пора: crome, firefox давно поддерживают, новый IE11 тоже, android потихоньку переползает, а ios полностью поддерживает уже. В итоге получился следующий код (jsFiddle)

var MO = (window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver); var observer = new MO(function(){ console.log("disabled changed") }); observer.observe(document.querySelector("id"), { attributes: true, attributeFilter: ["disabled"] }); 

Уже хорошо осталось только побороть IE, а тут все плохо… Первая идя — ну есть же DOMAttrModified, а также для совсем старых propertychange.
Конечно есть, даже работают, но не в нашем случае: события на задизейбленном элементе не кидаются. Отсюда получаем интересную картину: при добавлении атрибута disabled события молчат как партизаны, при удалении спокойно прокидываются. Я бы назвал это багой нежели стандартным поведением, так как ивент ивенту рознь и обрубать все на корню плохо, но что поделать. Однако это не мешает нам написать мини workaround для данной проблемы (jsFiddle)

var AttributeObserve = function(element, callback, attribute){     var MO = (window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver);     if (MO) {         var observer = new MO(callback);         observer.observe(element, { attributes: true, attributeFilter: [attribute] });     } else {         if (!AttributeObserve.__timer) {             AttributeObserve.__observed = []             AttributeObserve.__timer = setInterval(function(){                 for (var i = 0; i < AttributeObserve.__observed.length; i++) {                     var o = AttributeObserve.__observed[i];                     if (o.element.hasAttribute(o.attribute) !== o.flag) {                         callback()                     }                     o.flag = o.element.hasAttribute(o.attribute)                 }             }, 500)             AttributeObserve.__observed.push({                 element: element,                 attribute: attribute,                 flag: element.hasAttribute(attribute)             })         }      } } 

Предвидя возмущения некоторых лиц по поводу setInterval поясню: нет, это не хак, setInterval — вполне себе рабочая конструкция языка, которая при разумном подходе не привносит хаоса и тормозов, если посмотреть на реальную ситуацию то разумным интервалом был бы 500-1000мс, а если допускать что на странице обычно 10-20 контролов, а даже если и 100 проверить их атрибут — не есть проблема.
Также данный способ применим для случаев если вам надо отреагировать на изменение maxlength, min, max и прочих атрибутов которые могу влиять на поведение кастомных контролов. Те же min, max атрибуты для range input, если вы работаете с нативным range input и делаете fallback для браузеров без поддержки, будут хорошо смотреться при подобном подходе к наблюдению за атрибутами и это может решить многие проблемы — вы работаете с input в нативном виде, а плагин подхватывает изменения. Вот этот вариант я бы и хотел сейчас рассмотреть ибо тут у нас выпадает важная проблема — мы работаем не с disabled элементами, а значит может проапгрейдить наше решение (jsFiddle)

var AttributeObserver = function(element, callback, attribute){     var MO = (window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver);     if (MO) {         var observer = new MO(callback);         observer.observe(element, { attributes: true, attributeFilter: [attribute] });     } else if (element.addEventListener) {         element.addEventListener('DOMAttrModified', function(e){             if (e.attrName == attribute) {                 callback()             }         }, false);     } else if ("onpropertychange" in document) {         element.attachEvent ('onpropertychange', function(e){             if (e.attrName == attribute) {                 callback()             }         });      } } 

P.S. Хочется добавить, что код не претендует на звание готового плагина, а скорее просто демонстрирует один из возможных подходов.

ссылка на оригинал статьи http://habrahabr.ru/post/218529/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *