JBlocks — небольшой jQuery-плагин (~100 строк) для организации компонентов на странице.

Строится на трех основных принципах:

  • опиши поведение компонента в декларации;
  • разметь компонент в html с помощью специальных атрибутов;
  • общайся со экземплярами компонента через АПИ.

Если вам интересна тема декларативного javascript — прошу под кат.

Пусть у нас есть простой контрол: счетчик. Шаг счетчика — параметр, который можно менять для разных экземпляров.
image
Демо

Объявим новый компонент counter:

$.jblocks({
    // имя компонента
    name: 'counter',

    // декларация событий
    events: {
        // встроенные события: b-inited, b-destroyed
        'b-inited': 'oninit',

        // дом-события
        'click .js-inc': 'onClickPlus',
        'click .js-dec': 'onClickMinus'
    },

    // методы компонента
    methods: {
        oninit: function() {
            this.$info = this.$node.find('.js-info');
            this.count = 0;
        },

        /**
         * Обработчик клика на плюс
         */
        onClickPlus: function() {
            this.inc();
            this.update();
        },

        /**
         * Обработчик клика на минус
         */
        onClickMinus: function() {
            this.dec();
            this.update();
        },

        /**
         * Увеличить счетчик
         */
        inc: function() {
            this.count += this.params.step;
        },

        /**
         * Уменьшить счетчик
         */
        dec: function() {
            this.count -= this.params.step;
        },

        /**
         * Нарисовать новое значение
         */
        update: function() {
            this.$info.text(this.count);
        }
    }
});

Разметим в html компонент с помощью специальных атрибутов:

<div class="foo" data-b="сounter" data-p='{ "step": 2 }'>
    ...
</div>

  • data-b — айдишник блока (имя)
  • data-p — параметры блока


Компоненты можно инициализировать с помощью $.fn.jblocks('init') и уничтожать с помощью $.fn.jblocks('destroy'):

// Инициализируем все блоки в документе
$(document).jblocks('init');

// Уничтожаем все блоки в документе
$(document).jblocks('destroy');

// Или внутри конретной части DOM-дерева
$('#app').jblocks('init');

С помощью функции $.fn.jblocks('get') можно получать ссылки на экземпляры. Если компонент еще не был инициализирован, то сперва это будет сделано.

$('.foo').jblocks('get').each(function() {
    // this - экземпляр блока, inc — его метод
    this.inc();
});


UPD:

Спасибо всем за конструктивную критику (и даже один пул реквест). Обновил репозиторий и описание статьи в соответствии с новым АПИ.

Комментарии (73)


  1. Delphinum
    02.10.2015 14:19

    Не так давно написал подобное, назвал это виджетами и активно пользую. В общем то логика «блоков» очень проста, проблемы появляются уже при решении прикладных задач.


    1. vitkarpov
      02.10.2015 14:27

      Да, действительно, виджеты — это оно же. Спасибо.

      Не так давно написал подобное


      Решил поделиться решением, вдруг кому-то будет полезно: кажется, каждый пишет нечто подобное, если не пользуется чем-то более тяжелым.


      1. Delphinum
        02.10.2015 14:34

        Ну собсна решение сводится к следующему:

        Пример виджета
        function Widget(stcNode){
          this.srcNode = srcNode;
        }
        Widget.prototype.render = function(){
          // Что то делаем с this.srcNode
        }
        Widget.prototype.destroy = function(){
          // Как то чистим this.srcNode
        }
        
        // Наследуемся от Widget.
        


        1. vitkarpov
          02.10.2015 14:55

          Спасибо. Да, действительно, это все интересные задачки.


  1. Borz
    02.10.2015 14:25
    +5

    про заголовок статьи подумалось: «фреймворк на фреймворке и фреймворком погоняет»
    это же чистой воды плагин в терминологии jQuery

    по содержимому статьи — хотелось бы ещё увидеть пример отправки события на сервер.


    1. vitkarpov
      02.10.2015 14:32

      это же чистой воды плагин в терминологии jQuery


      Да, действительно, похоже на то. Спасибо.

      хотелось бы ещё увидеть пример отправки события на сервер


      JBlocks всего лишь способ описать виджеты на странице. Если нужно делать запрос на сервере: можно завести метод fetch, который дернет $.ajax внутри. Далее все зависит от логики приложения: можно найти блок на странице с помощью getBlocks и вызвать метод fetch когда это будет необходимо.


      1. Borz
        02.10.2015 14:36

        думается, более правильным было бы при вызове метода initBlocks() в качестве опционального аргумента принимать callback-функцию, вызываемую при изменении счетчика


  1. Ashot
    02.10.2015 14:36

    $.define
    

    Вот такое решение кажется сомнительным.
    Во-первых, слово define очень уж общее, и не понятно, что определяется именно JBlock.
    Во-вторых, использовать такое «общее» слово в качестве имени метода в корне объекта jQuery может привести к казусам, если в разработчик другого плагина тоже решит определить свой $.define. В общем, я бы на вашем месте переименовал бы метод в defineJBlock — и более точно определяет, что метод делает, и вряд ли его кто-нибудь перекроет.
    Я могу и ошибаться в своём мнении, но мне кажется так будет безопасней.


    1. vitkarpov
      02.10.2015 14:45

      Да, действительно. Пространство имено — очень важно. Просто хотелось сделать название как можно более очевидным. Видимо, лучше сделать $.jbDefine. Спасибо!


  1. newkamikaze
    02.10.2015 14:38
    +1

    Добавьте коллбэк после изменения значения, чтобы можно было использовать ajax.


    1. vitkarpov
      02.10.2015 14:52

      Если я правильно вас понял, то логику отправки нового значения на сервер, после изменения значения счетчика, можно либо инкапсулировать в блоке:

      $.define('counter', {
        ...
        methods: {
          inc: function() {
            this.count += this.params.step;
            this.updateOnServer();
          },
          updateOnServer: function() {
            $.ajax(...)
          }
        }
      })
      


      либо, т.к. в данном случае метод inc синхронный, можно вызвать где-то в коде приложения сперва inc у блока, а затем updateOnServer


      1. Delphinum
        02.10.2015 15:12
        +2

        Почему бы просто не выбрасывать событие, информирующее об изменении состояния (возможно конкретного параметра) блока? На это событие можно будет декларативно подписать внутренний или внешний метод и в нем отсылать сообщения на сервер.


        1. vitkarpov
          02.10.2015 15:19

          Да, можно и так действительно.


  1. GIum
    02.10.2015 16:00

    Какой же это нано-фреймворк если он зависиом от jQuery? Да и какой вообще смысл от jQuery в 2015 кроме как поддержка старых версий IE?


    1. withoutuniverse
      02.10.2015 19:20
      -1

      Не подумайте, что я придираюсь, мне правда интересно (я понемногу изучаю верстку и программирование для веб):

      Какие сегодня есть альтернативы jQuery? Почему он считается устаревшим?
      Если не использовать jQuery, то не выйдет использовать и тот же Bootstrap, а какие ему сегодня есть популярные альтернативы?


      1. GIum
        02.10.2015 19:43

        Он не столько устарел, сколько просто потерял свою необходимость по мере развития браузеров. Смотрите VanillaJS в качестве альтернативы.

        По поводу бустрапа, не все так печально, Вы просто не сможете использовать его плагины, а это далеко не самая его важная часть. Да и есть энтузиасты которые портировали его на ту же ванилу.


        1. withoutuniverse
          02.10.2015 22:53
          -1

          Я немного не понял — вы троллите? Предлагаете написать 240 килобайт jQuery кода на велосипеды в виде чистого javascript?
          Есть ли реальные альтернативы jQuery? Есть ли реальные альтернативы Bootstrap?


          1. GIum
            02.10.2015 23:02
            -1

            Я немного не понял — вы троллите?

            Нисколько.

            Предлагаете написать 240 килобайт jQuery кода на велосипеды в виде чистого javascript?

            Зачем тебе писать свой jQuery?


            1. withoutuniverse
              02.10.2015 23:16

              Предпочту общаться на ВЫ.
              Мне понадобится как минимум около 30 методов, реализация которых уже имеется в jQuery.
              Не вижу смысла изобретать велосипед заново и писать кроссплатформенный код, если подобное уже сделано профессионалами.

              Вы так и не ответили на мой вопрос: Есть ли реальные альтернативы jQuery? Есть ли реальные альтернативы Bootstrap?


              1. GIum
                02.10.2015 23:43

                Вы так и не ответили на мой вопрос: Есть ли реальные альтернативы jQuery? Есть ли реальные альтернативы Bootstrap?

                Смотри выше.


              1. symbix
                03.10.2015 00:07

                Реальная альтернатива бутстрапу — бутстрап.

                Какой такой кроссплатформенный код? Вам нужна поддержка IE6? Современный JavaScript (ES5+) ничем не менее удобен, чем jquery, если же нужна поддержка IE8 — есть ES5-shim.


  1. dom1n1k
    02.10.2015 17:46
    +1

    Идея хорошая (что-то типа очень лайтового Backbone), но апи надо переписывать и убирать всё из глобальной области видимости.
    Первый блин комом, но судя по аккуратному и симпатичному в целом коду, автору не составит труда довести до ума блин номер два.


    1. vitkarpov
      02.10.2015 18:42

      убирать всё из глобальной области видимости


      Предлагаете отказываться от jQuery как зависимости и поддержать нормальную модульность?

      Изначальная идея была в том, чтобы сделать штуку для, грубо говоря, простого верстальщика, который привык работать с jQuery и на простом сайте он уже подключен.


      1. dom1n1k
        02.10.2015 18:51

        Нет.
        По идеологии jQ-плагинов, они не должны совать свои методы в глобальный объект $. Тем более, с такими общими и неуникальными именами, как initBlocks или destroyBlocks.
        Обычно принято делать примерно так:

        $.jblocks('define', { ... });
        $.jblocks('init');
        $.jblocks('destroy');
        

        Это убирает весь функционал плагина в одно пространство имён и позволяет сохранить цепные вызовы, если нужно.


    1. novoxudonoser
      02.10.2015 20:40

      Присоединяюсь, почистите апи и добавьте местные советы, получится отличный легковесный плагн.


  1. jrip
    02.10.2015 18:49
    +1

    Ок, круто. Вы придумали чутка недоделанный Backbone.
    backbonejs.org


    1. vitkarpov
      02.10.2015 19:01
      -2

      Бэкбон это все-таки про одностраничные приложения, а это скорее reactjs, только очень простой — не для веб-приложений, которые живут годами, а для простых сайтов, например, лендингов


      1. jrip
        02.10.2015 19:06
        +1

        View из бекбона можно использовать и на многостраничных приложениях, будет ровно тоже самое что у автора этого фреймворка.

        Вот это, как раз View бекбона, даже events там такие же:

        $.define('сounter', {
        // декларация событий
        events: {
        // встроенные событие блока: b-inited, b-destroyed
        'b-inited': 'oninit',

        // дом-события
        'click .js-inc': 'onClickPlus',
        'click .js-dec': 'onClickMinus'
        },

        // методы блока
        methods: {
        }
        }


      1. InteractiveTechnology
        02.10.2015 19:40

        Сделали на backbonejs несколько сложных UI корпоративных систем в стиле extjs. Так что вы чего-то наговариваете лишнего, один из удобнейших фреймворков. Всего лишь backbonejs+requirejs и вот вам готовая модульная система с неограниченными возможностями построения клиентской части :)


        1. Delphinum
          02.10.2015 19:56

          Всего лишь backbonejs+requirejs и вот вам готовая модульная система с неограниченными возможностями построения клиентской части

          Вы ошибаетесь. Хотите приведу пример того, что эта связка не может? )


          1. InteractiveTechnology
            02.10.2015 20:03

            Конечно, если не сложно, с радостью!


            1. Delphinum
              02.10.2015 20:09

              Недавно была такая проблема. Есть у нас каталог виджетов, в котором каждый виджет хранится в отдельном файле. При открытии страницы на ней с помощью JS мы находим все узлы с data-widget="..." и используем их как корни для соответствующих виджетов. Тобишь:

              <div data-widget="Select"></div>
              

              долже быть использован как корень для виджета public/scripts/widgets/Select.js и т.д.

              Дело тут в том, что виджеты должны загружаться и подключаться к странице синхронно. После загрузки и подключения всех виджетов JS должен отсигналить об этом событием. Как это сделать, если учесть, что requirejs не хочет синхронно?


              1. vitkarpov
                02.10.2015 20:20

                А не хочется собрать весь js виджетов в один бандл и загрузить на страницу целиком?


                1. Delphinum
                  02.10.2015 20:28

                  Нельзя, начальство против.


                  1. vitkarpov
                    02.10.2015 20:37

                    Может стоит глянуть в сторону YM-модулей, вроде есть поддержка асинхронности.


                    1. Delphinum
                      03.10.2015 01:11

                      вроде есть поддержка асинхронности

                      Нужна поддержка синхронности )


              1. InteractiveTechnology
                02.10.2015 21:24

                Разве так не работает?

                var widgets = [];
                $('div[data-widget]').each(function(index, el) { 
                	var widget = $(el).data('widget');
                        widgets.push(widget);
                });
                require(widgets);
                

                Можно вот такой хак, вместо requirejs
                function injectScript(src){
                    var s, t;
                    s = document.createElement('script'); s.type = 'text/javascript'; s.async = true; s.src = src;
                    t = document.getElementsByTagName('script')[0]; t.parentNode.insertBefore(s,t);
                }
                


                1. Delphinum
                  02.10.2015 21:49

                  Разве так не работает?

                  Предполагается что будет так:
                  // Рендеринг виджетов.
                  var widgets = $('[data-widget]'),
                      widgetsNames = [];
                  widgets.each(function(){
                      widgetsNames.push('widget/' + $(this).data('widget'));
                  });
                  
                  // Предварительная загрузка скриптов виждетов для последовательной 
                  // обработки.
                  require(widgetsNames, function(a){
                      // Последовательный рендеринг виджетов.
                      widgets.each(function(){
                          var srcNode = this;
                          require(['widget/' + $(this).data('widget')], function(widget){
                              var widget = new widget(srcNode, obj);
                              widget.render();
                  
                              var widgetId = $(srcNode).attr('id');
                              // Добавление виджета к списку объявленных в popup виджетов.
                              if(widgetId !== undefined){
                                  obj.widgets[widgetId] = widget;
                              }
                          });
                      });
                  });
                  

                  но это нисколько не синхронно.

                  Можно вот такой хак, вместо requirejs

                  Мы же с вами о связке, а не о хаках говорим ;)


                  1. InteractiveTechnology
                    02.10.2015 21:52

                    Так

                    $('div[data-widget]').each(function(index, el) { 
                            require($(el).data('widget'));
                    });
                    


                    1. Delphinum
                      03.10.2015 01:12

                      Ок, а как вызвать в таком случае метод render у виджета? И где здесь синхронность?


              1. zxcabs
                03.10.2015 02:14

                webpack -> require.ensure(['./file.js'], function () {}) или require('bundle?lazy!./file.js')(function () {});


                1. Delphinum
                  03.10.2015 02:27

                  Вам бы сначала ветку прочесть, а уже потом webpack'и предлагать ;) (не в обиду)
                  У webpack, кстати, тоже есть одна нерешаемая проблема.


                  1. zxcabs
                    03.10.2015 03:26
                    +1

                    какая?


                    1. Delphinum
                      03.10.2015 13:39

                      Я уже упоминал о ней на хабре, но мне не удалось толково объяснить задачу.
                      Если коротко, нужно заставить webpack в зависимости от прикладной логики собирать проект с подключением и без подкбючения модуля.


                      1. stas404
                        03.10.2015 22:09

                        Это случайно не то, что вы имеете ввиду?
                        DefinePlugin:
                        https://webpack.github.io/docs/list-of-plugins.html#defineplugin
                        Resolve:
                        https://webpack.github.io/docs/configuration.html#resolve-alias


                        1. Delphinum
                          03.10.2015 23:49

                          Может быть, но все усложняется еще тем, что в результирующем js файле не должно быть ничего, с помощью чего пользователь сможет открыть исходник, который ему не предназначался. То есть на пример у нас есть admin.js, этот файл должен подключаться для админов, но для обычных пользователей его не просто нужно заменить на заглушку, но и вообще не включать при сборке. Ну и, естесна, делать две сборки, с admin.js файлом и без него нельзя.


                          1. stas404
                            04.10.2015 00:41
                            +1

                            Если в результирующем js-файле (давайте назовем его index.js) не хочется упоминаний и намеков на admin.js, то можно, например, сделать две «entry points»:

                            entry: {
                              index: path.resolve('./src/index.js'),
                              admin: path.resolve('./src/admin.js')
                            }
                            
                            При этом на выходе получим два отдельных файла — обычному пользователю отдаем один index.js, админу оба: index.js + admin.js.


                            1. Delphinum
                              04.10.2015 00:43
                              -3

                              Ну и, естесна, делать две сборки, с admin.js файлом и без него нельзя


                              1. stas404
                                04.10.2015 01:08
                                +1

                                Да, только под «двумя сборками» я понял два разных бандла (отдельный бандл для пользователя и отдельный для админа) и грузится либо один, либо другой.
                                А тут у вся общая логика лежит в index.js, а в admin.js лишь то, что относится к админу и отдается сверху тогда, когда необходимо.

                                Ответьте на три вопроса, подумаем вместе.
                                1) Чем не устраивает подход?
                                2) В каком виде вы представляете и хотели бы получить результат, с учетом того, что «в результирующем js-файле не должно быть ничего, с помощью чего пользователь сможет открыть исходник, который ему не предназначался»?
                                3) Решает ли какой-либо существующий сборщик модулей эту задачу более приятным способом?


                                1. Delphinum
                                  04.10.2015 01:22
                                  +1

                                  А тут у вся общая логика лежит в index.js, а в admin.js лишь то, что относится к админу и отдается сверху тогда, когда необходимо.

                                  Да, это и нужно. Возможно я не понял что дает предложенное вами решение.

                                  1) Последний? Ну если он создает то, что вы описали, то вполне устраивает.
                                  2) Два файла с разной логикой.
                                  3) Не знаю, что вы понимаете под приятностью, но когда то я эту зачаду решил через Grunt.

                                  Предлагаю закончить эту тему, а то меня сейчас адепты webpack линчуют )))


              1. oledje
                04.10.2015 12:46

                Что-то я не совсем понимаю, что вы имеете ввиду под словом синхронно. Если имеется ввиду последовательная загрузка файлов, то у меня два вопроса:
                Для чего загружать файлы последовательно и почему вы решили что RequireJs этого не умеет? RequireJs это просто загрузчик файлов (модулей). Как хотите, так и загружайте. Хоть последовательно (синхронно), поть параллельно (асинхронно).


                1. Delphinum
                  04.10.2015 13:30

                  Для чего загружать файлы последовательно

                  Ну задача такая.
                  почему вы решили что RequireJs этого не умеет

                  Потому что пытался заставить его сделать это и наткнулся на то, что RequireJs считает такой подход неправильным и не дает грузить модули синхронно. Если я ошибаюсь, приведите пример синхронной загрузки модуля с помощью RequireJs, буду благодарен.


                  1. oledje
                    04.10.2015 15:13

                    Если я правильно понял задачу, то это можно сделать например так:
                    plnkr.co/edit/6OQlGJVY04FDVn6Q1ABu?p=preview

                    Во вкладке Network можно убедиться что файлы загружаются последовательно один за другим:

                    Network


                    1. Delphinum
                      04.10.2015 15:20

                      Если я правильно понял задачу, то это можно сделать например так

                      Да так и делаем. Задача в том, чтобы не использовать подобные ухищрения для обхода невозможности синхронной загрузки в RequireJS. Ведь было бы куда проще сделать просто:
                      require([widget1, widget2, widget3], function(widgets){
                        for(var i in widgets)
                          widgets[i].render();
                      });
                      

                      Но приходится изощрятся.

                      Или «Начальство против» это такая новая методология разработки?

                      Не новая, очень даже старая методология.


                      1. oledje
                        04.10.2015 15:36

                        Ну выше вы утверждали что это невозможно, а на деле выходит что это решается в пять строчек кода.

                        Да, над изощренными задачами всегда приходится изощряться. И я полагаю, что если этот подход обусловлен только тем, что начальство против, то это трижды плохой подход. Нужно уметь аргументировать свою точку зрения, указать начальству на недостатки и предложить альтернативу. Я когда-то уволился только по причине таких задач от начальства компании заказчика.


                        1. Delphinum
                          04.10.2015 20:40

                          Ну выше вы утверждали что это невозможно, а на деле выходит что это решается в пять строчек кода

                          На деле не решается. Помнится делали мы так, как предлагаете вы, но в результате в файле index.js все же хранится адрес, по которому можно найти admin.js файл. Потому и решили отказаться от использования webpack.
                          Нужно уметь аргументировать свою точку зрения, указать начальству на недостатки и предложить альтернативу

                          Это все конечно прекрасно, но иногда начальство против и точка. Да, я тоже уволился ))


                          1. oledje
                            04.10.2015 21:26

                            Вы наверное что-то путаете. Я не предлагал вам использовать webpack. Изучите внимательно мой пример.


                            1. Delphinum
                              04.10.2015 21:29

                              Оу, сорри, попутал ветку )

                              Ну выше вы утверждали что это невозможно, а на деле выходит что это решается в пять строчек кода

                              Ну не совсем решается, на деле это костыль, с помощью которого можно обойти ограничения RequireJS по загрузки модулей синхронно.


                              1. oledje
                                04.10.2015 22:39

                                Почему не совсем? Это реализация поставленной вам задачи. Сама по себе задача позразумавает костыльное решение и RequireJs тут не при чем. Никаких ограничений такого рода в RequireJs нет. Где в вебе вообще есть синхронная загрузка файлов? Браузеры по умалчанию загружают файлы асинхронно.


                                1. Delphinum
                                  05.10.2015 00:05

                                  Сама по себе задача позразумавает костыльное решение

                                  То есть в невозможности синхронной загрузки файла через RequireJS виновата задача?
                                  Никаких ограничений такого рода в RequireJs нет.

                                  Как это нет? RequireJS не грузит файлы синхронно.
                                  Где в вебе вообще есть синхронная загрузка файлов

                                  $.ajax({
                                    async: false
                                  });
                                  

                                  Берите и грузите файлы синхронно.


                                  1. oledje
                                    05.10.2015 11:46

                                    Как это нет? RequireJS не грузит файлы синхронно.

                                    Повторюсь, изучите внимательно мой пример. В нем файлы загружаются синхронно при помощи RequireJs.

                                    Использование jQuery.ajax ни чем не лучше RequireJs, а тем более с параметром async: false.

                                    Во-первых, в вашем случае вам придется городить приблизительно что-то подобное что и в моем примере, вызывая jQuery.ajax в цикле, только выйдет это более громоздко.

                                    Во-вторых, не забывайте, что выполнение таких запросов может временно заблокировать браузер до тех пор пока они не будут выполнены.

                                    В-третьих, имейте в виду, что при загрузке js файлов при помощи jQuery.ajax (с параметром dataType: 'script') или при помощи jQuery.getScript в случае ошибки в загруженном файле вы не получите полезной информации в консоли (такой как файл, в котором произошла ошибка, строка и т.д.).

                                    В-четвертых, использование async: false теперь deprecated при некоторых обстоятельствах.


                                    1. Delphinum
                                      05.10.2015 14:32

                                      В нем файлы загружаются синхронно при помощи RequireJs

                                      В вашем примере файлы загружаются синхронно благодаря костылю. Сам RequireJS не грузит их синхронно, нет у него такой функции.
                                      Использование jQuery.ajax ни чем не лучше RequireJs, а тем более с параметром async: false

                                      Вопрос не в лучше/хуже, вопрос очень конкретный — загрузка модулей синхронно. Вы говорите что грузить их синхронно нельзя в принципе, я вам привожу пример того, как это легко осуществляется. В RequireJS от такого подхода отказались полностью — только асинхронная загрузка.
                                      Во-первых

                                      Почему же громоздко:
                                      for(var i in widgets){
                                        $.ajax({
                                          async: false,
                                          url: ...
                                        });
                                      }
                                      

                                      Куда проще вашего примера, так как нет никакой синхронизации потоков.
                                      Во-вторых

                                      Может, но мы не об этом.
                                      В-третьих

                                      Так вопрос не в этом, я о том, что RequireJS грузит только асинхронно. Вы можете костылить вокруг этого факта и добится синхронной загрузки, возможно даже лучше чем с помощью $.ajax, но это не отменяет невозможности синхронной загрузки через RequireJS.
                                      В-четвертых

                                      Да пактически всегда, видимо от того RequireJS решили и отказаться от синхронной загрузки.


  1. saggid
    02.10.2015 20:36

    Ребята, посмотрите на Riot.js) У подобного «бакбоновского» подхода к написанию компонентов есть несколько существенных проблем, и тогда, когда кода, написанного в таком стиле, у вас накопится много, поддержка подобных компонентов усложнится по таким причинам, как:

    1. Разделённость представления (html) и логики (js) по разным файлам, из-за чего вы постоянно будете видеть только один фрагмент своего компонента; а также постоянно держать в голове то, как они взаимодействуют друг с другом;
    2. Когда у вашего компонента накопится штук 10 event lievener'ов, особенно спустя пол года после его написания, вы долго будете «прощупывать своего слона» в стремлении понять, что там на что навешивается, и как оно вообще работает. Из этого следует также трудность дальнейшего развития подобных компонентов, даже их внешний вид постоянно приходится прорабатывать с учётом того самого массива event listener'ов: удалили какой-то класс у одного из div-ок? А вы уверены, что на него не был навешан event listener какой-нить?) В общем, придётся знать их наизусть и постоянно учитывать при разработке. Я лично работал в подобном стиле, не знаю как вам, но меня это реально удручало и раздражало.
    3. У такого подхода нет простой возможности каким-либо образом отрендерить всё это дело на сервере; я лично писал два шаблона: для сервера и для JS. Что тоже часто удручало.

    И эти проблемы разрешаются с помощью маленькой UI-библиотеки Riot.js. Весь код компонента в ней можно сложить в одном файле; и самое главное: вместо «бакбоновского» массива со списком слушателей событий, вы просто пишете на нужных элементах атрибуты «onclick», «onmouseover», «oninput», и так далее: Riot.js всё это проанализирует и навесит всё что надо и куда надо. Больше никакой мороки с анализом массива прослушивателей. Сначала это может выглядеть довольно странно, но такой подход на самом деле намного удобнее бакбоновского. При этом, кстати, никто не запрещает навесить какие-то свои дополнительные прослушиватели событий при инициализации компонента, через jQuery.on(), к примеру.

    Вот пример того же самого компонента «счетчика» (см. файл counter.html) на основе Riot: http://plnkr.co/edit/hIDIv6fKi4SLUHXleg0U?p=preview. Всего 28 строк кода, причём они содержат сразу и вёрстку, и JS логику, и даже внешний вид вашего виджета. А JS-код компонента вовсе состоит всего из четырёх простых строчек.

    Если всё это еще и на основе Jade шаблонизатора писать — то там вообще выйдет очень чистый и приятный для восприятия компонент. Единственное, стили я всё-таки обычно кладу в отдельный scss-файл, так как их зачастую бывает слишком много, и не удобно их хранить в Riot-компонентах по многим причинам, так как теряется возможность их дальнейшей обработки всякими Grunt'ами, а также теряется возможность запихнуть этот кусок стилей в один общий файл app.css. Поэтому, стили я держу отдельно.

    Плюс, ко всему прочему, вдобавок вы получаете возможность очень легко рендерить состояние ваших компонентов со стороны сервера через Node.js. К слову, я в своём проекте сделал небольшой сервер на ноде, который создаёт файл unix socket'а в папке проекта и возвращает результат рендера Riot.js компонентов на входящие запросы. А на стороне Пыхчанского я после этого реализовал небольшую функцию, которая коннектится к этому самому сокету и запрашивает нужные компоненты с данными. Итого, я избавил свой проект от написания шаблонов на стороне сервера и полностью переложил всю эту работу на Riot.js. Сначала компоненты сайта рендерит нода; а уже после этого браузер загружает тот же самый код и инициализирует всю JS-логику.

    В общем, подобный подход разработки получился намного удобнее и приятнее, чем бакбоновский, которым я тоже ранее пользовался. Рекомендую попробовать работать подобным образом и вам тоже, конечно же.

    P.S. Я знаю, что на хабре часто любят задавать разные «провокационные» вопросы, стремясь поумничать, поважничать и унизить слова другого человека. Не обижайтесь, если я оставлю подобное ваше сообщение без своего ответа, так как не люблю пустые разговоры. Не понравился данный подход — я рад за вас, и перетягивать кого-то куда-то насильно я не собирался, я лишь делюсь знаниями с другими ремесленниками. В моём случае это прекрасно работает. Как работать вам — это уже ваше личное решение.


    1. vitkarpov
      02.10.2015 20:40

      Хороший подход. По сути, это веб-компоненты, верно?


    1. Delphinum
      03.10.2015 01:16

      Мы как бы пытаемся отделить верстку от лигики, а вы предлагаете не только аттрибуты левые использовать, но и собственные теги придумывать? Как то не очень если честно.

      Не считаю ни одну из перечисленных вами проблем реальной, все они прекрасно решаются при использовании виджетов.


    1. jrip
      06.10.2015 12:22

      >Разделённость представления (html) и логики (js) по разным файлам,
      Т.е. по вашему шаблонизаторы это зло?

      >из-за чего вы постоянно будете видеть только один фрагмент своего компонента
      Может проще купить второй монитор чем все в кучу перемешивать?

      > Когда у вашего компонента накопится штук 10 event lievener'ов, особенно спустя пол года после его написания
      Если он нужны, они в любом подходе добавятся.

      >вы долго будете «прощупывать своего слона»
      в «бекбоновском» подходе есть такое понятие как subview

      >удалили какой-то класс у одного из div-ок?
      Нафига удалять классы из дивов? Надо разделять классы отображения и классы для Js

      > У такого подхода нет простой возможности каким-либо образом отрендерить всё это дело на сервере
      Реально ли часто, внезапно возникает необходимость что-то отрендерить на сервере? А если и возникает, не проблемы ли это планирования?

      >вы просто пишете на нужных элементах атрибуты «onclick», «onmouseover», «oninput», и так далее:
      Это здорово, когда интерфейс в целом простой и нет необходимости менять события по ходу работы скрипта.
      Ток одного не пойму — нафига при этом использовать этот олдскульный подход, вместо внятного списка селекторов мы получаем лапшеверстку, где вообще фиг поймешь кто куда навешен, пока всю не просмотришь.

      >При этом, кстати, никто не запрещает навесить какие-то свои дополнительные
      >прослушиватели событий при инициализации компонента, через
      Ок, того подхода не хватило, разбавим его еще одним. Главное чтобы тот кто после нас будет дорабатывать не оказался злобным маньяком.

      >К слову, я в своём проекте сделал небольшой сервер на ноде, который создаёт файл unix socket'а в папке проекта и возвращает результат рендера Riot.js компонентов на входящие запросы. А на стороне Пыхчанского я после этого реализовал небольшую функцию, которая коннектится к этому самому сокету и запрашивает нужные компоненты с данными. Итого, я избавил свой проект от написания шаблонов на стороне сервера и полностью переложил всю эту работу на Riot.js. Сначала компоненты сайта рендерит нода; а уже после этого браузер загружает тот же самый код и инициализирует всю JS-логику.

      Ну и нафига? Не проще отдавать просто данные, например на Пхп, а на клиенте все рендерить?

      >Не понравился данный подход — я рад за вас, и перетягивать
      >кого-то куда-то насильно я не собирался, я лишь делюсь знаниями с другими ремесленниками.
      В том то и проблема, таких «ремесленников» на JS все больше и больше, он уже ПХП переплюнул.


      1. saggid
        06.10.2015 13:44

        Дорогой мой собрат по ремеслу) Я же написал: не нравится подход — делайте так, как лично вам нравится.

        И советую вам задуматься: чем наполнено ваше сердце? Безосновательной ненавистью к окружающим, высокомерным отношением к работам других людей… Желанием непременно возвыситься на унижении другого человека.

        И вы от всего этого удовольствие получаете? Я непричастен к подобным ценностям и занятиям, до свидания.


        1. jrip
          06.10.2015 19:37

          >Дорогой мой собрат по ремеслу) Я же написал: не нравится подход — делайте так, как лично вам нравится.
          >И советую вам задуматься: чем наполнено ваше сердце?

          Мое сердце при прочтении вашего комментария наполнено предчувствием, что есть очень большая вероятность, в дальнейшем, работать с кодом, который написали следуя таким рекомендациями как ваша. Поэтому, чувствую необходимость выразить с вашим комментарием несогласие, дабы вселить в остальных прочитавших некоторое недоверие, а возможно и критическое восприятие.


          1. saggid
            07.10.2015 14:39

            Вы просто почти ничего не поняли из того, что я написал в своём самом первом комментарии. Вы не поняли, зачем я так делаю, почему я выбрал именно эту библиотеку, по какой причине я добавил серверный рендеринг к обычному браузерному, по какой причине я отказываюсь от event listener'ов, и так далее… Хотя даже мой коммент довольно подробно всё разъясняет.

            Если вы не понимаете чьих-то мотивов, это еще не означает, что вы умнее этого человека, а человек этот обязательно не прав.

            Большинство вопросов, которые здесь задают в комментах разные хабровчане — просто из-за их личного невежества. Зачем делать серверный рендер? Зачем кастомные тэги и атрибуты добавлять?.. Такое ощущение, будто весь рунет до сих пор всё судит на основе стандартов прошлого века. О веб-компонентах вообще походу никто ничего не слышал. Мир меняется, развивается, изобретаются новые подходы к разработке, а на хабрахабр народ всё вы***ся и вы***ся в комментариях своими познаниями прошлого десятилетия… Вы еще ждёте, что я буду сидеть тут и вести с вами эти беседы, доказывать вам свою правоту? Зачем мне это? Тем более когда тебе пишут в такой категоричной и высокомерной форме.

            Поизучайте современные UI библиотеки на досуге. Не одним Бакбоном же мир живёт. Почитайте о том, зачем нужен серверный рендер, если не понимаете, зачем его делают. Не торопитесь быстрее бежать в комментарии опровергать и предостерегать всех вокруг. Возможно, предостерегать надо от вас, а не от меня. До свидания ещё раз, и всего хорошего вам.


            1. jrip
              07.10.2015 15:14

              >Вы просто почти ничего не поняли из того, что я написал в своём самом первом комментарии.
              У вас были утверждения, у меня конкретные вопросы к ним, а также конкретные их опровержения.

              >Хотя даже мой коммент довольно подробно всё разъясняет.
              Нет.

              >Если вы не понимаете чьих-то мотивов, это еще не означает, что вы умнее этого человека, а человек этот обязательно не прав.
              Умом не меряюсь, ваши утвержения посчитал не правильными, ничего личного.

              >Поизучайте современные UI библиотеки на досуге. Не одним Бакбоном же мир живёт.
              С чего вы взяли что я не изучал ничего другого и использую только бекбон?

              >Большинство вопросов, которые здесь задают в комментах разные хабровчане — просто из-за их личного невежества.
              И вы еще пишете про чьето высокомерие?

              >Мир меняется, развивается, изобретаются новые подходы к разработке
              Конечно, только причем тут утвержедния из вашего первого комментария?

              > Возможно, предостерегать надо от вас, а не от меня. До свидания ещё раз, и всего хорошего вам.
              Вам лень прочитать то что я написал и ответить, вместо этого вы пишите бессмысленный высокомерный комментарий, зачем? Не лучше ли было написать по существу, а не пытаться как то нелепо себя обелить?
              Вообщем неконструктивный разговор, да всего хорошего :)


              1. saggid
                08.10.2015 16:35

                Я не люблю вести беседы с высокомерными людьми. Я с радостью поддержу беседу и разъясню всё более подробно, если человек внимательно читает мой текст и обращается с уважением к собеседнику. Не так уж часто в этом мире встречаешь умных людей) Но когда этот умный человек через каждое слово пишет тебе «ну и нафига?» — желание объяснять ему что-то после этого полностью пропадает)

                Отвечу на ваши вопросы.

                1. Шаблонизаторы не зло, в Riot.js используются те же самые шаблоны. Здесь также разделяется логика от отображения, но при этом они находятся недалеко друг от друга и по сути являются единым целым)

                2. Вообще, можно и редактор разделить на два окна, да. Но более проблемно то, что обычно шаблон и его JS логика находятся в разных файлах (и даже папках) вашего приложения, что просто является менее удобным вариантом, чем когда всё это дело лежит в одном файле в целостном виде.

                3. Я вообще не понял вас. Я же написал, что в Riot вместо списков event listener'ов используют атрибуты onclick, onmouseover, и так далее. Они, кстати, не добавленные авторами библиотеки же, как подумал один из комментаторов выше. Эти атрибуты полностью соответствуют спецификации HTML, и авторы Riot в этом плане ничего от себя не придумывали, они их лишь перехватывают и обрабатывают их своим кодом, так сказать.

                4. Subview всё равно не избавит вас от необходимости изучения кода же) просто чуток облегчит проблему. В Riot тоже, кстати, компоненты прекрасно вкладываются друг в друга, чем я неоднократно уже пользовался. Те же самые subview'ы, в общем.

                5. Классы из дивов иногда приходится удалять при изменении логики ранее написанных компонентов в соответствии с новым ТЗ) Я лично просто с этим несколько раз столкнулся, и пишу то, что мне было неприятно делать при переработке какого-то сложного бакбоновского компонента.

                6. Благодаря серверному рендеру убиваются сразу два зайца: посетитель ваш мгновенно видит контент на открытой странице и не ждёт подгрузки JS логики и рендеринга вёрстки, а поисковые боты не испытывают проблем со сканированием страниц вашего сайта.

                7. Думаю, это лишь ваши предположения о том, что это может быть неудобно) У меня абсолютно другое мнение по данному вопросу: написанный таким образом компонент намного удобнее изучать, чем Бакбоновские шаблоны с JS-логикой в отдельном файле (на котором я сам лично довольно много всего понаписал за время своей работы).

                8. В реальности такая необходимость возникает только в особых ситуациях, в которых и на Бакбоне вам также придётся создать свои отдельные listener'ы в методе инициализации вашего View'а. Например, когда надо отслеживать события изменения размера окна браузера, скроллинг и клики мышью, и так далее. Я про такие ситуации говорил. Здесь конечно же не засунешь эти event listener'ы куда-то в тело вёрстки вашего компонента)

                9. Насчёт серверного рендера я уже ответил, зачем оно нужно)


  1. lega
    02.10.2015 21:28
    +2

    Riot: Всего 28 строк кода...
    Для сравнения приведу пример под Angular Light, тут даже js писать не надо, пример.


  1. slayerhabr
    02.10.2015 23:03
    +2

    Я так понимаю Вы из Сколково?