The definitive guide of Symfony 1.0

11.7. 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.

11.7.1. 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

<ul>
  <li>suggestion1</li>
  <li>suggestion2</li>
  ...
</ul>

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 echo form_tag('mymodule/myaction') ?>
  Find an author by name:
  <?php echo input_auto_complete_tag('author', 'default name',
    'author/autocomplete',
    array('autocomplete' => 'off'),
    array('use_style'    => true)
  ) ?>
  <?php echo submit_tag('Find') ?>
</form>

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.

An autocompletion example

Figure 11.3 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'.

11.7.2. 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

<ul id="items">
  <li id="item_1" class="food">Carrot</li>
  <?php echo draggable_element('item_1', array('revert' => true)) ?>
  <li id="item_2" class="food">Apple</li>
  <?php echo draggable_element('item_2', array('revert' => true)) ?>
  <li id="item_3" class="food">Orange</li>
  <?php echo draggable_element('item_3', array('revert' => true)) ?>
</ul>
<div id="cart">
  <p>Your cart is empty</p>
  <p>Drag items here to add them to your cart</p>
</div>
<?php echo drop_receiving_element('cart', array(
  'url'        => '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.

11.7.3. 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

<p>What do you like most?</p>
<ul id="order">
  <li id="item_1" class="sortable">Carrots</li>
  <li id="item_2" class="sortable">Apples</li>
  <li id="item_3" class="sortable">Oranges</li>
  // Nobody likes Brussel sprouts anyway
  <li id="item_4">Brussel sprouts</li>
</ul>
<div id="feedback"></div>
<?php echo sortable_element('order', array(
  'url'    => 'item/sort',
  'update' => 'feedback',
  'only'   => 'sortable',
)) ?>

By the magic of the sortable_element() helper, the <ul> 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 <li> elements, you must define which child elements of the sortable element are to be made draggable (for instance, div or dl).

11.7.4. 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

<div id="edit_me">You can edit this text</div>
<?php echo input_in_place_editor_tag('edit_me', 'mymodule/myaction', array(
  'cols'        => 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 <textarea> if rows is more than 1).
  • loadTextURL: The URI of an action that is called to display the text to edit. This is useful if the content of the editable element uses special formatting and if you want the user to edit the text without formatting.
  • save_text and cancel_text: The text on the save link (defaults to "ok") and on the cancel link (defaults to "cancel").