Подумал я. И тут понеслось.
Началось все с задачи: необходимо сверстать обособленную страничку с функцией печати. Сама страничка была сверстана достаточно быстро, но с кнопкой инициации печати я застрял. Причиной этого ступора как раз и было отделение логики от представления. Иначе говоря мне почему-то сильно не хотелось делать так:
<a class="btn-print" href="javascript:window.print()">Печать</a>
А хотелось сделать это красиво, в отдельном файле js. «А вдруг будет несколько кнопок на странице», — подумал я.
Любимый jQuery для такой маленькой и легкой странички подключать тоже не хотелось. И я взялся за «велосипед».
Вариант первый. В лоб
Первым делом был набросан вот такой код:
window.onload = function() {
var elements = document.querySelectorAll('.btn-print');
for (var i = 0, len = elements.length; i < len; i++) {
elements.item(i).onclick = function() {
window.print();
return false;
}
}
}
Работает он просто: при загрузке страницы, находятся все элементы на странице с классом btn-print
(поддержка IE7 мне была ненужна, поэтому решено было использовать querySelectorAll
). Далее проходим по всем этим элементам и на событие onclick
вешаем функцию, где и производится вызов диалога печати. Для того чтобы не было прыжка страницы (т.е. чтобы ссылка не работала как якорь) функция возвращает false
(для IE как потом выяснил не работает).
«Но вдруг, кто-то еще привяжет к этим элементам событие, тот кто будет работать со страницей уже после меня», — подумал я. И эта мысль привела меня ко второму варианту:
Вариант второй. Думаем о будущем
Хотелось улучшить привязку событий, так, чтобы они не перебивали ранее привязанные события. Для этих целей была написана функция:
function bind(element, type, func) {
var events = element[type];
element[type] = function(e) {
func.call(element, e);
if (events && events.call) {
events.call(element, e);
}
}
}
Функция получает на вход три параметра: DOM элемент, тип события ('onclick'
, 'onload'
и т.д.) и функцию, которая добавляется к элементу — которая и будет производить обработку события.
Но к этому элементу уже может быть привязано событие, поэтому функция соответствующего типа копируется у элемента и кладется в переменную events
. Далее значение этого свойства у элемента заменяется на новую функцию. Внутри нее выполняется вызов func
(которая пришла в bind
третим параметром) посредством call
— в нее передается контекст (чтобы внутри этой функции к элементу можно было обращаться через this
) и объект события (в функции он доступен первым параметром), а следом вызов того что предварительно сохранили в events
(перед вызовом проверяется есть ли там что-то и является ли это что-то функцией).
Это дало возможность добавлять события так:
bind(document, 'onclick', function(e) {console.log(1, this, e)});
bind(document, 'onclick', function(e) {console.log(2, this, e)});
И при клике на документ, в консоли появлялось:
В итоге:
bind(window, 'onload', function() {
var btn = document.querySelectorAll('.btn-print'),
i, len, onclick, item;
for (i = 0, len = btn.length; i < len; i++) {
item = btn.item(i);
bind(item, 'onclick', function() {
window.print();
return false;
});
}
});
Тут все то же самое, только уже с использованием этого «лучшего» способа привязки событий.
Оглядел эти 25 строк. «Никто же не поймет (дело в том, что многие верстальщики в компании где я работаю, ограничиваются сухой версткой, JavaScript им не интересен и даже этот код будет для них целым ребусом), надо что-то привычнее», — подумал я
Вариант третий. Велосипед
Что может быть привычнее чем jQuery в эпоху вопросов:
— Это можно сделать на JavaScript?
— Нет
— А на jQuery?
И вот что из этого вышло:
// использование
_(window).load(function() { //ждем загрузки
_('.btn-print').click(function() { //находим элементы, привязываем обработчик события
window.print(); //наконец-то
});
});
(function(self) { // функция в которой создается "чудо-библиотека"
var _ = function(el) { //конструктор
if (this instanceof _) { // если это объект "класса" _
if (typeof el == 'string') {
//если это строка, то находим элементы и кладем в свойство el
this.el = document.querySelectorAll(el);
} else {
//если это не строка, то предполагаем что это DOM-объект и кладем в el
this.el = el;
}
return this; //возвращаем this - чтобы можно было использовать привычные цепочки вызовов
} else { //если это не объект "класса" _
return new _(el); //создаем и возвращаем объект
}
}
// т.е. в любом случае создается объект
_.each = function(el, func) { // перебор элементов, вызов для каждого func
var i, len;
if (el.length) { //если есть length - перебираем
for (i = 0, len = el.length; i < len; i++) {
func.call(el[i]);
}
} else { // если нет, то применяем как есть
func.call(el);
}
}
_.prototype.each = function(func) { // вызов func для каждого элемента из el
_.each(this.el, func);
return this; // для цепочек
}
_.prototype.bind = function(type, func) { //привязка событий
type = 'on' + type;
this.each(function() { //используем новые методы
var old = this[type];
this[type] = function(e) {
func.call(this, e);
if (old && old.call) {
old.call(this, e);
}
};
});
return this; // возвращается this для цепочек
}
_.each(['click', 'load'], function() { //создание методов click, load (можно продолжать список)
var type = this;
_.prototype[type] = function(func) {
this.bind(type, func);
}
});
self._ = _; //выбрасываем в глобальную область видимости
})(window); //передача window, так что в контексте функции self будет ссылкой на window
Можно посмотреть тут
Итог
Подумал еще немного, оглядел 70 строк кода, здравый смысл победил, я все удалил и использовал javascript:window.print()
, но это был безусловно полезный для меня опыт.
Не могу отнести себя к профессиональным разработчикам, скорее интересующийся вопросом. Основное же мое занятие — верстка. Возможно кому-то пост этот будет интересен, направлен он прежде всего на новичков, которые только начинают знакомится с JavaScript.
Хочу обратить внимание на то, что проверялось только в Mozilla Firefox 25.0, но в IE8-10 тоже работает, для остальных к сожалению проверить в данный момент не могу. Проверил, ошибок не выявил.
Автор: signumum