Пишем свой MooTools-плагин.

    Доброго времени суток.
    Это мой первый серьезный пост на Хабре, так что критика приветствуется.
    Сегодня я расскажу о написании плагина для JavaScript-библиотеки MooTools на примере модального всплывающего окна.

    HTML, CSS.

    Что же из себя будет представлять наше модальное окно? Схематично его можно представить следующим образом.

    Schema

    С точки зрения HTML, всплывающее окно — это обычный блочный элемент (например, div) и еще один блочный элемент для создания полупрозрачного оверлея. Функциональный HTML-код будет содержать всего две строки:
    <div id="Overlay"></div>
    <div idPopup»>Сообщение в окне.</div>

    CSS-код будет выглядеть так:
    body
    {
       font-family: Arial, Tahoma, Sans-Serif;
       font-size: 1em;
    }

    #Popup
    {
       visibility: hidden;
       position: absolute;
       left: 50%;
       top: 50%;
       background-color: #FFF;
       padding: 10px;
    }

    #Overlay
    {
       visibility: hidden;
       position: absolute;
       left: 0;
       top: 0;
       width: 100%;
       background-color: #000;
    }

    С помощью visibility: hidden мы намеренно прячем всплывающее окно и оверлей, чтобы их не было видно сразу после загрузки страницы. Внимательный читатель мог заметить, что у оверлея в данный момент нулевая высота и всплывающее окно находится не в центре страницы (т.к. в центре страницы размещен левый верхний угол окна). Все это мы исправим в JavaScript.

    JavaScript.

    Теперь займемся JavaScript-кодом.
    Пожалуй, полностью описывать синтаксис классов в MooTools я не буду, тут дается довольно полное описание. Давайте только взглянем на каркас класса, который возьмем для написания плагина, чтобы быстро разобраться, что тут к чему:
    var MoodalBox = new Class({
       // Здесь мы указываем классы, из которых в наш класс будут скопированы свойства и методы.
       // В данном случае нам нужен метод setOptions() из класса Options.
       Implements: [Options],

       // Перечисляем возможные опции, передаваемые экземпляру класса и их значения по умолчанию.
       options: {
          optionName1: defaultValue1,
          optionName2: defaultValue2
       },

       // Это — конструктор нашего будущего класса.
       initialize: function(options)
       {
          // Выставляем значения опций, переданных в конструктор
          // (если туда ничего не передано, то берутся значения по умолчанию).
          this.setOptions(options);

          // Далее следует логика конструктора.
       }
    });

    Теперь усложним этот каркас тремя новыми методами: show (показать окно), hide (скрыть окно), setPosition (спозиционировать окно), также придумаем более осмысленные опции и добавим парочку аргументов в конструктор:
    var MoodalBox = new Class({
       Implements: [Options],

       // Зададимся двумя опциями.
       options: {
          // Прозрачность оверлея после показа всплывающего окна.
          destinationOverlayOpacity: 0.7,
          
          // Возможность спрятать окно кликом по оверлею.
          allowManualClose: true
       },

       // В конструктор передаем два обязательных аргумента и необязательный аргумент с опциями.
       //  element — идентификатор элемента окна.
       //  overlay — идентификатор элемента оверлея.
       initialize: function(element, overlay, options)
       {
          this.setOptions(options);

          // Получаем элемент по его идентификатору.
          this.element = $(element);
          this.overlay = $(overlay);

          // Проверяем опцию возможности скрытия окна по клику.
          if (this.options.allowManualClose)
             // Цепляем на клик по оверлею метод скрытия.
             // Примечание: функция bind(param) возвращает метод this.hide, внутри которого переменная this
             // привязана к param. Если опустить вызов bind, тогда this внутри this.hide будет привязан к элементу,
             // событие в котором мы обрабатываем, т.е. к this.overlay.
             this.overlay.addEvent("click", this.hide.bind(this));

          // Получаем размеры окна для последующей его центровки.
          this.targetCoords = this.element.getCoordinates();

          // Эффекты для показывания/скрытия окна и оверлея.
          // Используется эффект, изменяющий заданное CSS-свойство во времени, в данном случае — это прозрачность.
          this.fx = {
             overlayAnimation: new Fx.Tween(this.overlay, { property: "opacity" }),
             elementAnimation: new Fx.Tween(this.element, { property: "opacity" })
          }
       },

       // Вызов этого метода покажет всплывающее окно.
       show: function()
       {
          // Выставляем элементы в нужные места.
          this.setPosition();
          
          // Запускаем анимацию показа (изменение прозрачности до видимых значений).
          this.fx.overlayAnimation.start(0, this.options.destinationOverlayOpacity);
          this.fx.elementAnimation.start(0, 1);
       },

       // Вызов этого метода скроет всплывающее окно.
       hide: function()
       {
          // Запускаем анимацию скрытия (изменение прозрачности до невидимых значений).
          this.fx.overlayAnimation.start(this.options.destinationOverlayOpacity, 0);
          this.fx.elementAnimation.start(1, 0);
       },

       // Здесь мы корректируем позицию всплывающего окна на странице и
       // размеры оверлея.
       setPosition: function()
       {
          this.element.setStyles({
             "marginLeft": -(this.targetCoords.width / 2),
             "marginTop": -(this.targetCoords.height / 2)
          });

          this.overlay.setStyles({
             "top": window.getScrollTop(),
             "height": window.getHeight()
          });
       }
    });

    Для использования класса необходимо создать его экземпляр и добавить на страницу кнопочку, нажатие на которую будет показывать окно. В результате HTML-код примет следующий вид:
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
       <title>Mootools plugin</title>
       <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
       <link href="./Style.css" rel="Stylesheet" type="text/css" />
       <!-- Для работы нужно подключить библиотеку Mootools. -->
       <script src="../../MooToolsCore.js" type="text/javascript" language="javascript"></script>
       <!-- Тут подключаем наш плагин. -->
       <script src="./MoodalBox.js" type="text/javascript" language="javascript"></script>
       <script type="text/javascript" language="javascript">
       //<![CDATA[
          var popupFx = null;
          
          // Создадим экземпляр плагина после загрузки DOM-модели.
          // В конструктор передаем идентификаторы элементов окна и оверлея.
          window.addEvent("domready", function() { popupFx = new MoodalBox("Popup", "Overlay"); });
       //]]>
       </script>
    </head>
    <body>
       <input type="button" value="Show popup" onclick="popupFx.show();" />
       <div id="Overlay"></div>
       <div id="Popup">Сообщение в окне.</div>
    </body>
    </html>

    Увидеть, как это работает можно здесь. А что же делать с опциями, которые можно изменять? Мы их просто передаем в конструктор в виде объекта. Например, так можно изменить прозрачность оверлея после показа окна:
    popupFx = new MoodalBox("Popup", "Overlay", { destinationOverlayOpacity: 0.3 });

    Более интересных результатов можно добиться с использованием класса Fx.Morph, который изменяет во времени одновременно несколько CSS-свойств. Результат с анимацией размеров можно посмотреть здесь. Если вам понравилась статья — буду писать еще.

    P.S. Весь CSS-код можно было не выделять в отдельный файл, а инкапсулировать в JavaScript-классе, но тогда бы класс оброс лишними вызовами изменений CSS-свойств элемента и было бы сложнее уловить саму суть.

    Кросс-пост из моего блога.

    Резюмирую ссылки.

    Простой пример.
    Пример с анимацией размеров.

    UPD. Зеркало (спасибо habracut):
    Простой пример.
    Пример с анимацией размеров.

    UPD. Извините за хостинг, может кто посоветует что-нить хорошее и бесплатное… Вот здесь архив со всеми примерами.

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 61

      +1
      Ссылки на демки битые, а статья понравилась — пишите еще.
      +3
      Хорошее вступление, надеюсь это будет начало серии подобных статей о mootools.
        0
        Спасибо, тогда буду готовить статью о цепях последовательных эффектов.
        0
        кто теперь объяснит, стоит ли вообще задумывать о Mootools если уже юзаю jQuery? вроде jQuery тоже самое умеет?
        • НЛО прилетело и опубликовало эту надпись здесь
            0
            я про разного рода анимации, у меня получалось =)
              0
              Выбор JS-фреймворка — дело сугубо личное и я ни в коем случае не говорю, что jQuery лучше или хуже Mootools, т.к. это в очередной раз привело бы к холивару. Я думаю, что эта статья будет интересна в первую очередь тем, кто еще не определился с выбором в пользу того или иного фреймворка и им будет полезно увидеть какие-либо примеры из мира Mootools, чтобы сравнить их с jQuery. Так уж получилось, что я использую Mootools, может быть потому что мне очень нравится Mootools download builder =)
                0
                :) да, удобная фишка, в EXT вроде подобие есть.
                Просто я раньше думал что мутулс для эффектов специально заточено или еще чего… вот и спросил, может если делать анимацию, то лучше пожертвовать несколькими килобайтами пользователя и подгрузить мутулс к jquery…
                  0
                  Да, вот только Ext, к сожалению, сам не отслеживает зависимости выбираемых компонентов.
                    0
                    а чем не нравится анимация в jQuery.UI?
                    0
                    Очень часто за тебя этот выбор делают разработчики CMS / CMF, которую предполагается использовать в проекте. Переписывать уже имеющиеся скрипты с одного фреймворка на другой- занятие долгое и неблагодарное. Проще научиться работать с другим js-фреймворком.
                  0
                  мутулс, как мы видим, тоже по-умолчанию этого не умеет.
                  • НЛО прилетело и опубликовало эту надпись здесь
                      +1
                      jQuery.UI содержит неплохие инструменты для анимирования.
                      • НЛО прилетело и опубликовало эту надпись здесь
                          0
                          да, мне тоже UI кажется пока еще недоделанным.
                          но эффекты там вполне даже ничего — docs.jquery.com/UI/Effects
                          насчет модульности — мутулс, насколько я знаю, тоже сооружается из модулей и в Core анимации нет.
                  0
                  Есть для этого jqModal. Но там код писали какие-то индусы, название перменных в одну букву, трудно читается.
                    0
                    не надо задумываться. jQuery не хуже.
                      –1
                      jQuery — eto super :)
                        0
                        Mootools, конечно, хорошо, но лично мне не понравилась слабая документация. Мне вот тоже по душе jQuery, в первую очередь из-за отличной документированности. Так сказать меньше преград при освоении.
                          0
                          Денис, ты что издеваешься? docs.mootools.net/
                            –4
                            Хм, последний раз там было несколько меньше (после выхода 1.2). Зато у jQuery на каждую функцию живые примеры :). Да и за счет более крупного сообщества легче найти ответ на тот или иной вопрос.
                            +1
                            Ой, ник перепутал, извиняюсь. Но несмотря на это, у mootools отличная документация.
                            –1
                            Mootools селекторы текут в IE. У нас сейчас обратный процесс, с Mootools перебираемся на jQuery, MooTools по сравнению с jQuery оказалась намноооого хуже.

                            Так что сидите смирно =).

                            Разве что о Dojo можно подумать.
                              0
                              спасибо, и всем выше тоже :)
                                +1
                                Уже 2 года пользую Mootools и ни разу не видел, чтобы хоть что-нибудь не работало в IE. Не обижайтесь, но если селекторы «текут», меняйте программистов, а не фреймворк + ко всему по скорости работы Moo быстрее JQ и модульная схема (именно такая, когда к ядру добавляются модули, реализующие те или иные функциональные возможности, а не надстройки, реализующие эффекты все и сразу, вроде UI в JQ) это действительно круто
                                  +1
                                  при чём тут «работало» и «не работало». Текут в IE6 и IE7 (сделайте сотню элементов, навешайте ивенты и профильните в IE6, даже на пустой странице можно), с позиционированием касяки (с оффсетом) в Opera 9.27. В багтрекер загляните.

                                  Селекторы в jQuery быстрее. Факт. А UI-хрень все равно руками пишем.
                                    0
                                    в getPosition есть баги также в opera9,5 и в ff3
                              +2
                              Считаю, что написано неплохо и имеет смысл активно продолжать. Спасибо.

                              [off: голосовать мне хабр не дает, наверно чего то нехватает :), так что только словесная поддержка]
                                +5
                                1) для скрытых элементов правильнее использовать свойство display
                                2) для возможности использования в боевых условиях надо брать не уже существующий блок, а создавать новый
                                3) если пишем плагин для mootools — сначала смотрим как принято оформлять исходники именно в этой библиотеке

                                а в целом хорошо будет, если люди узнают что есть адекватные библиотеки, а не только jquery
                                  0
                                  1) Да, возможно. Но т.к. здесь элементы спозиционированы абсолютно, то на мой взгляд — без разницы.
                                  2) Здесь я старался максимально просто написать класс, поэтому и CSS и создание элементов выделил из кода.
                                  3) Согласен.
                                    0
                                    я и не предлагал втаскивать css в скрипты. только создание блока и контента в нем.
                                  +4
                                  Yarc, я доработал ваш класс и исправил некоторые ошибки.

                                  Что было сделано:

                                  — MoodalBox теперь является синглетоном
                                  — Позиционирование оверлея и окна теперь возложено на css. Javascript работает только для корректировки позиции окна.
                                  — Динамическое создание оверлея и окна.

                                  Изначально MoodalBox работал неправильно, если страница была длиной больше длины viewport'a браузера. Я спозиционировал окно и оверлей при помощи css и эта проблема исчезла.

                                  Всё, что я добавил нового в класс, помечено моим ником.

                                  Прошу учесть изменения)

                                  И да, скачать новый код можно здесь: imaker.ru/repo/archive_bonch_edited.zip
                                    +2
                                    За CSS спасибо, буду знать, а то я всегда решал проблемы динамической высоты с помощью JavaScript. Объясните только, зачем этот класс делать синглтоном?
                                    И еще: идея не создавать элемент, а использовать имеющийся у меня возникла, когда я смотрел на стиль написания классов в самом Mootools. Возьмите любой класс, который реализует какой-либо эффект и увидите, что в его конструктор передается идентификатор элемента, с которым мы хотим работать. Таким образом исчезает проблема внедрения контента программно (в любом случае сверстать мало-мальски сложное окошко проще, чем создавать все его элементы программно), IMHO.
                                      0
                                      Синглетон — в данном случае для того, чтобы нельзя было несколько раз инстанцировать класс, что поможет избежать ошибок, если кто-то захочет это сделать. В принципе, от этого пожно отказаться, но с точки зрения ООП это правильный подход.

                                      По второму вопросу. Элемент не следует создавать в тех случаях, когда вы хотите, чтобы при отключенном JS пользователь все же что-то увидел. Например, такой подход с передачей id элемента (или самого элемента) можно использовать при создании выпадающего меню, или как в случае с плагинами mootools для Accordion или Sortables.
                                        0
                                        В том проекте, для которого я писал подобный класс, мне как раз было необходимо несколько экземпляров класса. Например, один — для показа какой-нибудь формы, а другой для блокировки оверлеем элементов управления и отображения прогресса при асинхронных запросах.
                                        0
                                        И еще, если требуется динамически создать сложный элемент, то я сначала верстаю его и только потом создаю на JS. Важно не забывать про порядок вложенных элементов, которые мы создаем.

                                        Здесь нам помогут el.inject(element. 'after|before|top|...') и закладка html в firebug (чтобы посмотреть порядок динамически создаваемых тагов).
                                      +2
                                      А, еще забыл: добавил возможнось внедрять контент в окно.
                                      Контент может быть как строкой (текстом), так и элементом (например, div'ом).
                                        0
                                        автор, развивай класс. На данный момент он выглядит ущербно ) встраивание информации всплывающего окна в код основного окна диковато, было бы неплохо ajax задействовать. В целом молодец, ждем продолжения…
                                          0
                                          Это ж просто пример, как написать класс-плагин на mootools. У автора не стояла задача сделать продакшн-плагин.

                                          А то, что я немного модифицировал код, так это только для того, чтобы показать решение проблемы, которая присутствует практически во всех системах всплывающих окон.
                                          +1
                                          На счет хостинга — www.000webhost.com/

                                          Хороший и бесплатный )
                                            +1
                                            Именно на него я и выложил примеры. А в самый ответственный момент они закрыли сайт на проверку соответствия контента их правилам.
                                              0
                                              Можешь дать мне примеры в ЛС и я их на своем выложу.
                                                0
                                                Спасибо, но habracut уже выложил.
                                            0
                                            Я как раз еще не разбирался толком с яваскриптом и фреймворками для него, но меня всегда интересовали подобные эффекты.

                                            Отличный топик, спасибо.
                                              0
                                              Все здорово, но Jevix на хабре портит код «елочками».
                                                0
                                                нахрен они его для кода включили-то? oO
                                                0
                                                this.hide.bind -> this.hide.bindWithEvent. Не?

                                                и терминология не верная — это у вас никакой не плагин, а просто «что-то» при помощи Mootools.

                                                Плагины подразумевают расширение функционала, которое будет доступно без дополнительных манипуляций. Ну например добавление функционала к Element.
                                                  –4
                                                  кстати, что-то сразу не подумал. А ведь есть же уже mootools-more.js. Там есть модальные окна. Чего велосипед-то делать? =)
                                                  0
                                                  Тогда почему разработчики Mootools тот же Fx.Slide отнесли в раздел плагинов?
                                                    0
                                                    потому что он очень много чего экстендит в самом mootools.
                                                  0
                                                  А чем синтаксис подсвечивали?
                                                    0
                                                    Вроде многие тут подсвечивают на source.virtser.net/
                                                    Я тоже )) Правда немного глючная эта подсветка для HTML.
                                                    0
                                                    вместо
                                                    this.fx.overlayAnimation.start(0, this.options.destinationOverlayOpacity);
                                                    this.fx.elementAnimation.start(0, 1);
                                                    я бы сделал просто
                                                    this.fx.overlayAnimation.start(this.options.destinationOverlayOpacity);
                                                    this.fx.elementAnimation.start(1);
                                                    и в другом месте аналогично.

                                                    Также добавил бы link: cancel, хотя при большой скорости анимации, которая по умолчанию это не важно
                                                    new Fx.Tween(this.overlay, { property: 'opacity', link:'cancel' })
                                                      0
                                                      Спасибо за напоминание о link.
                                                      0
                                                      Столкнулся с тем, что window.getHeight() не определяет размер окна с контентом, а только размер окна браузера. Решается использованием функции getPageSize() с quirksmode.org:
                                                      function getPageSize(){
                                                      var xScroll, yScroll;
                                                      if (window.innerHeight && window.scrollMaxY) {
                                                      xScroll = document.body.scrollWidth;
                                                      yScroll = window.innerHeight + window.scrollMaxY;
                                                      } else if (document.body.scrollHeight > document.body.offsetHeight){ // all but Explorer Mac
                                                      xScroll = document.body.scrollWidth;
                                                      yScroll = document.body.scrollHeight;
                                                      } else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
                                                      xScroll = document.body.offsetWidth;
                                                      yScroll = document.body.offsetHeight;
                                                      }
                                                      var windowWidth, windowHeight;
                                                      if (self.innerHeight) { // all except Explorer
                                                      windowWidth = self.innerWidth;
                                                      windowHeight = self.innerHeight;
                                                      } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
                                                      windowWidth = document.documentElement.clientWidth;
                                                      windowHeight = document.documentElement.clientHeight;
                                                      } else if (document.body) { // other Explorers
                                                      windowWidth = document.body.clientWidth;
                                                      windowHeight = document.body.clientHeight;
                                                      }
                                                      // for small pages with total height less then height of the viewport
                                                      if(yScroll < windowHeight){
                                                      pageHeight = windowHeight;
                                                      } else {
                                                      pageHeight = yScroll;
                                                      }

                                                      // for small pages with total width less then width of the viewport
                                                      if(xScroll < windowWidth){
                                                      pageWidth = windowWidth;
                                                      } else {
                                                      pageWidth = xScroll;
                                                      }

                                                      arrayPageSize = new Array(pageWidth,pageHeight,windowWidth,windowHeight);
                                                      return arrayPageSize;
                                                      }

                                                      В вашем классе:

                                                      var a = getPageSize();
                                                      this.overlay.setStyles({
                                                      «top»: window.getScrollTop(),
                                                      «height»: a[1]
                                                      });

                                                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                      Самое читаемое