Pull to refresh

Comments 42

Мне кажется что ваш notice очень быстро превратится в непонятную кашу.

Недавно впервые столкнулся с большим количеством js в проекте и разделил его на 3 части:
— модули
— обработчики
— вспомогательные функции

Дальше написал метод автозагрузки обработчиков, который может выполнить набор функций в объекте.

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

Вот код который выполняет все функции в объекте.

Project.load = function (obj) {
  for (var handler in obj) {
    var load = obj[handler]();

    if (!load) continue;

    if (load.init) {
      load.init();
    } else {
      Project.init(load);
    }
  }
}


Скоро напишу статью об этом.
notice() в кашу не превращается, с чего бы? Обработка любого события в среднем занимает 3 строчки. Он может быть очень большим, это да. Но во-первых большой notice() != сложный notice(), его сложность практически не увеличивается от добавления новых событий. А во-вторых этот, даже большой, notice() — является почти идеальным изложением высокоуровневой логики всего приложения!

В этом его громадное преимущество перед динамической подпиской на события внутри модулей — либо вы чётко и явно видите что в какие моменты происходит в системе, либо всё это происходит неявно, под капотом. Явное всегда лучше неявного. К сожалению, иногда без динамической подписки на события обойтись нельзя, но эти ситуации бывают намного реже, чем принято думать.
А для чего это нужно? Приведите пожалуйста пример проблемы которую удается решить этим, я сейчас не говорю о проблеме структурирования кода, это есть во всех ОО подходах к JS и у каждого он свой.

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

Для отладки ведь можно добавлять console.log, а в готовом файле их просто убрать, тут же базируется вся архитектура на этом.
Проблемы? При чём тут конкретные проблемы, это простая разница между явным и не явным, и явное всегда лучше не явного (явное не всегда возможно, но когда возможно — всегда лучше).

Динамическая подписка на события автоматически означает несколько вещей, которые могут капитально осложнить отладку и понимание работы системы в целом:
  • Нет полного списка всех возможных событий. Любой модуль может в любой момент начать генерировать новый вид событий, а другой модуль может начать их слушать. В результате без изучения всего кода всех модулей вы никогда не выясните, какие же вообще события бывают в вашей системе.
  • Т.к. модули могут подписываться и отписываться от событий, то вы не можете быть уверены, что модуль гарантированно получит нужное событие. И если он не делает то, что должен был сделать после отправки этого события, то отладка может быть… интересной.
  • Иногда возникают проблемы из-за того, чтобы несколько динамически зарегистрированных обработчиков события выполняются не в том порядке.
  • и т.д.
Фактически, динамические обработчики моментально превращают вашу систему в неконтролируемый набор хаотично общающихся объектов. Если эти объекты пишутся очень аккуратно, с учётом всех возможных ошибок и нюансов, то всё отлично работает. Ты просто добавляешь новый модуль, и оно само его на лету подхватывает и как-то колбасится. Что и как там внутри происходит — никто не знает, но всё работает. Если аккуратно написано. А вот если не работает — отладка будет весёлой.

Я знаю о чём говорю — моя текущая система написана именно в таком стиле — только масштабы другие, не одностраничный js, а куча сетевых сервисов в кластере, которые динамически друг друга находят и общаются. Так вот, несмотря на то, что эта моя система отлично работает, я предпочитаю видеть в коде статический диспетчер событий до тех пор, пока это возможно — ибо это намного проще.
Вот например модуль всплывающего окна, почему он был бы лучше используя вашу технологию?

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

MyProject.module.popUp = function (data) {
  var $pp = $('<div class="project-popup"><a href="#" class="project-popup_close">Закрыть</a>'),
		  $bg = $('<div class="project-popup_backbg">');

  function getBgDimenstions() {
    return {
      x: $(document).width(),
      y: $(document).height()
    }
  }

  function getPpPosition() {
    return {
      left: $(window).width() / 2 - $pp.width() / 2,
      top: $(window).scrollTop() + ($(window).height() / 2) - $pp.height() / 2
    }
  }

  function setPositions() {
    var ppPos = getPpPosition(),
			  bgDim = getBgDimenstions();

    $pp.css({ 'left': ppPos.left, 'top': ppPos.top });
    $bg.width(bgDim.x).height(bgDim.y);
  }


  return {
    isBuild: false,

    el: {
      pp: $pp,
      bg: $bg
    },

    build: function () {
      $('body').append($bg).append($pp.append(data));

      var SELF = this;

      $bg.click(function () {
        SELF.hide();
      });

      $pp.find('.project-popup_close').click(function () {
        SELF.hide();
        return false;
      });
    },

    show: function () {
      if (!this.isBuild) {
        this.build();
        this.isBuild = true;
      }

      $('body').addClass('project-popup-overflow');

      setPositions();

      $pp.fadeIn();
      $bg.fadeTo(300, 0.7);

      $(window).bind('resize.popUp', function () {
        setPositions();
      });
    },

    hide: function () {
      $pp.fadeOut();

      $bg.fadeOut('normal', function () {
        $('body').removeClass('project-popup-overflow');
      });

      $(window).unbind('resize.popUp');
    },

    destroy: function () {
      $pp.remove();
      $bg.remove();
    }
  }
}
Статья об архитектуре. Т.е. о том, как разделить функциональность приложения между модулями, и как потом их объединить чтобы они совместно реализовали функциональность полного приложения. В каком стиле написаны эти модули, какими библиотеками пользуются, и как они делают свою часть работы — с точки зрения описанной в статье архитектуры не имеет значения, делайте как Вам удобно.

Поэтому Ваш вопрос не имеет смысла — имея один модуль его никак нельзя «улучшить» описанным в статье подходом. Улучшить (потенциально, конечно) можно архитектуру приложения, для чего нужно видеть не один модуль, а все, плюс код «ядра», которых их объединяет и обеспечивает взаимодействие между этими модулями. При этом реализация самих модулей может практически не измениться, смотря насколько хорошо функциональность приложения разделена между модулями и насколько они изолированы друг от друга.
А вы можете поделится живым примером, где используются тысячи строк js?
А если есть несколько реализаций одного и того же виджета, и надо в зависимости от ситуации подгружать определенную реализацию. Вы предлагаете все эти реализации впихнуть в notice?
Так же при каждом чихе надо лезть в этот самый notice и там что-то делать, это очень неудобно.
notice() занимается обработкой событий, а не подгрузкой конкретных реализаций. notice() знает, что при возникновении события A нужно создать новый объект класса B и добавить его в определённое место на страничке. Конкретная реализация класса B выбирается в других местах — либо на этапе подгрузки скриптов с сервера, либо класс B это на самом деле фабрика, и он сам выбирает подходящую реализацию в момент создания объекта.
Я не говорю что у вас плохая или неправильная архитектура, я с ней не работал, но с первого взгляда мне это кажется неудобным, и как писали выше приведет к каше, когда количество модулей будет приблежаться к тысяче (а вернее в несколько раз больше, потому как согласно вашей архитектуре каждый модуле будет разделен на несколько более простых виджетов).

Вот тут идет вполне конкретная реализация.
case 'spellcheck: success':
w.sum.add(data.spellerrors);
spellerrors[data.phrase] = data.spellerrors;
break;


Я не совсем понимаю почему обработчик события должен выноситься из модуля (виджета), в какой-то общий компонент. На мой взгляд, все что должен сделать notice: поймать событие и оповестить всех кто на него подписан.
Ведь по сути на тот же case 'spellcheck: success' могут реагировать несколько виджетов, получается, что придобавлении нового модуля который работает с этим кейсом надо лезть в notice и что-то допедаливать. Изменилась концепция модуля — лезем в notice, переписываем.

А во-вторых этот, даже большой, notice() — является почти идеальным изложением высокоуровневой логики всего приложения!

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

Я с трудом представляю тысячу разных модулей (не тысячу объектов одного класса, а именно тысячу разных классов) на одной страничке. Фактически это означает тысячу разных элементов UI, с которыми должен взаимодействовать пользователь. Вам этого пользователя не жалко? :) Я не уверен, что можно выделить тысячу таких виджетов даже на страничке GMail. Для таких больших проектов, как я и писал в статье, нужна полноценная архитектура предложенная Nicholas Zakas. Суть статьи в том, что большинство сложных приложений всё-таки не до такой степени сложны как GMail, и для них будет проще и нагляднее использовать описанный минималистский вариант этой архитектуры.
Тысяча модулей — легко в SAAS проектах.
Кстати GMail в принципе не особо большой проект и достаточно простой.
Если рассматривать как подход для более простых проектов, тогда это перетекает больше в вопрос идеологии, кому что больше по душе. Подойдет практически любая нормально структурированная архитектура.
Backbone.js не было 2 года назад, когда автор разработал свой фреймворк. И кстати у backbone нет глобального диспетчера событий (а очень не помешал бы).
что вы подразумеваете под «глобальным» диспетчером событий?
Диспетчер через который проходят все события приложения, неважно из какого объекта они запущены (triggered).
$(window).trigger('my_custom_event', { foo: 'bar' });

$(window).bind('my_custom_event', myCustomEventHandler);

function myCustomEventHandler(){ doStuff(); }


Подобное реализуется любой библиотекой уже много лет.
Это не отлавливает нативные события backbone.
Зачем? backbone.view легко расширяется до обработки любых событий в документе, например генерируемых jquery.ui.
Вообще не понимаю, зачем реализовывать свою систему событий вместо предоставляемой jquery.
Я выше в комментариях объяснял, зачем. Если в какой-то момент понадобятся динамические обработчики событий, то, разумеется, нет смысла что-то своё изобретать, вполне подойдут события jQuery. Но статический диспетчер всё сильно упрощает, поэтому (пока его возможностей хватает) нет смысла усложнять систему без нужды.
Скажите, а насколько легко вы в процессе принимаете решения о способе реализации тех или иных фич? Не возникает ли сложностей с выбором из пяти альтернативных вариантов реализации?
И правильно ли я понял, что предлагаемая вами концепция виджетов чем-то похожа на jquery.ui, когда на один элемент можно навесить и draggable, и resizable и пр.?
Принимать решения всегда непросто. :) Я стараюсь писать код как можно проще, что в свою очередь обычно означает что не будет универсальным и типовым. Т.е. из N вариантов приходится выбирать каждый раз разные, потому что ситуации/приложения тоже каждый раз разные, и самым простым решением в одной конкретной ситуации оказывается не то же решение, что в другой ситуации.

Могу привести пример такой ситуации даже с ключевыми элементами архитектуры: в некоторых приложениях проще оказывается держать в модулях/виджетах только логику, а все дизайнерские/оформительские действия выполнять в notice(); а в других приложениях проще писать модули с жёстким ограничением что в их часть DOM никто снаружи не лазит, и все дизайнерские/оформительские операции над модулем реализуются у него внутри (в существующих и/или специально созданных новых методах).

Насчёт jQuery.UI — врядли. Виджеты о которых писал я не предназначены для совместного навешивания на один и тот же элемент DOM. Виджет является единственным владельцем какого-то блока DOM, и максимум может делегировать части своего блока вложенным виджетам — но в этом случае он сам уже в эти части не лазит (если нужно — дёргает методы вложенного виждета которому он делегировал эту часть своего блока).
Подскажите как через jQuery отловить например встроенное событие backbone «change:attribute» не дублируя его методами jQuery?
События backbone не являются событиями документа, если я не ошибаюсь.
Возможно, а отлавливать их нужно. И то как сейчас backbone предлагает сделать (например передавать линки на вью в модели и/или обратно через конструкторы) — не очень удобно. А был бы родной бекбоновский глобальный диспетчер событий — ловить model.save() во вью было бы можно простой однострочной декларацией в events вью без лишних выкрутасов.
Во-первых, не надо путать Backbone.Events и события DOMа, это разные вещи. А что касается линков на модели, в чем проблема? View служит для формирования отображения модели, так что иметь ссылку на модель просто обязана, и все легко делается как раз простой однострочной декларацией this.model.bind('change', this.render, this); в методе initialize.
Не понял почему мне адресовано пожелание не путать события backbone и DOM, если я говорю только о нативных событиях backbone, но это неважно. Проблемы с передачей линков нет — костыль как костыль. Но еще раз — если бы был глобальный диспетчер backbone, линки можно было бы не передавать вообще, что сделало бы код лаконичнее и без костылей.
var eventEmitter = _.extend({}, Backbone.Events);
eventEmitter.bind('msg', function(msg){ console.log(msg) });
eventEmitter.trigger('msg', 'hello world');

не катит?
это не отловит например нативное событие «change», который триггерится backbone скажем в момент model.fetch() и которое хотелось бы отлавливать во вью простой декларацией во View.events
Хочется уточнить по поводу заворачивания статуса запроса в — разве replaceWith не решает описанной в тексте проблемы?
Нет, не решает. Я, безусловно, в магию верю. Но здесь довольно безнадёжная ситуация: есть объект (с тэгами) на который есть две ссылки (одна в DOM, вторая в this.$). Заменить этот объект другим (доступным по обоим ссылкам) используя только одну из ссылок невозможно — нужен дополнительный indirection level (ссылка на ссылку). Как писал Спольски, все программисты делятся на две группы — одни понимают ссылки, вторые нет. :-/ В данном случае я бы хотел их временно не понимать, и чтобы всё как-нить магически заработало. ;)
Мне почему то казалось что в this.$ как объект jQuery должна быть ссылка на DOM.
Разве проблема не в том что используется this.$.html(«далее идет строка с HTML кодом») вместо this.$.replaceWith? Руки чешутся проверить, но время то позднее :)
(ниже — псевдокод)
Нет, так не получится. У нас есть ссылка на "div#foo" в this.element и он же в DOM. Делаем так: $('div#foo').replaceWith('div#bar'), в DOM объект заменяется, а вот в this.element ссылка ведёт на "div#foo", которого даже нету на странице.
Допустим так, тогда поясните как у автора работает смена html кода после заворачивания его в span.
Видимо, меняется содержимое тега, на который у нас ссылка.
Этот span добавляет тот самый необходимый indirection level. С ним в DOM ссылка на span, а уже внутри span ссылка на отрендерённый шаблон. В this.$ тоже ссылка на span.

this.$.html() заменяет ссылку внутри span, после него span указывает на совершенно другой отрендерённый шаблон. При этом сам span не заменялся, так что ссылки на него и в DOM и в this.$ по-прежнему корректны.
Вот так вот берешь проект, открываешь такой маленький js файлик, чтобы просто поменять, например, какое-нить название поля…
А потом сидишь два дня разбираешся откуда гребанная ошибка выскакивает.
А как в вашей архитектуре может быть реализована функция wysiwyg-редактора «выделение текста» -> «удаление выделенного текста» (удаление с помощью кнопки на панели либо нажатием клавиши Del)?
  • За textarea с текстом отвечает виджет Text, у которого есть метод Text.del_selection().
  • За кнопку(и) на панели отвечает виджет Button, который по нажатию на кнопку вызывает notice('delete text').
  • Функция notice в обработчике события 'delete text' вызывает Text.del_selection().
UFO just landed and posted this here
Имхо ваш notice — это Контроллер, а «события» — это его методы. Если ваше приложение — это одна страница (именно как контекст, а не с точки зрения загрузки с сервера одного html), то все логично. Одна страница — один контроллер. А вот если в приложении появится несколько контекстов, то едининый контроллер смотрится странно. Думаю это и имели в виду предыдущие комментаторы, когда говорили про «будет каша».
Sign up to leave a comment.

Articles