Improve this Doc

JavaScript PHP Chains

Chains are the glue between your JavaScript API and PHP. Chains are not meant to replace your JavaScript completely, but they certainly make it much easier to bind events between PHP and JavaScript.

Agile Toolkit by default integrates with jQuery, however Chains can also be used for accessing other JavaScript frameworks such as Google Map API.

Creating Chains

class jQuery_Chain

Translate PHP code into JavaScript.

A PHP JavaScript chain is a special object which will translate all it’s method calls into JavaScript method calls. Object relies on __call feature of PHP.

Any non-object properties passed to the methods will automatically be converted into JSON:

$js->hello('world')->enable(true)->test([ 'foo'=>'bar' ]);

Results in:

js.hello('world').enable(true).test({foo: 'bar'});

Normally chains are connected with a jQuery selector through event:

$form->js('click')
    ->find('input')
    ->css([ 'background' => 'red' ]);

Result:

jQuery('#id-of-form').find('input').css({background: 'red'});

Selector

In jQuery, you need to use $(‘selector’) to specify where to start. Agile Toolkit automatically starts with the $view of your choice. Every View object has a js() method which will create a new chain, using that object’s selector. To add JS code for hiding any object on your page, such as a grid, all you need to write is:

$grid->js(true)->hide();

Grid will automatically be hidden when loaded.

Arguments

jQuery_Chain objects convert all method calls into respective jQuery code. The native PHP arguments are automatically converted into JavaScript. Native types such as integers and strings are properly quoted and converted into JavaScript.

If you specify array() then the chain will try to determine if it’s an array or a hash. Hash is converted into {key:value,..} and PHP arrays are converted into [‘a’,’b’,’c’] in JavaScript. If you use one chain as an argument to a method call on another chain, then that chain would be converted into JavaScript code first and then inserted appropriately:

$x = 2+2;
$js = $page->js(true)
    ->css(array('width'=>$x))
    ->html('<b>O\'Relly</b>');

// Produces:
// $('#my_page_view)
//    .css({'width':4}).html('\x3cb\x3eO\x27Relly\x3c/b\x3e')

Tip

Always remember that operations cannot be used in arguments. You can set one field value to be copy of another field, but you can’t concatenate two fields using operation:

Example:

$field->js(true)->val(123);
// $('#field_id').val(123);


$field->js(true)->val( $field2->js()->val() );
// $('#field_id').val( $('#field2_id').val() )

$field->js(true)->val( $field2->js()->val() + $field3->js()->val() );
// WRONG. Use String.concat() instead.

$field->js(true)->val( $field2->js()->val()->concat( $field3->js()->val() ) ) ;
// $('#field_id').val( $('#field2_id').val().concat( $('#field3_id').val() ) )

Binding Chains

When you execute the js() method, the first argument you specify defines when the action is to take place. The example below demonstrates how you can bind chains to different events:

$page->add('H3')->set('never')
    ->js()->effect('highlight');

$page->add('H3')->set('load')
    ->js(true)->effect('highlight');

$page->add('H3')->set('click')
    ->js('click')->effect('highlight');

$page->add('H3')->set('mouseover')
    ->js('mouseover')->effect('highlight');

ReBinding

You can bind one single chain to multiple events. The example below binds the same chain to both buttons:

$b1=$page->add('Button')->set('Left');
$b2=$page->add('Button')->set('Right');

$chain=$b1->js('click')->effect('highlight');
$b2->js('click',$chain);

This combination allows you to enable many useful actions involving different components on your page. In the form below, the icon’s “click” event and the age field’s “change” event have binding chains interacting other objects:

$form= $page->add('Form');

$age=  $form->addField('line','age');
$name= $form->addField('line','name');
$res=  $form->addField('line','result');

$age->addIcon('plus')
    ->js('click',$age->js()->val('123'));

$name->js('change',$form->js()->submit());

$form->onSubmit(function($form) use($res) {
    if($form['age']<20){
        return $res->js()->val('Credit Declined');
    }else{
        return $res->js()->val('Credit Approved');
    }
}

Enclosing Chains

While it seems quite simple on the PHP side, the code generated by chain is different depending on the event you bind it with. For example, when you leave the argument to js() blank (null), then the chain code will not appear in your page source at all. If you specify “true”, then it’s executed immediately. If you specify any action, however, then it’s enclosed into a function and bind()’ed to the element of your choice:

$js = $page->js()->hide();
// Produces nothing

$js = $page->js(true)->hide();
// Produces:
// $('#view_id').hide();

$js = $page->js('click')->hide();
// Produces:
// $('#view_id').bind('click',
//    function(ev){ ev.preventDefault();ev.stopPropagation();
//       $('#view_id').hide()
//  });

Agile Toolkit automatically wraps the action into a function. Sometimes you would want to wrap chain into function yourself:

jQuery_Chain::_enclose()

Instead of using chain as a value within an argument, wrap it into a function.

Enclosing allows you to specify one chain as a callback in another chain:

$data=[
  ['name'=>'John','surname'=>'Smith'.rand(1,20)],
  ['name'=>'Peter','surname'=>'Tester'.rand(20,40)],
  ['name'=>'Joe CLICK ME','surname'=>'Blogs'.rand(1,20)],
  ['name'=>'Bill','surname'=>'Chill'.rand(20,40)],
];

$grid = $page->add('Grid');
$grid->addColumn('name');
$grid->addColumn('surname');
$grid->setSource($data);

$grid->js(true)->find('tbody tr')->eq(2)->find('td')->click(
    $grid->js()->effect('bounce')->_enclose()
);

// Produces:
// $('#grid_id').find('tbody tr').eq(2).find('tr').click(
//    function() {
//      $('#grid_id').effect('bounce');
//    }
// )

Clicking on Joe Blogs’s name or surname will make the grid bounce. Without _enclose(), however, the bouncy java-script chain would be executed as an argument and not encapsulated as a function for the event.

You will notice that calling _enclose() does not add preventDefault and stopPropagation. If you want them, use enclose(true, true).

Combining Chains

So far you’ve seen how we can make chains, and then bind them to one - or multiple - triggers. What about binding multiple chains to a single trigger? That’s where the second argument to js() function is used.

The call syntax is js(trigger, prepend_js). As a second argument, you can pass either a JS chain, or an array of JS chains:

$js = [];

$js[]=$page->add('P')->set('Bounce')
    ->js()->effect('bounce');

$js[]=$page->add('P')->set('Highlight')
    ->js()->effect('highlight');

$page->add('Button')->set('Trigger Both')
    ->js('click',$js);

Alternatively you can also bind the same action several times. This syntax can be used when actions are applied by controllers, or from multiple objects:

$js1=$page->add('P')->set('Bounce')
    ->js()->effect('bounce');

$js2=$page->add('P')->set('Highlight')
    ->js()->effect('highlight');

$b=$page->add('Button')->set('Trigger Both');

$b->js('click',$js1);
$b->js('click',$js2);

Although I’d use a different postfixes for click events:

$b->js('click.tr1',$js1);
$b->js('click.tr2',$js2);

This allows you to unbind them separatelly.

Loading JavaScript dependencies

Agile Tookit maintains a certain type of universal approach where any page can be either loaded directly in the browser or portion of that page can be loaded through dialog or atk_load().

To load files you define them as dependencies within PHP code:

$this->js()->_load('jquery.someplugin');

Note that you do not need to specify event for such a chain. As soon as jQuery sees this, it will dynamically add <script> tag.

Some of the complex JavaScript libraries may fail to load dynamically:

$this->app->js->addInclude('jquery.someplugin');

Tip

If page A loads dialog with page B, hten static inlcudes for javascript code on page B must be declared on page A (or better yet, both pages). While _load() will properly dependency, addInclude will fail to do so. It relies on <head> of the page being outputed, but it’s not sent on dynamic page loads. See also Cutting Output.

Executing your own code

There is a way to output UNSAFE JS code from PHP as a last resort, but before that you should learn how to do it properly.

jQUery Plugin / widget

  1. Create your own jQuery plugin or jQuery UI widget.
  2. Load with ->_load(‘your_js_file’);
  3. Call it with js()->your_plugin(‘action’);

Creating your own lib

  1. Create your own JavaScript lib (not compatible with jQuery)
  2. Determine how it’s called, e.g. MyLib.action(123)
  3. Create chain: js()->_library('MyLib')->action(123);

Using UNIV chain

  1. Create new file.js with:

    jQuery.each({
        test: function(){ alert("Yes, it works!"); }
        // more functions here
    }, jQuery.univ._import);
    
  2. Include your chain with $this->js()->_load('file');

  3. Execute your method with: js()->univ()->test();

Ugly insecure JavaScript inclusion

If everything else failed, you can output chunk of JavaScript code like this:

$this->js(true, 'alert(2+2)');

If you use this, always consider output sanitization of your JS code.

Customizing Selectors

By default selector is determined by calling AbstractView::getJSID, however you can specify a different selector for your chain.

jQuery_Chain::_selector()

Specify a different selector for chain.

jQuery_Chain::_selectorThis()

Use “this” as a selector.

jQuery_Chain::_selectorDocument()
jQuery_Chain::_selectorWindow()
jQuery_Chain::_selectorRegion()

Calling methods you can’t call

If you want to call a method, which already happens to be defined in PHP, such as init(), you can use _fn wrapper:

$view->js(true)->_fn('init', [ 1 ]);
// converts into .init(1)