Flash Ajax effect seen in Rails |
[eluser]Nick Husher[/eluser]
Part 2. Adding AJAX Now that we have a basic working prototype that allows a user to add and remove comments via regular HTTP transactions, let's look at how to transplant this process into an asynchronous data flow. We're applying concepts of unobtrusive javascript here, so we want to change the markup as little as possible. My own personal preference is to try to use as few id="..." attributes as possible, mainly because I find that subconsciously I like to use the same ids a lot, and it's bad Web2.0 Quarma to have multiple elements with the same id on the same page. It also means I can quickly spot elements of relative structural or scripting importance. I had the asynchronous piece of the code already in mind when I wrote the markup, and applied ids to the parts of the code I'd most-likely need to be doing javascript with. Let's review: Code: form_open('comments',array('id'=>'add_comment_form')) YUI has two library files we'll be using for this part of the tutorial, yahoo-dom-event.js and connection-min.js. Yahoo-dom-event.js is a minimized collection of three sets of YUI functionality, the first provides convenience methods for the javascript language and YAHOO's library (yahoo.js), the second provides convenience methods and browser normalization for DOM tree interactions (dom.js), and the last one provides normalization of event handling across-browsers. Connection-min.js is a utility that allows for cross-browser use of asynchronous interaction, and some related convenience tools related to that. We're using the -min variants to save bandwidth. I'm assuming that you, the reader, are reasonably well-versed in CodeIgniter, but probably aren't as well-versed in javascript, so I'll walk through the javascript code in somewhat more depth than the previous part's CI code. I'll start with the basic setup of the inline script: Code: (function() { The first real code I want to do is wire up the necessary event listeners to make sure they're working right. There are two onClick handers we need to create, the first to listen on the form's submit button, and the other to listen for clicks to the [-] links in the comments field. Events in DOM trees bubble upwards--if you click on a node in the tree, parent elements will also fire their click events--so instead of attaching an event listener to every single delete link, we're going to attach one and then use some creative telepathy to do the work for us. Code: (function() {
[eluser]Nick Husher[/eluser]
It was brought to my attention that deleting comments in IE6 and IE7 wasn't working properly, this has been fixed. I had forgotten that the IE family has a different Event object than everyone else. I've fixed it on the live demo and above in the example. More tutorial to follow soon, so stay tuned.
[eluser]Nick Husher[/eluser]
Part 2: Adding AJAX (cont'd) We have the basic javascript hooks in place for listening to the necesary events that will bring about asynchronous actions. Right now they don't do anything, but sit tight, we'll see some awesomeness soon. The two pieces I now have to build are the CodeIgniter ajax action part and the javascript to handle sending a request to a server and handling the data that's sent back. Let's start with the server side, where I'm added a new function to my Comments controller: Code: function ajax_action($action) { Also note that validation errors are returned with a separate ajaxResponse. Later, I'll use the ajaxResponse field to decide where to put the ajaxHTML content. Now let's look at the javascript. To add a new comment, we need to make an asynchronous request to '<ci_base_dir>/index.php/comments/ajax_action/create_comment', which we will do with YAHOO.util.Connect, which is Yahoo's excellent connection library. I added the following code to the event listener listening for clicks on the form's submit button; whenever the button is pressed the following code is executed. Code: var callback = { Really, there's not a lot of complicatedness going on here. The setForm function is binding the next AJAX request to pass along whatever form data is present in the named form to the server. Connect is making the connection, and the callback object is waiting for a response. The response checks if the server returned an error; if not, it inserts the new comment and if so, displays the appropriate validation errors.
[eluser]Nick Husher[/eluser]
Okay, it's been a while since I posted to this, but there's not a lot left to do. First, dynamically deleting entries through AJAX, and second how to do animation in YUI. Let's get (re)started. Part 2: Adding Ajax (cont'd x2) In the last part, I wrote the code for deleting a post by passing 'delete_comment' as the first parameter while id parameter present in the postdata variable will determine which element gets deleted. In a previous section, I also created an event listener that captures the onclick events to those links. I can use the references to the relevant HTML element and its properties to figure out which ID needs to be deleted, and then perform the operation asynchronously. I then need to hide or remove the post from the DOM tree, I chose the former but the latter is just as easy. Basically, the code is set up very similarly to the previous part. Define a callback with a success function that performs an action when the request is complete, then set up the request with YAHOO.util.Connect.asyncRequest. There are two things different about this aspect, however; one is that I'm extracting the comment ID from the href attribute in the delete link with a regular expression. The other is that I'm formulating the POST body without a form. Yahoo's connect library allows you to generate your own post body as a fourth argument to the asyncRequest function, which allows a user of the function to perform POST-based requests independently of any form elements. In this case, I'm taking the ID I parsed from the delete link's URL and using that in my POST body. When the success function is called, the element is hidden. (target[removed] will select the parent element of the clicked link, which happens to be the div element containing the comment.) You can just as easily remove the element from the DOM tree at this point, it makes little difference. Code: var callback = { Up next: Animation.
[eluser]Nick Husher[/eluser]
Part 3: Adding Animation Finally, we're on the spit 'n polish bit. Having elements pop in and vanish instantly is really quite jarring. Sometime's it's not clear what's new, or what's been deleted. Animation, while being very very pretty, has a definite user interface benefit; when a user interface animates from one state to another, it's clear to the user exactly how the state of the system has changed. Paradoxically, sudden changes in state can sometimes go unnoticed by the eye. With that in mind, let's get to animat'n. The first thing I want to do is animate new comments into the tree. At the moment they sorta pop in as if by magic. The objective is to simulate the yellow flash as seen on Signal vs. Noise, 37Signal's excellent (if a bit full-of-itself) weblog. To do this in YUI, we need to use its ColorAnim function in the Animation library. ColorAnim allows us to change any color-based aspect of an element over time, which means we can start the element's background at a yellow, then fade to white. ColorAnim takes three arguments, the element to animate, the properties to animate, and the duration of the animation. There's more to it than that, but for 90% of purposes, those three things do the job perfectly. The properties object takes a little getting used to, it's a JSON literal of CSS properties keying how those properties are to change, so in our case: Code: var animProperties = { Code: var commentList = document.getElementById('comments-list'); The delete animation does two things; it fades the element out by shifting the element's opacity, then it hides the element by reducing the height to zero. If both of these things happen at once, it's not as clear that the element has been deleted more than it has been hidden (which is strictly true, but not the effect we want), so I opted to do one thing completely then the other. The whole thing takes half a second, so it's not a huge timewaster. So, in order to do one animation after the other, I need to know when the first animation is complete. YUI's animations fire events in certain situations, which you can subscribe functions to. For my purposes, I want to subscribe one animation's animate() function to to the other animation's onComplete event. Our simple target[removed].style.display = 'none' now becomes this: Code: var fadeOut = new YAHOO.util.Anim(target[removed], YAHOO.util.Anim behaves exactly like ColorAnim, except that it applies to non-color properties like opacity, height, width, border-width, etc. Well, I'm finally done. It took a while to get here, but sometimes good things take time. Really, the workflow becomes much faster once you have a clear workflow in mind of exactly how you want your client and server interacting. Doing AJAX things can sometimes be daunting because they're so multifaceted, thinking about how the client should behave in asynchronous and non-asynchronous cases is one aspect of the puzzle, another is in how the javascript and server code should communicate--is sending HTML snippets back and forth a good idea? Hm--and finally normalizing your results cross-browser can be tricky at best and murderously frustrating at worst. Sometimes the process consists of more than a little black magic; so many times something that looks like it should work perfectly inexplicably won't work at all, while something that's bodged together without thought for architecture or maintainability works simply and flawlessly. All I can say is that think ahead before writing code; think about how you're formulating transactions and how those transactions are getting handled. I've also found Firefox's Firebug plugin indespensible for when an asychronous piece of code is misbehaving; the Net->XHR tab is designed for debugging such transactions, and it's excellent at it. I wrote this code in about two hours of free time including CSS, javascript, databasing, etc, with another 20 minutes of polish based off of suggestions from a fellow developer. If I built a generalized interface for handling asynchronous transactions, I could cut that development time down significantly. Note: I also cut some corners; I do zero database and input checking, and in a production environment where user input could not only be flawed, but maliciously genererated, I would want to be much more careful. The obvious solution would be to pass session tokens along with requests, as well as strict sanitizing of inputs and database checking and graceful failing. I'll let you figure that part out, though.
[eluser]Nick Husher[/eluser]
NOTE: Some of my code was automatically altered by the forum's XSS checker. Anything with [removed] in it isn't by design. Keywords that were removed: innner_HTML, parent_Node. (Obviously minus the underscores)
[eluser]peterphp[/eluser]
Hey Nick, thanks for this excellent tutorial! Just one thing: the download link (http://demos.nickol.us/codeigniter/ajax_...script.zip) is not working anymore, maybe you can update the link? Cheers, Peter |
Welcome Guest, Not a member yet? Register Sign In |