Приветствую читатели, стилизованные элементы формы на сайтах сейчас прочно вошли в реалии современного дизайна и кто бы что не говорил, во многих случаях смотрятся они действительно приятно.
Многие из вас уже обзавелись плагинчиками типа 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 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 (!AttributeObserver.__timer) {
AttributeObserver.__observed = []
AttributeObserver.__timer = setInterval(function(){
for (var i = 0; i < AttributeObserver.__observed.length; i++) {
var o = AttributeObserver.__observed[i];
if (o.element.hasAttribute(o.attribute) !== o.flag) {
callback()
}
o.flag = o.element.hasAttribute(o.attribute)
}
}, 500)
AttributeObserver.__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. Хочется добавить, что код не претендует на звание готового плагина, а скорее просто демонстрирует один из возможных подходов.