Как стать автором
Обновить

widget своими руками

Время на прочтение8 мин
Количество просмотров12K
Привет.

Наверное, многие из вас пользовались стандартными javascript-виджетами, написанными как безвестными разработчиками, так и крепкими профессионалами, так и гуру. Есть великолепные коллекции виджетов, поставляемых в качестве плагинов к известным фреймворкам (вроде jQuery, Prototype JS, YUI), так и целые фреймворки, акцентирующие свое внимание на создании виджетов, вроде Ext JS или qooXdoo.

Но что, если вы не хотите использовать достаточно объемный фреймворк для создания простого диалогового окна, или хотите разобраться в том, как создаются такие виджеты, или даже разрабатываете собственный фреймворк?

Об этом и пойдет речь в сегодняшнем посте — как сделать простейшее диалоговое окно и alert средствами чистого javascript, без применения каких-либо фреймворков.

Disclaimer: должен предупредить, что код, представленный далее — только пример реализации, он предназначен только для того, чтобы показать путь, но это не конечный результат. Конечно, вы можете использовать получившийся виджет в своем проекте, но знайте — можно сделать гораздо лучше, в общем, есть пространство для самовыражения и наворачивания. Кроме того, я старался описывать каждый шаг, что будет полезно новичку, но, без сомнения, будет раздражать профессионала. Впрочем, если ты профессионал, то тебе вряд ли чем-либо поможет данная статья.

Прежде всего, нам понадобится небольшая коллекция методов, облегчающих нашу работу — это, прежде всего, навешивание и удаление обработчиков событий на DOM-элементы, остальные совершенно необязательны, и просто являются хелперами, уменьшающими объем кода и увеличивающими его понятность:

  1. // Для этого проекта мы будем использовать одну переменную с глобальной областью видимости "DEMO".
  2. // Для чего нужно использовать как можно меньше переменных с глобальной областью видимости?
  3. // По двум причинам:
  4. // 1. Вы можете быть уверенным, что весь ваш код надежно упрятан в безопасное место, и другой разработчик, подключив
  5. //   свой модуль (или свою библиотеку), не сломает вашу функцию или объект, и ваш код (и его) будет работать именно так,
  6. //   как вы ожидаете. Программирование - непростая штука, так зачем находить себе проблемы там, где их можно избежать?
  7. // 2. Объекты, являющиеся свойствами глобального объекта (в клиентском javascript'е - объекта window) иначе обрабатываются
  8. //   сборщиком мусора, и живут дольше, а значит, если вы для каждую мелкую функцию или временную переменную будете
  9. //   создавать в глобальной области видимости, использование памяти будет неоптимальным, что само по себе плохо. А если
  10. //   вы плохо распоряжаетесь памятью, то, по мере роста функционала, ваш проект очень скоро начнет тормозить.
  11. // Лично для меня любая из этих причин является достаточно веским аргументом для того,
  12. // чтобы упаковывать весь свой код в одну переменную с глобальной областью видимости.
  13. if (typeof DEMO == "undefined" || !DEMO) {
  14.   var DEMO = {};
  15. }
  16.  
  17. // Lang - это утилита, содержащая удобные методы для работы с языком.
  18. // На самом деле, это просто синтаксический сахар - то есть, можно обойтись и без них,
  19. // но с ними приходится меньше печатать, да и код становится более человекопонятным.
  20. //
  21. // Обратите внимание, что перед тем, как создать объект Lang в объекте DEMO, я проверяю, не создан ли он уже
  22. // в рамках этой демонстрации это не является необходимым, но в случае, если ваш код будет использовать человек,
  23. // не слишком хорошо понимающий, что он делает (скажем, просто подключает ваш виджет к своему блогу),
  24. // он может и два и три раза подключить несколько ваших виджетов, имеющих те же самые объекты как свою часть - а без
  25. // этой проверки объект DEMO.Lang несколько раз перезапишется, причем, это будет совершенно лишняя операция.
  26. DEMO.Lang = typeof DEMO.Lang != 'undefined' && DEMO.Lang ? DEMO.Lang : {
  27.   isUndefined : function (o) {
  28.     return typeof o === 'undefined';
  29.   },
  30.   isString : function (o) {
  31.     return typeof o === 'string';
  32.   }
  33. };
  34.  
  35. // DOM - это ряд функций, облегчающих работу с DOM. В рамках примера мне достаточно только двух - навешивание и удаление
  36. // обработчиков событий, метод get вспомогательный и без него можно обойтись.
  37. DEMO.DOM = typeof DEMO.DOM != 'undefined' && DEMO.DOM ? DEMO.DOM : {
  38.   get : function (el) {
  39.     return (el && el.nodeType) ? el : document.getElementById(el);
  40.   },
  41.  
  42.   addListener : function (el, type, fn) {
  43.     // если el не dom-элемент, а строка, тогда пробуем найти dom-элемент с id, равным этой строке
  44.     if (DEMO.Lang.isString(el)) { el = this.get(el); }
  45.  
  46.     // проверка наличия необходимой функциональности в браузере перед использованием функции называется
  47.     // feature-testing.
  48.     // Подробнее об этом принципе можно прочитать, здесь: www.unix.com.ua/orelly/webprog/jscript/ch20_01.htm
  49.     // а конкретно об этом кусочке кода здесь: fastcoder.org/articles/?aid=17
  50.     if (el.addEventListener) {
  51.       el.addEventListener(type, fn, false);
  52.     } else if (el.attachEvent) {
  53.       el.attachEvent('on' + type, fn);
  54.     } else {
  55.       el['on' + type] = fn;
  56.     }
  57.   },
  58.  
  59.   removeListener : function (el, type, fn) {
  60.     if (DEMO.Lang.isString(el)) { el = this.get(el); }
  61.  
  62.     if (el.removeEventListener){
  63.       el.removeEventListener(type, fn, false);
  64.     } else if (el.detachEvent) {
  65.       el.detachEvent('on' + type, fn);
  66.     } else {
  67.       el['on' + type] = function () { return true; };
  68.     }
  69.   }
  70. };
* This source code was highlighted with Source Code Highlighter.


Чтобы не раздувать листинг, я не включил в него еще две функции — purge и setInnerHTML, по ссылке вы можете найти их описание и принцип действия. Также вы можете найти их в исходниках примера.

Ну и теперь перейдем к тому, ради чего все затевалось — к созданию непосредственно диалогового окна.

  1. if (typeof DEMO == "undefined" || !DEMO) {
  2.   var DEMO = {};
  3. }
  4.  
  5. // Обратите внимание - объект строится по так называемому "модульному" паттерну, предложенному YAHOO!
  6. // подробнее: ajaxian.com/archives/a-javascript-module-pattern
  7. DEMO.Dialog = typeof DEMO.Dialog != 'undefined' && DEMO.Dialog ? DEMO.Dialog : function () {
  8.   // "private"-свойства
  9.  
  10.   // создаем контейнер диалога и запоминаем его.
  11.   var dialog = document.createElement('div');
  12.  
  13.   dialog.className = 'dialog';
  14.   document.body.appendChild(dialog);
  15.  
  16.   // Главная функция. На вход может приходить как строка (в этом случае она становится текстом окна)
  17.   // так и объек со свойствами:
  18.   // body {String} - текст окна
  19.   // buttons {Array} - массив кнопок, при этом каждая кнопка - объект вида:
  20.   //  id : {String} - id кнопки
  21.   //  text : {String} - текст кнопки
  22.   //  callback : {Function | Object} - либо функция (в этом случае она будет повешена на click по кнопке), либо объект вида:
  23.   //   fn : {Function} непосредственно функция
  24.   //   type : {String} тип события, на которое будет навешена функция.
  25.   var render = function (o) {
  26.     var html, i, length = (typeof o.buttons === 'undefined') ? 0 : o.buttons.length,
  27.       button;
  28.  
  29.     // текст диалогового окна
  30.     if (typeof o === 'string') {
  31.       html = '<p>' + o + '</p>';
  32.     } else {
  33.       html = '<p>' + ((o.body) ? o.body : o) + '</p>';
  34.     }
  35.  
  36.     for (i = 0; i < length; i++) {
  37.       button = o.buttons[i];
  38.       html += '<a href="#" id="' + button.id + '">' + button.text + '</a>';
  39.     }
  40.  
  41.     // нам не нужно беспокоиться об утечках памяти, Дуглас Крокфорд побеспокоился за нас
  42.     DEMO.DOM.setInnerHTML(dialog, html);
  43.  
  44.     activateListeners(o.buttons);
  45.   };
  46.  
  47.   // навешиваем обработчики событий на кнопки. Если кнопок нет - ничего не делаем.
  48.   var activateListeners = function (buttons) {
  49.     var i, length, button, isUndefined = DEMO.Lang.isUndefined;
  50.  
  51.     if (DEMO.Lang.isUndefined(buttons)) { return; }
  52.     length = buttons.length;
  53.  
  54.     for (i = 0; i < length; i++) {
  55.       button = buttons[i];
  56.       if (!isUndefined(button.callback.type) && !isUndefined(button.callback.fn)) {
  57.         DEMO.DOM.addListener(button.id, button.callback.type, button.callback.fn);
  58.       } else {
  59.         DEMO.DOM.addListener(button.id, 'click', button.callback);
  60.       }
  61.     }
  62.     cached_buttons = buttons;
  63.   };
  64.  
  65.   return {
  66.     // публичный функции
  67.     // показываем диалогове диалоговое окно
  68.     show : function (o) {
  69.       render(o);
  70.     },
  71.     // прячем диалоговое окно
  72.     hide : function () {
  73.       dialog.style.display = 'none';
  74.     }
  75.   };
  76. }();
* This source code was highlighted with Source Code Highlighter.


Теперь у нас есть объект DEMO.Dialog, у которого есть два public метода — show и hide. При этом объект достаточно гибкий и может отрисовать как диалоговое окно, так и оповещение.

Если мы хотим вызвать показать диалоговое окно, то мы добавим две кнопки:
  1. DEMO.Dialog.show({
  2.   body : 'Хотите кофе?',
  3.   buttons : [
  4.     {
  5.       id : 'cancel',
  6.       text : 'Не',
  7.       callback : function () { DEMO.Dialog.hide(); }
  8.     },
  9.     {
  10.       id : 'accept',
  11.       text : 'Да!',
  12.       callback : function () {
  13.         DEMO.Dialog.hide();
  14.         document.body.appendChild(document.createTextNode('Я тоже :)'));
  15.       }
  16.     }
  17.   ]
  18. });
* This source code was highlighted with Source Code Highlighter.


Если мы хотим оповестить пользователя о чем-то, то достаточно одной кнопки «Закрыть»:

  1. DEMO.Dialog.show({
  2.   body : 'Э... кхм... ваше кофе сбежало!',
  3.   buttons : [
  4.     {
  5.       id : 'accept',
  6.       text : 'Куда?! o_O',
  7.       callback : function () { DEMO.Dialog.hide(); }
  8.     }
  9.   ]
  10. });
  11.  
* This source code was highlighted with Source Code Highlighter.


Думаю, css-код, используемый для примера, не важен.

Демонстрация.

Исходный код в архиве.

Вес javascript кода после склейки:
  • не сжатый: 6.4 KB
  • сжатый с помощью YUI Compressor'а: 2.2 KB
  • сжатый с помощью YUI Compressor'а и gzip-а: 931 B


Разумеется, это лишь пример, в реальном проекте нужно было добавить следующее:
  • кнопку «закрыть», чтобы которая скрывает окно
  • обработка события resize объекта window — чтобы располагать диалоговое окно всегда по центру
  • динамическое создание id у кнопок и самого диалогового окна, и запоминание конкретных instance'ов этих объектов
  • передачу контекста, т.е. this вместе с обработчиком, навешиваемым на кнопку
  • дополняйте :)


P.S. кросспост в моем блоге.
Теги:
Хабы:
+36
Комментарии73

Публикации

Изменить настройки темы

Истории

Работа

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн