$$._chain( func1 [, args1] ) ._chain( func2 [, args2] ) ... ._chain( funcN [, argsN] ) .run();
Метод _chain добавляет звено цепочки, funcX – функция, argsX – опциональный массив передаваемых ей параметров, метод _run запускает цепочку. Для того, чтоб передать управление следующему звену цепочки, внутри текущей функции достаточно вызвать метод this.next();

Скачать _chain.js можно здесь >>
Зачем это нужно?
_chain.js прежде всего применим в Rich Internet Applications построенных на JavaScript, и вот почему:
- В js существуют отсроченные во времени функции, т.е. мы не можем сказать точно, когда они закончат работу и соответственно, если после отсроченной функции вызвать что – то ещё ( funcWithDelay(); func(); ), то что – то ещё ( func() ) выполнится быстрее. В js отсроченные во времени функции это прежде всего это AJAX запросы и функции анимации.
- Если несколько функций анимации выполняются параллельно, это может существенно тормозить работу веб – приложения, поэтому в некоторых случаях имеет смысл создать цепочку и выполнять следующую функцию только после завершения предыдущей.
- Чем масштабней веб – приложение, тем длинее цепочки могут быть в нём. Гораздо удобней, когда вызов всех функций цепочки перед глазами в одном месте.
- Внутри каждой функции цепочки следующее звено анонимно и скрыто в методе this.next(), это даёт сразу два преимущества по сравнению с явным вызовом функции: во-первых одну и ту же функцию можно использовать в нескольких цепочках, во-вторых при таком подходе легче разделить презентационный слой приложения и слой бизнес-логики.
function jumpTop(){ this.arguments.obj.animate({ marginTop: 0}, 300, this.next ); } function jumpBottom(){ this.arguments.obj.animate({ marginTop: 180}, 300, this.next ); } function jumpLeft(){ this.arguments.obj.animate({ marginLeft: '-=300'}, 300, this.next ); } function jumpRight(){ this.arguments.obj.animate({ marginLeft: '+=300'}, 300, this.next ); } function getBigger(){ this.arguments.obj.animate({ width: '+=50', height : '+=50'}, 300, this.next ); } function getSmaller(){ this.arguments.obj.animate({ width: '-=50', height : '-=50'}, 300, this.next ); }
Если у вас хорошая фантазия, можете представить, например, что jumpTop – это AJAX запрос, jumpBottom – обработка полученного json объекта, getBigger – плавное появление всплывающего окошка и т.п.
Хоть _chain.js и является самостоятельным скриптом и не использует в личных целях ни один из фреймворков, в примерах данной статьи мы используем jQuery (главным образом из-за его замечательного метода animate). Если ваши религиозные воззрения не позволяют вам пользоваться jQuery, не читайте дальше.
Первое знакомство
В каждой функции цепочки в переменной this доступно свойство arguments, в котором хранится объект, доступный для всех функций цепочки. Для того, чтоб задать или изменить свойства arguments в _chain.js предусмотрен метод _arguments( obj ). Функции цепочки могут вообще не пользоваться переменными массива args, переданного в _chain(func, args ) и использовать только объект this.arguments.
Для вызова следующей функции цепочки нужно воспользоваться методом this.next( obj ). Здесь obj – опциональный объект, расширяющий this.arguments свойствами obj для всех последующих функций цепочки.
var elem0 = $('#elem0'); var test0 = {}; var chain0 = $$._arguments({ obj : elem0 }) ._chain( jumpRight ) ._chain( jumpTop ) ._chain( jumpRight ) ._chain( jumpBottom ) ._chain( jumpLeft ) ._chain( jumpTop ) ._chain( jumpLeft ) ._chain( jumpBottom ); $('#test0_run').bind('click', function(){ if (! test0.running ) test0 = chain0._run(); });
Для того, чтоб предотвратить одновременное выполнение одной и той же цепочки несколько раз, можно воспользоваться свойством running, которое есть у объекта, возвращаемого методом _run() и которое равно true пока цепочка не закончит работу. Именно поэтому в большинстве примеров этой статьи вы встретите конструкции вида:
if (! test.running ) test = chain._run();
Объединение цепочек
Опишем пару цепочек и попробуем их объединить.
var test1_1Obj = $('#elem1'); var test1_2Obj = $('#elem1_1'); var chain1_1Obj = {}; var chain1_1 = $$._chain( jumpTop ) ._chain( getBigger ) ._chain( jumpRight ) ._chain( getSmaller ) ._chain( jumpBottom ) ._chain( jumpLeft ); $('#test1_1_run').bind('click', function(){ if ( (! chain1_1Obj.running) && (! chain1_3Obj.running ) ) chain1_1Obj = $$._arguments({ obj : test1_1Obj })._chain( chain1_1 )._run(); }); var chain1_2 = $$._chain( jumpRight ) ._chain( jumpTop ) ._chain( getBigger ) ._chain( jumpRight ) ._chain( getSmaller ) ._chain( jumpBottom ) ._chain( jumpLeft ) ._chain( jumpLeft ); $('#test1_2_run').bind('click', function(){ if ( (! chain1_1Obj.running) && (! chain1_3Obj.running ) ) chain1_1Obj = $$._arguments({ obj : test1_1Obj })._chain( chain1_2 )._run(); }); var chain1_2Obj = {}; var chain1_3 = $$._arguments({ obj : test1_2Obj }) ._chain( chain1_1 ) ._chain( chain1_2 ); $('#test1_3_run').bind('click', function(){ if ( (! chain1_2Obj.running ) && (! chain1_3Obj.running ) ) chain1_2Obj = chain1_3._run(); }); var chain1_3Obj = {}; var chain1_4 = $$._arguments({ obj : test1_2Obj }) ._chain( chain1_1 ) ._arguments({ obj : test1_1Obj }) ._chain( chain1_2 ); $('#test1_4_run').bind('click', function(){ if ( (! chain1_1Obj.running ) && (! chain1_2Obj.running ) && (! chain1_3Obj.running ) ) chain1_3Obj = chain1_4._run(); });
Цепочка1 – цепочка функций, заставляющая двигаться синий квадратик.
Цепочка2 – другая последовательность действий, применённая к синему квадратику.
Цепочка3 – последовательное выполнение сначала первой, потом второй цепочки, применяется уже к красному квадратику.
Цепочка4 – применение Цепочки1 к красному квадратику, затем Цепочки2 к синему.
Добавление элементов в работаущую цепочку
В данном случае квадратик прыгнет ровно столько раз, сколько мы ему скажем (кликнем). Если цепочка не запущена – запускаем её, если же запущена – добавляем в очередь на выполнение ещё одну итерацию прыгания.
var test2Obj = $('#elem2'), chain2Obj = {}, chain2 = $$._arguments({ obj : test2Obj }); $('#test2_run').bind('click', function(){ if (! chain2Obj.running){ chain2Obj = chain2._chain( jumpTop )._chain( jumpBottom )._run(); } else { chain2Obj = chain2Obj._chain( jumpTop )._chain( jumpBottom ); } });
Передача параметров в функции цепочки
Как мы уже говорили, в звено цепочки можно передать массив параметров, которые функция получит на входе. В следующем примере при описании цепочки передадим имя css класса в функцию, которая добавит нашему скачашему объекту этот класс, и добавит его в arguments.classToRemove, для того, чтоб последняя функция цепочки убрала из объекта этот css класс.
var test3Obj = $('#elem3'), chain3Obj = {}, chain3 = $$._arguments({ obj : test3Obj }) ._chain( jumpTop ) ._chain( jumpRight ) ._chain( function( className ){ this.arguments.obj.addClass( className ); this.next({ classToRemove : className }); }, [ 'clone_obj' ]) ._chain( jumpBottom ) ._chain( jumpLeft ) ._chain( function(){ this.arguments.obj.removeClass( this.arguments.classToRemove ); this.next(); }); $('#test3_run').bind('click', function(){ alert(chain3Obj.running) if (! chain3Obj.running) chain3Obj = chain3._run(); });
Никогда не пренебрегайте вызовом this.next() в функциях цепочки, даже если вы точно знаете, что данная функция – последнее звено.
Примеры из жизни
Мы столкнулись с необходимостью использовать CPS когда начали заниматься разработкой AJAX сайтов.
Нарастающее количество действий, которое нужно было выполнить с помощью js, требовало от нас всё больше концентрации. Допустим, находясь в разделе «компания»->«новости»->«теперь мы торгуем бананами!» пользователь нажал на контентную ссылку «купите банан»,
ведущую на пункт меню «продукция»->«бананы». Какие при этом действия должен выполнить js?
- Скрыть контент и показать слой загрузки;
- Сделать неактивным пункт меню «новости» и закрыть подпункты пункта «компания»;
- Послать на сервер AJAX запрос страницы /production/banana/ и дождаться ответа;
- Сделать активным пункт меню «продукция», открыть его подпункты, cделать активным подпункт «бананы»;
- Заменить url в адресной строке с www.somecompany.ru/#company/news/we-trade-bananas/ на www.somecompany.ru/#production/banana/;
- Скрыть слой загрузки и отобразить контент страницы «купите банан»;
При этом выпонление пунктов 1, 2, 3, 4, 6 отсрочено во времени, т.к. 1, 2, 4, 6 – анимация, а 3 – AJAX запрос. К тому же результат выполнения 3 очень нужен в 6, поэтому просто вызвать все эти функции в строчку не получится. С помощью _chain.js вызов приведённой выше цепочки выглядит примерно следующим образом:
$$._chain( $p._hideContent ) ._chain( $p._hideCurrentMenuLevels ) ._chain( $p._askContent, newPageUrl ) ._chain( $p._showNewMenuLevels ) ._chain( $p._changePageUrl, newPageUrl ) ._chain( $p._showContent ) ._run();
Кстати, впервые мы упомянули эту проблему и концепцию CPS в нашей статье «ActionWeb. Асинхронный интернет».
Итак, примеры из жизни:
www.akomarov.com
www.stroydelo-kuban.ru
www.viprieltyug.ru
www.rudent.info
www.nikitaeremin.com
Заключение
Надеемся _chain.js найдёт своего пользователя. В следующей статье мы расскажем о том, как менять цепочку по ходу её выполнения. И да пребудет с вами Сила!
