Еще раз про JS-хуки - мое представление о том, как должно быть

Ежу понятно, что гибкость и расширяемость – это одни из основных характеристик любой CMS. И LiveStreet в этом плане весьма неплохо организован. Но, на мой взгляд, иногда движку не хватает какой-то концептуальной структурированности. Давайте рассмотрим, как мы можем расширять и менять скины (шаблоны), серверную часть (PHP) и клиентскую часть (javascript).

Скины (шаблоны)
Если нам нужно что-то добавить в отдельные предопределенные места, мы пользуемся хуками. Если же нам нужно значительно исправить какой-то шаблон – мы его переопределяем.

Серверная часть (PHP)
Если нам нужно добавить логику обработки в каких-то предопределенных местах, то мы пользуемся хуками. Если нам нужно значительно изменить логику работы какого-то метода в экшене или модуле, мы переопределяем метод.

Клиентская часть (javascript)
Если нам нужно добавить логику обработки в каких-то предопределенных местах, то мы пользуемся хуками. Если нам нужно значительно изменить логику работы какого-то метода в объекте ls, то мы… и тут происходит разрыв шаблона – мы пользуемся маркерами. Т.е. используем способ, который ничего не переопределяет, а грубо впихивает кусок кода в заданное место. Вопрос – зачем? Мало того, что ломается общий концептуальный подход, так еще и логика работы с обработкой ls-методов ломается.

Вот есть родной код (я немного упрощаю):
ls.blog = (function ($) {
   ls.blog.toggleJoin = function(obj, idBlog){
      var url = aRouter['blog']+'ajaxblogjoin/';
      var params = {idBlog: idBlog};

      ls.hook.marker('toggleJoinBefore');
      ls.ajax(url,params,function(result) {
         // тут код
         ls.hook.run('ls_blog_toggle_join_after',[idBlog,result],obj);
      });
   };
});

И теперь, если мне нужно, например, до ajax-вызова запустить иконку «ждите ответа», а по окончании вызова ее выключить, то я должен ДО вызова использовать инжектор, а ПОСЛЕ вызова – хук. Т.е. фактически для одного и того же действия надо использовать два совершенно разных механизма (с разным синтаксисом, разной реализацией, разным способом обработки). Зачем?

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

Предлагаемое решение
Клиентская javascript-часть сейчас весьма неплохо структурирована – все аккуратно обернуто пространством ls, разбито на логические блоки, разнесено по внятным и небольшим методам. Поэтому вполне логично было бы дать возможность точно так же, как в PHP-классах, переопределять методы. А для «ветвлений» использовать хуки, как в PHP и в шаблонах.

Для этого совсем чуть-чуть модифицируем вышеприведенную родную функцию (метод):
ls.blog = (function ($) {
   ls.blog.toggleJoin = function(obj, idBlog){
      var url = aRouter['blog']+'ajaxblogjoin/';
      var params = {idBlog: idBlog};

      if (ls.hook.run('ls_blog_toggle_join_before', this) === false) return;
      ls.ajax(url,params,function(result) {
         // тут код
         ls.hook.run('ls_blog_toggle_join_after',[idBlog,result],obj);
      });
   };
});

Тогда, если мне нужно будет лишь включить/выключить какую-то свою мигалку, не меняя логики работы функции, то мне достаточно поставить хук на два вызова:
ls.hook.add('ls_blog_toggle_join_before',function() {
   // turn on
});
ls.hook.add('ls_blog_toggle_join_after',function() {
   // turn off
});

А можно даже дать возможность сразу на оба хука повесить функции:
ls.hook.add('ls_blog_toggle_join_trigger',function() {
   // turn on
}, function() {
   // turn off
});

Более того, если в вызываемую хуком функцию всегда будет передаваться контекст, то возможностей у нас гораздо больше, чем это может показаться на первый взгляд:
ls.hook.add('ls_blog_toggle_join_before',function() {
   this.url = aRouter['blog']+'mySpecialAjaxUrl/'; // меняем url
   this.params = {idBlog: idBlog, otherParam:  'blabla'}; // добавляем свои параметры
});

Идем дальше – если нам по каким-то причинам надо прервать выполнение метода (и цепочки вызовов функций по хуку), то можно явно вернуть значение false.

Но уж если мне всего этого мало, то я могу просто переопределить весь метод в своем javascript-коде вот так:
ls.blog.toggleJoin = function(obj, idBlog){
    // тут полностью мой код, верчу, как хочу
};

10 комментариев

avatar
Т.е. фактически для одного и того же действия надо использовать два совершенно разных механизма (с разным синтаксисом, разной реализацией, разным способом обработки). Зачем?
вот так исторически сложилось :) Изначально хуков в js не было, они понадобились 1d10t , который сделал их реализацию и запулил на гит. По факту их никто и не использовал, кроме него, поэтому фидбека и обкатки не было.

Более того, если в вызываемую хуком функцию всегда будет передаваться контекст, то возможностей у нас гораздо больше, чем это может показаться на первый взгляд
не уверен на счет этого, тогда переменные внутри функции нужно определять как this.var, а это уже идет на контекст выше в ls.blog, в итоге будет ls.blog.var. Не?
  • ort
  • 0
avatar
вот так исторически сложилось
Понимаю, нормальное явление, идеальные решения не возникают сразу. Но ведь ничто не вечно под луной :) Я ж говорю, сама идея js-хуков — отличная. И предлагаю ее до логического конца довести.

не уверен на счет этого, тогда переменные внутри функции нужно определять как this.var, а это уже идет на контекст выше в ls.blog, в итоге будет ls.blog.var. Не?
А это все зависит от того, какой контекст будет передаваться в исполняемую функцию, т.е. от конкретной реализации все зависит.
avatar
А это все зависит от того, какой контекст будет передаваться в исполняемую функцию, т.е. от конкретной реализации все зависит.
это я про твой пример выше, в нем такое не должно прокатить
avatar
Я же говорю — от конкретной реализации зависит.

Сейчас в hook.js так:
this.run = function(name,params,o) {
    // ...
    if($.type(callback) == 'function'){
        callback.apply(o, params);
    }
    // ...
}

Это значит, что значение this (т.е. контекст) будет зависить от того, что мы будем передавать при вызове хук-функции:
ls.hook.run('ls_blog_toggle_join_after',[idBlog,result], obj);

Вот что будет в третьем параметре obj, то и будет контекстом вызываемой функции.

Я бы предложил делать как-то так:
ls.blog = (function ($) {
   ls.blog.toggleJoin = function(obj, idBlog){
      var $method = this;
      var url = aRouter['blog']+'ajaxblogjoin/';
      var params = {idBlog: idBlog};

      if (ls.hook.run('ls_blog_toggle_join_before', $method) === false) return;
      ls.ajax(url,params,function(result) {
         // тут код
         ls.hook.run('ls_blog_toggle_join_after',[idBlog,result],$method);
      });
   };
});

Т.е. условиться, что контекстом будет всегда вызывающий метод — ls.MODULE.METHOD. Кстати, при вызове хук-функций "_after" из аякса достаточно будет передавать только result, все остально мы можем получить из this.VAR
avatar
Т.е. условиться, что контекстом будет всегда вызывающий метод — ls.MODULE.METHOD. Кстати, при вызове хук-функций "_after" из аякса достаточно будет передавать только result, все остально мы можем получить из this.VAR
я этот вариант и рассматриваю
ты его проверял в работе? логически в этом примере ты через контекст ls.MODULE.METHOD не сможешь получить доступа к внутренним переменным
avatar
По факту их никто и не использовал, кроме него, поэтому фидбека и обкатки не было.
ну вот мы разобрались полностью и идея
if (ls.hook.run('ls_blog_toggle_join_before', this) === false) return;

вполне тоже хорошая. только вот контекст не по ссылке передавать а привязать (через аплай, бинд или джеквиервский прокси), ибо через ссылку он будет доступен как: thisArginFunc.SomeVar1
avatar
он и передается через apply
callback.apply(o, params);

проблема в том, что контекст не дает доступа к локальным переменным, соответственно нет возможности их изменить
avatar
контекст не дает доступа к локальным переменным
разве? хм, надо будет проверить
avatar
я об этом все сообщения выше и писал :)
avatar
Можно даже чуть сократить, вот так:
if (!ls.hook.run('ls_blog_toggle_join_before', this)) return;
если проверку возвращаемого значения вызываемой хук-функции делать внутри ls.hook.run()

А насчет контекста — собственно, в текущей реализации контекст через apply() передается, и я так же предполагал это делать
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.