Development

/plugins/sfUJSPlugin/README

You must first sign up to be able to contribute.

root/plugins/sfUJSPlugin/README

Revision 4061, 18.4 kB (checked in by francois, 6 years ago)

sfUJSPlugin: Fixed XHTML compliance for dynamic inclusion (closes #1763)

Line 
1 = Unobtrusive JavaScript Plug-In =
2
3 The `sfUJSPlugin` offers helpers that facilitate the creation of interactive effects with JavaScript in an unobtrusive way.
4
5 If you want to learn more about the unobtrusive approach to JavaScript, and how you can use JavaScript to enhance usability without sacrificing accessibility, read [http://onlinetools.org/articles/unobtrusivejavascript/index.html this article].
6
7 ''Warning'': This plugin is in Alpha state. Syntax is subject to change.
8
9 == Introduction ==
10
11 Doing unobtrusive JavaScript can be a pain for several reasons:
12
13   * You need to traverse the DOM after it's been built to modify an element's style/behaviour. If the element that you want to modify doesn't have an `id`, you have to refer to it using complicated CSS selectors/XPath queries, hard to read and to maintain when the template changes.
14   * The behaviours and the structure appear separated from each other in the code, and that makes debugging difficult. Templates and JavaScript files are far from each other in the symfony directory structure. In particular, a template with a lot of unobtrusive visual effects becomes unmaintainable because of the constant switch between the XHTML and the JS file.
15   * The 'rails-like' syntax of Ajax helpers is quite adapted to Rapid Application Development, because the code is concise and readable. UJS is most of the time longer to write, since you need to declare a JavaScript block in the template, and you must first find the DOM element you want to modify.
16
17 All these lead to an alternative way to code UJS, using PHP helpers in the template. The `sfUJSPlugin` can be used to add interactive effects and Ajax calls to your pages, and is an alternative to the [http://www.symfony-project.com/book/trunk/11-Ajax-Integration symfony Javascript helpers]. This implementation uses [http://jquery.com/ JQuery] as the underlying JavaScript framework, but the same could be achieved with [http://www.prototypejs.org/ Prototype].
18
19 The UJS implementation of this plugin uses the [wiki:sfPJSPlugin], which provides a way to call dynamically generated JavaScript files. The benefit of UJS over PJS alone is that you can write JavaScript through helpers directly in the main template and avoid switching back and forth to a behaviour template.
20
21 == Installation ==
22
23   * Install the plugin
24  
25   {{{
26   $ symfony plugin-install http://plugins.symfony-project.com/sfUJSPlugin
27   }}}
28  
29   * Alternatively, if you don't have PEAR installed, you can download the latest package attached to this plugin's wiki page and extract it under your project's `plugins/` directory
30  
31   * If it is not installed yet, install the [wiki:sfPJSPlugin], which is necessary to run this plugin.
32
33   * Enable the `sfUJS` module in the `settings.yml`:
34    
35 {{{
36 all:
37   .settings:
38     enabled_modules:        [default, sfUJS]
39 }}}
40
41   * Activate the `sfUJSFilter` in the `apps/myapp/filters.yml`:
42
43 {{{
44 rendering: ~
45 web_debug: ~
46 security:  ~
47
48 # generally, you will want to insert your own filters here
49
50 cache:     ~
51 common:    ~
52
53 UJS:
54   class: sfUJSFilter
55
56 flash:     ~
57 execution: ~
58 }}}
59
60   * Optional: You can choose to use the UJS alternative to the `Tag` helper so that every mention of an event handler (such as `onclick`) in a symfony helper gets rendered in an unobtrusive way. This is done by copying the `sfUJSPlugin/lib/helper/UJSTagHelper.php` file into `myapp/lib/helper/TagHelper.php` and clearing the cache (see below)
61
62   * Clear the cache to enable the autoloading to find the new classes
63  
64   {{{
65   $ symfony cc
66   }}}
67
68 == Basic syntax ==
69
70 === Declaring the helper in templates ===
71
72 The UJS plugin provides a set of helpers to be used within templates. As for other helpers, you must declare the related helper group to make the helper functions available in the template.
73 {{{
74 <?php use_helper('UJS') ?>
75 }}}
76
77 === Adding JavaScript code unobtrusively ===
78
79 The following template code:
80 {{{
81 <div id="foobar">
82   I'm here !
83 </div>
84 <?php UJS("$('#foobar').css('display', 'none')") ?>
85 }}}
86
87 Renders as follows:
88 {{{
89 <head>
90   <script type="text/javascript" src="/UJS/script/key/bc8b3812f3d7a20f7ed7c1ab25ec449a.php"></script>
91 </head>
92 <body>
93   <div id="foobar">
94     I'm here !
95   </div>
96 </body>
97 }}}
98
99 The filter contained in the plugin automatically declares the use of a special JavaScript file, which is generated dynamically by the `UJS/script` action. This action packages all the code included by calls to the `UJS()` helper. In the previous example, the included JavaScript is:
100 {{{
101 $().ready(function(){
102     $('#foobar').css('display', 'none');
103  })
104 }}}
105
106 The resulting DOM after execution is:
107 {{{
108 <div id="foobar" style="display:none">
109   I'm here !
110 </div>
111 }}}
112
113 Alternatively, you can write the Javascript code between two helper calls, to avoid complicated quotes escaping problems.
114 {{{
115 <?php UJS_block() ?>
116 $('#foobar').css('display', 'none');
117 <?php UJS_end_block() ?>
118 // same as
119 <?php UJS("$('#foobar').css('display', 'none')") ?>
120 }}}
121
122 === Static vs dynamic UJS inclusion ===
123
124 By default, all code declared with UJS helpers ends up in an attached file. That's the way the plugin achieves complete unobtrusiveness: there is absolutely no JavaScript code in the XHTML response, and the behaviour layer is clearly separated from the content layer. This also allows for caching of the behaviour code by the browsers, just like CSS files are cached to avoid reloading style declarations. As compared to embedded code, UJS code saves server bandwith and accelerates the display on the client side.
125
126 This works fine as long as the UJS code is static. But on some special cases, the UJS code depends on the user session or on the database, and in this case separating the UJS code from the reponse creates issues due to caching. That's why the helper provides a way to directly embed UJS code in the XHTML instead of getting it via the `UJS/script` action.
127
128 There are three ways to enable this 'non-static' behaviour:
129
130  - Set the `static` response parameter to `false` in the `symfony/view/UJS` namespace. This is mostly useful for when you want to define this behaviour in an actions file:
131 {{{
132 sfContext::getInstance()->getResponse()->setParameter('static', false, 'symfony/view/UJS');
133 }}}
134
135  - Call the `UJS_set_inclusion(false)` helper at the end of a template to declare that the UJS code of the template is not static:
136 {{{
137 <?php UJS_set_inclusion(false) ?>
138 }}}
139
140  - Alternately, you can define that the default inclusion method for all templates is to embed instead of attach via the `app.yml` file:
141 {{{
142 all:
143   UJSPlugin:
144     static: false
145 }}}   
146
147 Once the UJS code is declared non-static, the UJS plugin filter will no longer include the `UJS/script` action as an external script, but rather look for a `</body>` tag and insert a `<script>` content just before. So for instance, the following template code:
148 {{{
149 <div id="foobar">
150   I'm here !
151 </div>
152 <?php UJS("$('#foobar').css('display', 'none')") ?>
153 <?php UJS_set_inclusion(false) ?>
154 }}}
155
156 Renders as follows:
157 {{{
158 <div id="foobar">
159   I'm here !
160 </div>
161 ...
162 <script>
163 //  <![CDATA[
164 $().ready(function(){
165    $('#foobar').css('display', 'none');
166  })
167 //  ]]>
168 </script>
169 </body>
170 }}}
171
172 If you prefer that the UJS code appears somewhere else in the document, all the `include_UJS()` helper somewhere in your template/layout, and the plugin will use this placeholder instead of looking for a `</body>` for script inclusion.
173
174 == UJS Helpers documentation ==
175
176 The `UJS()` helper is just one of a bunch of helpers proposed by the `UJS` helper group to facilitate the writing of UJS code with PHP. The other helpers of the plugin, for which you will find complete syntax and examples below, are:
177
178  - `UJS_add_behaviour($selector, $event, $code)`
179  - `UJS_change attributes($selector, $attributes)`
180  - `UJS_change style($selector, $style_attributes)`
181  - `UJS_write($code)`
182  - `UJS_link_to_function($text, $code)`
183  - `UJS_button_to_function($text, $code)`
184  - `UJS_ajaxify_link($selector, $ajax_options)`
185  - `UJS_ajaxify_form($selector, $ajax_options)`
186  - `UJS_ajaxify($selector, $ajax_options)`
187
188 === Automatic iteration with jQuery ===
189
190 UJS uses jQuery to translate the helpers into JavaScript. This means that the code you write can also use JQuery, since the library is automatically included by the helper functions.
191
192 One of the great advantages of jQuery is that when a selector returns more than one DOM element, calling a method on the result of the selector automatically iterates over the results. So for instance,
193
194 {{{
195  $('p').css('color', 'red');
196 }}}
197
198 ...will loop over all `<p>` elements of the document and change their `color` CSS attribute to `red`.
199
200 The UJSPlugin helpers support the same automatic iteration feature, so all the helpers expecting a selector as first parameter can work with a selector returning one or more results.
201
202 === Adding a behaviour unobtrusively ===
203
204 The following template code:
205
206 {{{
207 <div id="foobar">
208   click me
209 </div>
210 <?php UJS_add_behaviour('#foobar', 'click', "alert('foobar')") ?>
211 }}}
212
213 Renders as follows:
214
215 {{{
216 <div id="foobar">
217   click me
218 </div>
219
220 // in linked UJS/script
221 $().ready(function(){
222   $('#foobar').click(function() { alert('foobar') });
223 }
224 }}}
225
226 And the resulting DOM after execution is:
227
228 {{{
229 <div id="foobar" onclick="alert('foobar')">
230   click me
231 </div>
232 }}}
233
234 === Modifying an element unobtrusively ===
235
236 The following template code:
237
238 {{{
239 <div id="foobar">
240   I'm here !
241 </div>
242 <?php UJS_change_attributes('#foobar', 'style=color:yellow class=foo') ?>
243 <?php UJS_change_style('#foobar', 'text-decoration:underline') ?>
244 }}}
245
246 Renders as follows:
247
248 {{{
249 <div id="foobar">
250   I'm here !
251 </div>
252
253 // in linked UJS/script
254 $().ready(function(){
255     $('#foobar').attr('style', 'color:yellow').attr('class', 'foo');
256     $('#foobar').css('text-decoration', 'underline');
257  })
258 }}}
259
260 And the resulting DOM after execution is:
261
262 {{{
263 <div id="foobar" style="color:yellow; text-decoration:underline" class="foo">
264   I'm here !
265 </div>
266 }}}
267
268 === Adding some content unobtrusively ===
269
270 The following template code:
271
272 {{{
273 <?php UJS_write('<a href="#" onclick="$(\'#foobar\').toggle();return false;">click me</a>') ?>
274 <div id="foobar">
275   I'm here !
276 </div>
277 <?php UJS_change_style('#foobar', 'display:none') ?>
278 }}}
279
280 Renders as follows:
281
282 {{{
283 <span style="display: none" class="UJS_placeholder" id="UJS_0"></span>
284 <div id="foobar">
285   I'm here !
286 </div>
287
288 // in linked UJS/script
289 $().ready(function(){
290     $('#UJS_0').after('<a href="#" onclick="$(\'#foobar\').toggle();return false;">click me</a>');
291     $('#UJS_0').remove();
292     $('#foobar').css('display', 'none');
293  })
294 }}}
295
296 And the resulting DOM after execution is:
297
298 {{{
299 <a href="#" onclick="$('#foobar').toggle();return false;">click me</a>
300 <div id="foobar" style="display:none"">
301   I'm here !
302 </div>
303 }}}
304
305 Alternatively, you can write the HTML code between two helper calls, to avoid complicated quotes escaping problems.
306
307 {{{
308 <?php UJS_write_block() ?>
309 <a href="#" onclick="$('#foobar').toggle();return false;">click me</a>
310 <?php UJS_end_write_block() ?>
311 // same as
312 <?php UJS_write('<a href="#" onclick="$(\'#foobar\').toggle();return false;">click me</a>') ?>
313 }}}
314
315 == UJS started by a click ==
316
317 The most common JavaScript effects are triggered by a user click, either on a link or on a button. Just like the regular JavaScript helpers, the UJS helper group provides two helpers just for that purpose:
318
319 {{{
320 <?php echo UJS_link_to_function('click me', "alert('foobar')") ?>
321 <?php echo UJS_button_to_function('and also click me', "alert('foobarbaz')") ?>
322 }}}
323
324 These two lines render as follows:
325
326 {{{
327 <span style="display: none" class="UJS_placeholder" id="UJS_0"></span>
328 <span style="display: none" class="UJS_placeholder" id="UJS_1"></span>
329
330 // in linked UJS/script
331 $().ready(function(){
332 $('#UJS_0').after('<a href="#" onclick="alert(\'foobar\'); return false;">click me</a>');$('#UJS_0').remove();
333 $('#UJS_1').after('<input type="button" value="and also click me" onclick="alert(\'foobarbaz\')" />');$('#UJS_1').remove();
334 })
335 }}}
336
337 And the resulting DOM after execution is:
338
339 {{{
340 <a onclick="alert('foobar'); return false;" href="#">click me</a>
341 <input type="button" onclick="alert('foobarbaz')" value="and also click me"/>
342 }}}
343
344 === UJS remote update started by a click (Ajax) ===
345
346 The plugin provides helpers to modify an existing link, button or form to make it Ajax, so that clicking/submitting it will issue a remote call and update a DOM element with the response.
347
348 The following template code:
349
350 {{{
351 <div id="ajax_feedback">
352   ajax feedback zone
353 </div>
354 <?php echo link_to('click me', 'foo/bar', 'id=1234') ?>
355 <?php UJS_ajaxify_link('#1234', array(
356   'update' => '#ajax_feedback',
357   'url' => 'foo/ajaxbar',
358 )) ?>
359 }}}
360
361 Renders as follows:
362
363 {{{
364 <div id="ajax_feedback">
365   ajax feedback zone
366 </div>
367 <a href="/foo/bar" id="1234">click me</a>
368
369 // in linked UJS/script
370 $().ready(function(){
371   $('#1234').click(function() { $.ajax({url: '/foo/ajaxbar', success: function(response) { $('#ajax_feedback').html(response) }}); return false });
372 })
373 }}}
374
375 The syntax is the same for all the UJS Ajax helpers. `UJS_ajaxify_link()` (to ajaxify a link), `UJS_ajaxify_form()` (to ajaxify a form) and `UJS_ajaxify()` (to ajaxify whatever element) all expect two parameters: a CSS3 selector of the element(s) to ajaxify, and an associative array of Ajax options. The possible Ajax options are:
376
377   - `url`: Internal URI of the action called remotely
378   - `update`: Selector of the element(s) to update with the remote response
379   - `position`: If specified, the remote response will not replace the content of the `update` element, but rather complement it. Possible values are `before`, `top`, `bottom`, and `after`.
380   - You can specify code to be executed at various steps of the Ajax request execution via the callback options:
381     - `beforeSend`
382     - `success`
383     - `error`
384     - `complete`
385   - `confirm`: Adds a confirmation dialog.
386   - `condition`: Perform remote request conditionally by this expression. Use this to describe browser-side conditions when request should not be initiated.
387
388 == Replacement for usual helpers ==
389
390 === Catching all event handlers in helper calls ===
391
392 Symfony already contains quite a lot of helpers outputting HTML elements. All these helpers support setting additional attributes, including event handlers. For instance, the `link_to()` helper transforms this call:
393
394 {{{<?php echo link_to('click me', 'foo/bar', 'onclick=alert("you clicked me!")') ?>}}}
395
396 Into:
397
398 {{{<a href="/foo/bar" onclick="alert(\"you clicked me!\")">click me</a>}}}
399
400 This is obtrusive code, for sure, but a damn fast way to define event handlers. What if symfony coud do the translation for you, so that every event handler defined in a symfony helper gets rendered in an unobtrusive way? That's exactly what the `UJSTag` helper group does.
401
402 To enable this helper, copy the `lib/helper/UJSTagHelper.php` file bundled in this plugin into your application's `lib/helper/` directory, and rename it to `TagHelper.php`.
403
404 Now, every mention of an event handler in a symfony helper call will be catched and transformed into an UJS behaviour. So the previous helper call:
405
406 {{{<?php echo link_to('click me', 'foo/bar', 'onclick=alert(\'you clicked me!\')') ?>}}}
407
408 Will be transformed into:
409
410 {{{
411 <a id="UJS_0" href="/foo/bar">click me</a>
412
413 // in linked UJS/script
414 $().ready(function(){
415   $('#UJS_0').click( function() { alert('you clicked me!') } );
416 })
417 }}}
418
419 === UJS for dummies ===
420
421 So, you have always been using the classic 'Javascript' helper group, you don't want to learn a new syntax, but you want unobtrusiveness? You don't really deserve it, but hey, it's possible. This plugin comes with an alternative to the 'Javascript' helper group that uses the same syntax, but outputs unobtrusive code instead of obtrusive one.
422
423 To enable this helper, copy the `lib/helper/UJSJavascriptHelper.php` file bundled in this plugin into your application's `lib/helper/` directory, and rename it to `JavascriptHelper.php`. From then on, symfony will use this file when you call `<?php use_helper('Javascript') ?>` instead of the one package with the framework.
424
425 Use the helpers as you always did:
426
427 {{{
428 <?php use_helper('UJavascript') ?>
429 <div id="ajax_feedback">
430   ajax feedback zone
431 </div>
432 <?php link_to_remote('click me for Ajax', array(
433   'update' => 'ajax_feedback',
434   'url' => 'test/ajax',
435 )) ?>
436 }}}
437
438 Renders as follows:
439
440 {{{
441 <div id="ajax_feedback">
442   ajax feedback zone
443 </div>
444 <span style="display: none" class="UJS_placeholder" id="UJS_0"></span> 
445
446 // in linked UJS/script
447 $().ready(function(){
448   $('#UJS_0').after('<a href="#" onclick="jQuery.ajax({url: \'/frontend_dev.php/test/ajax\', success: function(response) { $(\'#ajax_feedback\').html(response) }}); return false;">click me for Ajax</a>');$('#UJS_0').remove();
449 })
450 }}}
451
452 Helpers implemented in the `UJavascript` helper group are:
453
454  - `link_to_remote()`
455  - `link_to_function()`
456  - `button_to_remote()`
457  - `button_to_function()`
458
459 '''Warning''': Although this is very practical, using this helper is not advised. Unobtrusiveness is not just a post-processing, it's a way to design interactions. You will never achieve true unobtrusiveness unless you drop the habits taken with the Javascript helpers, and this means stop using the functions listed ahead.
460
461 '''Warning''': Due to a bug in `sfWebDebug` ([http://www.symfony-project.com/trac/ticket/1548 #1548]), this method will work in production but not in the development environment.
462
463 == Todo ==
464
465   * Server-side and browser-side caching of unobtrusive script file
466   * More unit tests
467   * Visual effects
468   * Complex interactions
469   * Prototype implementation
470   * add sfPJSPlugin as a PEAR dependency
471
472 == Changelog ==
473
474 === Trunk ===
475
476   * francois: Made dynamic inclusion XHTML compliant (thanks xavier)
477
478 === 2007-05-20 | 0.6.0 Beta ===
479
480   * francois: Added unit tests (uses jQuery's testing framework, slightly modified)
481   * francois: fixed a bug in `UJS_change_style()`. Now recognizes combined style modifications
482   * francois: '''BC break''' Renamed module `UJS` to `sfUJS`
483
484 === 2007-04-27 | 0.5.2 Alpha ===
485
486   * francois: Now uses sfPJSPlugin to build dynamic JavaScript file
487   * francois: Fixed problem with static mode when URL_REWRITING is turned on
488   * francois: Fixed compatibility with flash variables
489
490 === 2007-03-07 | 0.5.1 Alpha ===
491
492   * francois: added the `UJavascript` helper group as an alternative to the normal `Javascript` helper group
493   * francois: added `UJS_ajaxify` helpers
494   * francois: Replaced '$' with 'jQuery' in generated JS code to allow compatibility with Prototype
495   * francois: Refactored documentation
496   * francois: Made UJS code inclusion static by default (i.e packed into a single .js file called in the template)
497   * francois: Fixed a bug in `UJSTagHelper` when `UJS` helper was not loaded
498
499 === 2007-02-27 | 0.5.0 Alpha ===
500
501   * francois: Initial release
Note: See TracBrowser for help on using the browser.