Еще раз про JS-хуки - мое представление о том, как должно быть
Ежу понятно, что гибкость и расширяемость – это одни из основных характеристик любой CMS. И LiveStreet в этом плане весьма неплохо организован. Но, на мой взгляд, иногда движку не хватает какой-то концептуальной структурированности. Давайте рассмотрим, как мы можем расширять и менять скины (шаблоны), серверную часть (PHP) и клиентскую часть (javascript).
Скины (шаблоны)
Если нам нужно что-то добавить в отдельные предопределенные места, мы пользуемся хуками. Если же нам нужно значительно исправить какой-то шаблон – мы его переопределяем.
Серверная часть (PHP)
Если нам нужно добавить логику обработки в каких-то предопределенных местах, то мы пользуемся хуками. Если нам нужно значительно изменить логику работы какого-то метода в экшене или модуле, мы переопределяем метод.
Клиентская часть (javascript)
Если нам нужно добавить логику обработки в каких-то предопределенных местах, то мы пользуемся хуками. Если нам нужно значительно изменить логику работы какого-то метода в объекте ls, то мы… и тут происходит разрыв шаблона – мы пользуемся маркерами. Т.е. используем способ, который ничего не переопределяет, а грубо впихивает кусок кода в заданное место. Вопрос – зачем? Мало того, что ломается общий концептуальный подход, так еще и логика работы с обработкой ls-методов ломается.
Вот есть родной код (я немного упрощаю):
И теперь, если мне нужно, например, до ajax-вызова запустить иконку «ждите ответа», а по окончании вызова ее выключить, то я должен ДО вызова использовать инжектор, а ПОСЛЕ вызова – хук. Т.е. фактически для одного и того же действия надо использовать два совершенно разных механизма (с разным синтаксисом, разной реализацией, разным способом обработки). Зачем?
Нет, сама идея с хуками интересна. Она, конечно, в какой-то степени близка к пользовательским событиям в jQuery, повешенным на все окно, но, в отличие от jQuery, поддерживает приоритеты. Но реализация, на мой взгляд, хромает.
Предлагаемое решение
Клиентская javascript-часть сейчас весьма неплохо структурирована – все аккуратно обернуто пространством ls, разбито на логические блоки, разнесено по внятным и небольшим методам. Поэтому вполне логично было бы дать возможность точно так же, как в PHP-классах, переопределять методы. А для «ветвлений» использовать хуки, как в PHP и в шаблонах.
Для этого совсем чуть-чуть модифицируем вышеприведенную родную функцию (метод):
Тогда, если мне нужно будет лишь включить/выключить какую-то свою мигалку, не меняя логики работы функции, то мне достаточно поставить хук на два вызова:
А можно даже дать возможность сразу на оба хука повесить функции:
Более того, если в вызываемую хуком функцию всегда будет передаваться контекст, то возможностей у нас гораздо больше, чем это может показаться на первый взгляд:
Идем дальше – если нам по каким-то причинам надо прервать выполнение метода (и цепочки вызовов функций по хуку), то можно явно вернуть значение false.
Но уж если мне всего этого мало, то я могу просто переопределить весь метод в своем 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 комментариев
не уверен на счет этого, тогда переменные внутри функции нужно определять как this.var, а это уже идет на контекст выше в ls.blog, в итоге будет ls.blog.var. Не?
А это все зависит от того, какой контекст будет передаваться в исполняемую функцию, т.е. от конкретной реализации все зависит.
Сейчас в hook.js так:
Это значит, что значение this (т.е. контекст) будет зависить от того, что мы будем передавать при вызове хук-функции:
Вот что будет в третьем параметре obj, то и будет контекстом вызываемой функции.
Я бы предложил делать как-то так:
Т.е. условиться, что контекстом будет всегда вызывающий метод — ls.MODULE.METHOD. Кстати, при вызове хук-функций "_after" из аякса достаточно будет передавать только result, все остально мы можем получить из this.VAR
ты его проверял в работе? логически в этом примере ты через контекст ls.MODULE.METHOD не сможешь получить доступа к внутренним переменным
вполне тоже хорошая. только вот контекст не по ссылке передавать а привязать (через аплай, бинд или джеквиервский прокси), ибо через ссылку он будет доступен как: thisArginFunc.SomeVar1
проблема в том, что контекст не дает доступа к локальным переменным, соответственно нет возможности их изменить
если проверку возвращаемого значения вызываемой хук-функции делать внутри ls.hook.run()
А насчет контекста — собственно, в текущей реализации контекст через apply() передается, и я так же предполагал это делать