Code snippets for symfony 1.x

Navigation

Refine Tags

Snippets tagged "linktoremote"

Degradable AJAX for form_remote_tag(), link_to_remote(), button_to_remote(),etc

GO HERE TO VIEW CODE:

http://www.symfony-project.com/forum/index.php/t/4804/

My Snipeet works on the premise that in the JS helpers such as form_remote_tag, or link_to_remote(), the AJAX request action is called via an OnClick/onSubmit() when JS is enabled. For the sake of our argument, we will call the AJAX request action AMod/Aaction .When JS is not enabled, usually a secondary link given via either the 'href' or 'action' attribute is called. The default secondary link for these actions is '#'. Through my classes, when JS is not enabled, the secondary link brings you to myapp/yz_degradable_ajax_module/Process_nonajax_page action. All of the pertinant information is sent to that action via the GET method. The process_nonajax_page action will also be passed AMod/Aaction and the varables that the original JS helper was trying to pass to it. AMod/Aaction will be called via getPresentation and the values usually passed to it via POST or GET will be passed via $this->getRequest->getAttributes() because that is one of the only ways to pass values from one action to another. The HTML output from getPresentation will be put into a varable and redirects back to the original page. Once back on the original page, the create_div method intercepts that variable and outputs it to the user.

So to the lay user, when JS is not active, the page dynamically loading the new contents via ajax, reloads the whole page with the desired contents.

I created 2 files, 1 class and one module(with 1 action.class.php) that lets you to easily integrate degradable AJAX many of the Javascript remote functions

Place the yzDegradableAjax.class.php class in /app/lib/ . This file contains all of the functions and methods used.

Please let me know if this works for you or if my instructions are too confusing and I'll try to clarify. All criticisms are welcome.

Since the this Symfony Snippet section likes to go crazy whenever I paste in lots of code, you can view the code and course documentation at this form:

http://www.symfony-project.com/forum/index.php/t/4804/
by Drew Lipsky on 2007-01-30, tagged ajax  buttontoremote  degradable  degradation  formremotetag  graceful  linktoremote 

Recalling AJAX state upon page reload

Remembering AJAX State

If you are updating your interface with ajax actions, typically users have no way to bookmark or return to the page with those updates already in place. This is seen as a fundamental problem with ajax: the URL shown at the top of the browser no longer constitutes a complete specification of the information shown in the window.

This snippet explains how you can track and recreate the current view even when using ajax to update your page. Now if users reload the page or send this link to a friend, the current state will remain intact. (You can view an example of this technique on http://www.ask.com/)

The trick relies on two parts:

  1. Updating the window.location.hash (the part of the url after the "#", also called an anchor) on a page.
  2. Using it to reconstruct the state of the interface. In other words, when users click on your ajax links, you will update the URL with a new hash string, and when the page is loaded you can you look for this hash string and update the interface accordingly.

Part One: Tracking State

First, you must allow the links created using the helpers link_to_remote and link_to_function to NOT have a 'return false;' appended to the end of the onclick attribute of the tag that is generated. There is no way to change this by way of the default helper, but it's easy to override with our own.

A 'return false' statement means the browser won't try to navigate to the url in the 'href' attribute of your link. Usually you don't want your browser window to navigate to the default "href" link--you are just creating the link as means to make the ajax call. Typically href attributes, if any, are present in case the user does not have javascript enabled.

However, we do not want "return false" appended to the onclick attribute. Ideally our helper function would allow us to do something like this:

<?php echo my_link_to_remote('Read this post', array(
    'update' => 'indicator',
    'url'    => 'post/read?id='.$post->getId(),
    'return' => 'true',
), array('href' => '#post:read|id:'.$post->getId()) ?>

which creates this:

<a href="#post:read|id:1234" onclick="new Ajax.Updater(....); return true;">Read this post</a>

When a user clicks the link, not only is the ajax action called but the URL of the page gets updated to something like "http://mysite.com/#post:read|id:1234".

So, let's write our own customer helper. I placed it into the /lib/helper directory in a file called myJavascriptHelper.php.

<?php
/* File: myJavascriptHelper.php */
 
/**
 * Returns a link that will trigger a javascript function using the
 * onclick handler and return TRUE OR FALSE after the fact 
 * depending on the $html_options['return'] parameter. 
 * This attribute is removed before we use the default content_tag helper.
 *
 * Examples:
 *   <?php echo link_to_function('Greeting', "alert('Hello world!')", array('return'=>true)) ?>
 *   <?php echo link_to_function(image_tag('delete'), "if confirm('Really?'){ do_delete(); }") ?>
 */
function my_link_to_function($name, $function, $html_options = array())
{
  $html_options = _parse_attributes($html_options);
 
  $html_options['href'] = isset($html_options['href']) ? $html_options['href'] : '#';
  $html_options['onclick'] = $function.'; return ';
  $html_options['onclick'] .= (isset($html_options['return']) && $html_options['return'] == true) ? 'true;' : 'false;';
  unset($html_options['return']);
 
  return content_tag('a', $name, $html_options);
}
 
/**
 * See docs on link_to_remote helper for more info.
 */
function my_link_to_remote($name, $options = array(), $html_options = array())
{
  return my_link_to_function($name, remote_function($options), $html_options);
}
 
?>

Now for my ajax links I use my new helper and make sure to pass an href attribute as well as the 'return' attributte. This determines if clicking on the link will return true (update the url) or false (don't update it). Here is a real example of how I use it to keep track of what message is currently viewed in a message component within my interface.

<?php echo my_link_to_remote('Load This Message Inline', 
    array(
        'update'    => 'message_viewer',
        'url'=>'message/viewInline?id='.$message->getId().'&view='.$view,
        'loading' => "Element.show('message_indicator')",
        'complete'=> "viewInlineComplete(".$message->getId().", '".$view."')",
        'method'=>'get',
    ), array(
        'href' => '#message:viewInline|id:'.$message->getId().'|view:'.$view,
        'return' => true)) ?>

Notice that the href attribute has a string that mirrors the URL attribute. We could probably take the my_link_to_remote helper a step further and automatically generate the HREF attribute based on the URL, but I decided to keep some flexibility there. Now, if a user clicks the link, the url will get a string like "#message:viewInline|id:3|view:inbox" appended to it.

In my experience, both IE 6 and FF did NOT scroll to the top of the window nor did the page reload. In fact I believe the W3 DOM specs state that links to a hash address never reload the document. Instead, I get a nice new attribute in the URL of the page. Now, if my user wants to bookmark the page, or perhaps reload it, the current ajax state can be recalled using the information in the anchor tag.

Part Two: Using the Hash to Reconstruct State

Although you cannot retrieve the value after the hash from within PHP, you can retrieve it client-side by way of the 'location.hash' function and then set a cookie, update the page, or do whatever you like based on those attributes. I am using it to reload a message from a users inbox into the inline viewer. Let's see how it works.

First, we need a way to load in the hash string and turn it into something we can use. Here is the function I use and included in my main.js file that is used throughout my site.

function readHashVars()
    {
        var hash = window.location.hash;
        hash = hash.substring(1);
        hash = hash.replace(/\|/g, '&');
        hash = hash.replace(/\:/g, '=');
        // lets turn our url hash into a javascript hash (like an associated array)
        // we will use a useful function provided in the prototype library
        hash = hash.parseQuery();
        return hash;
    }

Note: This function requires the prototype library to work!

Next, whenever my page is loaded I should check to see if the hash contains attributes I set within my ajax actions. If there are, I call the same client-side update from remote function as I do when a user clicks on the ajax link in the interface. Here is the top of my indexSuccess.php file for my home page action. I do not include this within the main.js file because I want to use the PHP helpers like remote_function provided by symfony. Compare this to the use of my_link_to_remote in the code above.

<?php use_helper('Javascript') ?>
<?php echo javascript_tag("
function updateDashboard()
{
    var hash = readHashVars();
    // we now have a hash with attributes matching the parameters in the url
    // so if the url has #message:viewInline|id:3, we have a javascript object like
    // hash { message: viewInline, id: 3 }
 
    if(hash.message == 'viewInline') {
        " . remote_function(array(
            'update'    => 'message_viewer',
            'url'           => 'message/viewInline',
            'with'      => "'id='+hash.id+'&view='+hash.view",
            'loading' => "Element.show('message_indicator')",
            'complete'=> 'viewInlineComplete(hash.id, hash.view)',
            'method'    => 'get',
        )) . "
    }
}
 
Event.observe(window, 'load', updateDashboard, false);
") ?>

This time, instead of calling the "remote_function" helper with parameters within PHP, I rely on javascript to send the correct parameters using the "with" parameter. That way, I can include values from javascript that aren't available until the page is loaded.

That's all there is to it (phew!). Now that this is all set up, I can easily convert my inline ajax links to javascript functions that are automatically called when the necessary attributes exist within the URL hash.

by scott meves on 2007-01-29, tagged ajax  linktoremote