` and the `
`. With this method, you can do several Ajax calls and see the responses accumulate after the `update` element.
The `position` parameter can take the following values:
* `before`: Before the element
* `after`: After the element
* `top`: At the top of the content of the element
* `bottom`: At the bottom of the content of the element
### Updating an Element According to a Condition
A remote call can take an additional parameter to allow confirmation by the user before actually submitting the `XMLHttpRequest`, as shown in Listing 11-18.
Listing 11-18 - Using the Confirm Parameter to Ask for a Confirmation Before Calling the Remote Function
[php]
'feedback',
'url' => 'post/delete?id='.$post->getId(),
'confirm' => 'Are you sure?',
)) ?>
A JavaScript dialog box showing "Are you sure?" will pop up when the user clicks the link, and the `post/delete` action will be called only if the user confirms his choice by clicking OK.
The remote call can also be conditioned by a test performed on the browser side (in JavaScript), if you provide a `condition` parameter, as shown in Listing 11-19.
Listing 11-19 - Conditionally Calling the Remote Function According to a Test on the Client Side
[php]
'feedback',
'url' => 'post/delete?id='.$post->getId(),
'condition' => "$('elementID') == true",
)) ?>
### Determining the Ajax Request Method
By default, Ajax requests are made with the POST method. If you want to make an Ajax call that doesn't modify data, or if you want to display a form that has built-in validation as the result of an Ajax call, you might need to change the Ajax request method to GET. The `method` option alters the Ajax request method, as shown in Listing 11-20.
Listing 11-20 - Changing the Ajax Request Method
[php]
'feedback',
'url' => 'post/delete?id='.$post->getId(),
'method' => 'get',
)) ?>
### Authorizing Script Execution
If the response code of the Ajax call (the code sent by the server, inserted in the `update` element) contains JavaScript, you might be surprised to see that these scripts are not executed by default. This is to reduce remote attack risks and to allow script execution only when the developer knows for sure what code is in the response.
That's why you need to declare explicitly the ability to execute scripts in remote responses, with the `script` option. Listing 11-21 gives an example of an Ajax call declaring that JavaScript from the remote response can be executed.
Listing 11-21 - Authorizing Script Execution in the Ajax Response
[php]
'feedback',
'url' => 'post/delete?id='.$post->getId(),
'script' => true,
)) ?>
If the remote template contains Ajax helpers (such as `remote_function()`), be aware that these PHP functions generate JavaScript code, and they won't execute unless you add the `'script' => true` option.
>**NOTE**
>Even if you enable script execution for the remote response, you won't actually see the scripts in the remote code, if you use a tool to check the generated code. The scripts will execute but will not appear in the code. Although peculiar, this behavior is perfectly normal.
### Creating Callbacks
One important drawback of Ajax interactions is that they are invisible to the user until the zone to update is actually updated. This means that in cases of a slow network or server failure, users may believe that their action was taken into account, when it actually was not processed. This is why it is important to notify the user of the events of an Ajax interaction.
By default, each remote request is an asynchronous process during which various JavaScript callbacks can be triggered (for progress indicators and the like). All callbacks have access to the `request` object, which holds the underlying `XMLHttpRequest`. The callbacks correspond to the events of any Ajax interaction:
* `before`: Before request is initiated
* `after`: Immediately after request is initiated and before loading
* `loading`: When the remote response is being loaded by the browser
* `loaded`: When the browser has finished loading the remote response
* `interactive`: When the user can interact with the remote response, even though it has not finished loading
* `success`: When the `XMLHttpRequest` is completed, and the HTTP status code is in the 2XX range
* `failure`: When the `XMLHttpRequest` is completed, and the HTTP status code is not in the 2XX range
* `404`: When the request returns a 404 status
* `complete`: When the `XMLHttpRequest` is complete (fires after `success` or `failure`, if they are present)
For instance, it is very common to show a loading indicator when a remote call is initiated, and to hide it once the response is received. To achieve that, simply add `loading` and `complete` parameters to the Ajax call, as shown in Listing 11-22.
Listing 11-22 - Using Ajax Callbacks to Show and Hide an Activity Indicator
[php]
Loading...
'feedback',
'url' => 'post/delete?id='.$post->getId(),
'loading' => "Element.show('indicator')",
'complete' => "Element.hide('indicator')",
)) ?>
The show and hide methods, as well as the JavaScript Element object, are other useful additions of Prototype.
Creating Visual Effects
-----------------------
Symfony integrates the visual effects of the script.aculo.us library, to allow you to do more than show and hide `
` elements in your web pages. You will find good documentation on the effects syntax in the wiki at [http://script.aculo.us/](http://script.aculo.us/). Basically, the library provides JavaScript objects and functions that manipulate the DOM in order to achieve complex visual effects. See a few examples in Listing 11-23. Since the result is a visual animation of certain areas in a web page, it is recommended that you test the effects yourself to understand what they really do. The script.aculo.us website offers a gallery where you can get an idea of the dynamic effects.
Listing 11-23 - Visual Effects in JavaScript with Script.aculo.us
[php]
// Highlights the element 'my_field'
Effect.Highlight('my_field', { startcolor:'#ff99ff', endcolor:'#999999' })
// Blinds down an element
Effect.BlindDown('id_of_element');
// Fades away an element
Effect.Fade('id_of_element', { transition: Effect.Transitions.wobble })
Symfony encapsulates the JavaScript `Effect` object in a helper called `visual_effect()`, still part of the `Javascript` helper group. It outputs JavaScript that can be used in a regular link, as shown in Listing 11-24.
Listing 11-24 - Visual Effects in Templates with the `visual_effect()` Helper
[php]
I was here all along!
// Will make a call to Effect.Appear('secret_div')
The `visual_effects()` helper can also be used in the Ajax callbacks, as shown in Listing 11-25, which displays an activity indicator like Listing 11-22, but is visually more satisfactory. The `indicator` element appears progressively when the Ajax call starts, and it fades progressively when the response arrives. In addition, the feedback element is highlighted after being updated by the remote call, to draw the user's attention to this part of the window.
Listing 11-25 - Visual Effects in Ajax Callbacks
[php]
Loading...
'feedback',
'url' => 'post/delete?id='.$post->getId(),
'loading' => visual_effect('appear', 'indicator'),
'complete' => visual_effect('fade', 'indicator').
visual_effect('highlight', 'feedback'),
)) ?>
Notice how you can combine visual effects by concatenating them in a callback.
JSON
----
JavaScript Object Notation (JSON) is a lightweight data-interchange format. Basically, it is nothing more than a JavaScript hash (see an example in Listing 11-26) used to carry object information. But JSON has two great benefits for Ajax interactions: It is easy to read in JavaScript, and it can reduce the size of a web response.
Listing 11-26 - A Sample JSON Object in JavaScript
var myJsonData = {"menu": {
"id": "file",
"value": "File",
"popup": {
"menuitem": [
{"value": "New", "onclick": "CreateNewDoc()"},
{"value": "Open", "onclick": "OpenDoc()"},
{"value": "Close", "onclick": "CloseDoc()"}
]
}
}}
If an Ajax action needs to return structured data to the caller page for further JavaScript processing, JSON is a good format for the response. This is very useful if, for instance, one Ajax call is to update several elements in the caller page.
For instance, imagine a caller page that looks like Listing 11-27. It has two elements that may need to be updated. One remote helper could update only one of the elements of the page (either the `title` or the `name`), but not both.
Listing 11-27 - Sample Template for Multiple Ajax Updates
[php]
Basic letter
Dear name_here,
Your e-mail was received and will be answered shortly.
Sincerely,
To update both, imagine that the Ajax response can be a JSON data block
containing the following array:
{"title":"My basic letter","name":"Mr Brown"}
Then the remote call can easily interpret this response and update several
fields in a row, with a little help from JavaScript. The code in Listing
11-28 shows what could be added to the template of Listing 11-27 to achieve
this effect.
Listing 11-28 - Updating More Than One Element from a Remote Response
[php]
'publishing/refresh',
'complete' => 'updateJSON(request)'
)) ?>
The `complete` callback has access to the Ajax response and can pass it to a
third-party function. This custom `updateJSON()` function iterates over the
JSON obtained from the response via `responseJSON` and for each member of the
array, updates the element named by the key with the content of the value.
Listing 11-29 shows how the `publishing/refresh` action can return a JSON response.
Listing 11-29 - Sample Action Returning JSON data
[php]
class publishingActions extends sfActions
{
public function executeRefresh()
{
$this->getResponse()->setHttpHeader('Content-Type','application/json; charset=utf-8');
$output = '[["title", "My basic letter"], ["name", "Mr Brown"]]';
return $this->renderText('('.$output.')');
}
Using the `application/json` content type allows Prototype to automatically evaluate the JSON passed as the body of the response, which is preferred to returning it via the header, because the HTTP header is limited in size and some browsers struggle with responses that do not have a body.
By using `->renderText()` the template file is not used, resulting in better performance.
JSON has become a standard among web applications. Web services often propose responses in JSON rather than XML to allow service integration in the client (mashup), rather than on the server. So if you wonder which format to use for communication between your server and a JavaScript function, JSON is probably your best bet.
>**TIP**
>Since version 5.2, PHP offers two functions, `json_encode()` and `json_decode()`, that allow you to convert an array between the PHP syntax and the JSON syntax, and vice versa ([http://www.php.net/manual/en/ref.json.php](http://www.php.net/manual/en/ref.json.php)). These facilitate the integration of JSON arrays (and Ajax in general) and enable the use of more readable native PHP code:
>
> [php]
> $output = array("title" => "My basic letter", "name" => "Mr Brown");
> return $this->renderText(json_encode($output));
Performing Complex Interactions with Ajax
-----------------------------------------
Among the symfony Ajax helpers, you will also find some tools that build up complex interactions with a single call. They allow you to enhance the user experience by desktop-application-like interactions (drag-and-drop, autocompletion, and live editing) without the need for complex JavaScript. The following sections describe the helpers for complex interactions and show simple examples. Additional parameters and tweaks are described in the script.aculo.us documentation.
>**CAUTION**
>If complex interactions are possible, they need extra time for presentation tweaking to make them feel natural. Use them only when you are sure that they enhance the user experience. Avoid them when there is a risk that they will disorient users.
### Autocompletion
A text-input component that shows a list of words matching the user's entry while the user types is called an autocompletion. With a single helper called `input_auto_complete_tag()`, you can achieve this effect, provided that the remote action returns a response formatted as an HTML item list similar to the example shown in Listing 11-30.
Listing 11-30 - Example of a Response Compatible with the Autocomplete Tag
- suggestion1
- suggestion2
...
Insert the helper in a template as you would do with a regular text input, following the example shown in Listing 11-31.
Listing 11-31 - Using the Autocomplete Tag Helper in a Template
[php]
Find an author by name:
'off'),
array('use_style' => true)
) ?>
This will call the `author/autocomplete` action each time the user types a character in the `author` field. It's up to you to design the action so that it determines a list of possible matches according to the author request parameter and returns them in a format similar to Listing 11-30. The helper will then display the list under the `author` tag, and clicking one of the suggestions or selecting it with the keyboard will complete the input, as shown in Figure 11-3.
Figure 11-3 - An autocompletion example
![An autocompletion example](/images/book/F1103.png "An autocompletion example")
The third argument of the `input_auto_complete_tag()` helper can take the following parameters:
* `use_style`: Styles the response list automatically.
* `frequency`: Frequency of the periodical call (defaults to 0.4s).
* `indicator`: Id of an indicator that will be shown when loading of the autocompletion suggestions started and faded when it completes.
* tokens: To allow tokenized incremental autocompletion. For instance, if you set this parameter to `,` and if the user entered `jane, george`, the action would receive only the value `'george'`.
### Drag-and-Drop
The ability to grab an element with the mouse, move it, and release it somewhere else is familiar in desktop applications but rarer in web browsers. This is because coding such behavior in plain JavaScript is very complicated. Fortunately, it requires only one line in symfony.
The framework provides two helpers, `draggable_element()` and `drop_receiving_element()`, that can be seen as behavior modifiers; they add observers and abilities to the element they address. Use them to declare an element as draggable or as a receiving element for draggable elements. A draggable element can be grabbed by clicking it with the mouse. Until the mouse button is released, the element can be moved, or dragged, across the window. A receiving element calls a remote function when a draggable element is released on it. Listing 11-32 demonstrates this type of interaction with a shopping cart receiving element.
Listing 11-32 - Draggable Elements and Drop-Receiving Elements in a Shopping Cart
[php]
- Carrot
true)) ?>
- Apple
true)) ?>
- Orange
true)) ?>
Your cart is empty
Drag items here to add them to your cart
'cart/add',
'accept' => 'food',
'update' => 'cart',
)) ?>
Each of the items of the unordered list can be grabbed by the mouse and dragged across the window. When released, they return to their original position. When released over the `cart` element, it triggers a remote call to the cart/add action. The action will be able to determine which item was dropped in the `cart` element by looking at the `id` request parameter. So Listing 11-32 simulates a real shopping session: You grab items and release them in the cart, and then proceed to checkout.
>**TIP**
>In Listing 11-32, the helpers are written just after the element they modify, but that is not a requirement. You could very well group all the `draggable_element()` and `drop_receiving_element()` helpers at the end of the template. The important thing is the first argument of the helper call, which specifies the identifier of the element to receive the behavior.
The `draggable_element()` helper accepts the following parameters:
* `revert`: If set to `true`, the element will return to its original location when released. It can also be an arbitrary function reference, called when the drag ends.
* `ghosting`: Clones the element and drags the clone, leaving the original in place until the clone is dropped.
* `snap`: If set to `false`, no snapping occurs. Otherwise, the draggable can be dragged only to the intersections of a grid of interval x and y, and in this case, it takes the form `xy` or `[x,y]` or `function(x,y){ return [x,y] }`.
The `drop_receiving_element()` helper accepts the following parameters:
* `accept`: A string or an array of strings describing CSS classes. The element will accept only draggable elements that have one or more of these CSS classes.
* `hoverclass`: CSS class added to the element when the user drags an accepted draggable element over it.
### Sortable Lists
Another possibility offered by draggable elements is the ability to sort a list by moving its items with the mouse. The `sortable_element()` helper adds the sortable behavior to an item, and Listing 11-33 is a good example of implementing this feature.
Listing 11-33 - Sortable List Example
[php]
What do you like most?
- Carrots
- Apples
- Oranges
// Nobody likes Brussel sprouts anyway
- Brussel sprouts
'item/sort',
'update' => 'feedback',
'only' => 'sortable',
)) ?>
By the magic of the `sortable_element()` helper, the `
` element is made sortable, which means that its children can be reordered by drag-and-drop. Each time the user drags an item and releases it to reorder the list, an Ajax request is made with the following parameters:
POST /sf_sandbox/web/frontend_dev.php/item/sort HTTP/1.1
order[]=1&order[]=3&order[]=2&_=
The full ordered list is passed as an array (with the format `order[$rank]=$id`, the `$rank` starting at 0, and the `$id` based on what comes after the underscore (`_`) in the list element `id` property). The `id` property of the sortable element (`order` in the example) is used to name the array of parameters.
The `sortable_element()` helper accepts the following parameters:
* `only`: A string or an array of strings describing CSS classes. Only the child elements of the sortable element with this class can be moved.
* `hoverclass`: CSS class added to the element when the mouse is hovered over it.
* `overlap`: Set it to `horizontal` if the items are displayed inline, and to `vertical` (the default value) when there is one item per line (as in the example).
* `tag`: If the list to order is not a set of `- ` elements, you must define which child elements of the sortable element are to be made draggable (for instance, `div` or `dl`).
>**TIP**
>Since symfony 1.1 you can also use `sortable_element()` helper without the `url` option. Then no AJAX request will be made on sorting. Useful if you want to defer the AJAX call to a `save` button or similar.
### Edit in Place
More and more web applications allow users to edit the contents of pages directly on the page, without the need to redisplay the content in a form. The principle of the interaction is simple. A block of text is highlighted when the user hovers the mouse over it. If the user clicks inside the block, the plain text is converted into a text area filled with the text of the block, and a save button appears. The user can edit the text inside the text area, and once he saves it, the text area disappears and the text is displayed in plain form. With symfony, you can add this editable behavior to an element with the `input_in_place_editor_tag()` helper. Listing 11-34 demonstrates using this helper.
Listing 11-34 - Editable Text Example
[php]
You can edit this text
40,
'rows' => 10,
)) ?>
When the user clicks the editable text, it is replaced by a text input area filled with the text, which can be edited. When the form is submitted, the `mymodule/myaction` action is called in Ajax with the edited value set as the `value` parameter. The result of the action updates the editable element. It is very fast to write and very powerful.
The `input_in_place_editor_tag()` helper accepts the following parameters:
* cols and rows: The size of the text input area that appears for editing (it becomes a `