![]() |
|
Snippets |
|
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:
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.
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.