Изменение атрибута class у HTML-элементов является основой, базой для управления содержимым страницы с помощью DHTML. Любое действие по оформлению проще всего производить, именно изменяя динамически свойство className: от простого добавления правила {cursor:pointer;} при подготовке таблиц к сортировке до создания сложных электронных документов (пример: http://infodisk.info/konstitut.htm).
Работа с className – одна из важнейших и наиболее частых задач DHTML, поэтому функции Javascript, выполняющие эту работу, должны быть написаны очень тщательно.
Какие конкретно действия можно выполнять с атрибутом class у HTML-элемента? Значение этого атрибута может состоять из нескольких слов (разделённых пробелами), каждое из которых является отдельным селектором класса для набора правил CSS: <p class='flole border'> (а в CSS может быть написано .flole {float:left;} .border {border:1px solid gray;}). Значит, можно (1) добавлять к атрибуту class новые слова-селекторы; (2) удалять существующие селекторы; а иногда (например, при чередующейся раскраске строк в таблице) требуется и заменять (3) один селектор на другой.
То есть достаточно традиционный набор действий: add, remove, replace. Чаще всего Javascript-библиотеки обходятся первыми двумя, для которых делают отдельные функции вида:
Ну, а чтобы лишний раз не менять className (не заставлять браузер перерисовывать страницу и тормозить всю работу), в набор добавляют ещё функцию проверки существования:
А потом используют результат этой функции перед добавлением или удалением «слов» из className:
Приведённый набор функций, в принципе, можно считать стандартным – все используют более-менее похожие алгоритмы. Они хороши для «средней» нагрузки, но для пересчёта большой таблицы (или просто длинного списка элементов) скорость работы лучше бы повысить.
Оптимизировать алгоритмы add-remove можно в двух местах. А если возникает ещё и третья задача (replace – заменить один селектор на другой), результаты оптимизации будут, как миним, умножены на два.
Переделанные функции (и вся система управления className) могли бы выглядеть примерно так:
Получилось «правильно», но не очень красиво. Обычно так всегда и бывает – более правильный код более длинный. Да. А если класснэйм надо добавлять раз 20, код станет совсем длинным, и уже нерациональным. Вполне логично делать одни и те же действия по извлечению строки всё-таки внутри часто повторяющейся функции, как-нибудь так:
Правда, в последнем примере (и в предыдущем, если нет элемента с ид "first") вывалится ошибка, даже несмотря на заботливо добавленный "el && " перед получением el.className. А ещё хуже, если функцию hasClass вызвать с двумя «плохими» параметрами (или без параметров) – она вернёт true. Лекарство во всех этих случаях двойное:
1) проверять в функции hasClass наличие обоих параметров, и если хотя бы один не приводится к true (undefined, или просто не содержит букв), возвращать false (что означает: «нет такого селектора в данном классе»);
2) в «рабочих» функциях (меняющих класснейм) проверять наличие свойства className у первого параметра (который должен быть элементом); причём, рекомендуемая специалистами проверка вида ("className" in el) здесь не подходит, так как мы пытаемся обеспечить получение какого попало параметра (например, не объекта); поэтому проверять будем на undefined:
Рабочих функции у нас три, и во всех теперь повторяется одна и та же цепочка предварительных действий. И она ещё удлинится: мы всё-таки хотим обрабатывать с помощью данных функций и объекты (элементы), и строки («я не жадный, я запасливый»). Что-то регулярно повторяющееся и длинное надо выносить в отдельную функцию (зачем ещё нужны функции?).
В нашем случае функции так малы, что проще не выносить из них что-то, а объединить в одну. Так мы и сделали – одну функцию с тремя параметрами: 1) элемент (объект), 2) селектор (строка или массив из двух строк, которые надо менять одна на другую), 3) указатель на выбо действия (число): 1 – добавить селектор, 2 – удалить, 3 – заменить.
Но чего-то в такой конструкции опять нам не хватало – может быть, разные типы второго параметра не нравились, может переключатель действий с помощью switch... И тогда мы придумали сделать переключатель действий не на основе значения отдельного параметра, а на основе наличия параметров – всё, наконец, получилось логично и стройно. Вот оно, это всеобщее уравнение селекторов CSS:
Строк, может, получилось и больше, чем было бы в отдельных функциях, но зато красивее. Мудрый читатель удивится, почему сюда до кучи не добавили функцию проверки класса (hasClass). – Потому что проверять наличие селектора в атрибуте «класс» бывает нужно не только для изменения этого атрибута, но и просто для поиска (отбора) элементов. Эта функция, кстати, позволяет корректно использовать для выборки из HTML-коллекций конструкции for (var i in list), например, так:
Собственно, проверка if (!elCls || !cls) return false в функции hasClass именно для этой цели и была изначально придумана.