18 |
19 |
20 |
21 |
22 |
23 |
26 |
27 |
28 |
29 |
32 |
33 |
64 |
Using jQuery Ajaxy
65 |
66 |
Seeing it work
67 |
68 |
The following demo is perhaps overly simplistic, but we know the simpler something is; the simpler it is to understand what is going on. So let's get started by clicking through the tabs/links to change the content and update our state.
69 |
70 |
83 |
84 |
85 | As you clicked through these, you would have noticed the content kept changing. If you have a eye for detail, you may have noticed that the page's URL hash also kept changing - this is how we track the state of our application. And if you are really really keen, you may have even noticed the page's title was also changing appropriately for each click.
86 |
87 |
88 |
89 | Now let's mix it up a bit. Click the forward and back buttons in your browser. Watch as it correctly recreates the states of your browsing history. Isn't that cool! Now refresh the page, and notice how the correct state was again recreated. Or even copy the URL, open a new tab, and stick the URL in there and go. Again the state was created. This is soo important, as it allows you to keep track of the state of you application, and the best thing is that it all happens automatically for you - Ajaxy handles it all leaving you to focus on what is important.
90 |
91 |
92 |
The code responsible
93 |
94 |
95 | All of these changes happen because we have upgraded those normal a elements into full featured rich Ajax links. We did this by simple adding the following CSS classes to the links: ajaxy and ajaxy-page. The first CSS class tells Ajaxy that this link is one to be upgraded, and the second is defined by our demo page to say we should use the page controller. Let's see the complete HTML before we move on:
96 |
97 |
98 |
The HTML:
99 |
100 | <ul id="menu">
101 | <li><a href="./pages/apricots.html" class="ajaxy ajaxy-page">Learn about Apricots</a></li>
102 | <li><a href="./pages/bananas.html" class="ajaxy ajaxy-page">Lean about Bananas</a></li>
103 | <li><a href="./pages/coconuts.html" class="ajaxy ajaxy-page">Learn about Coconuts</a></li>
104 | <li><a href="./pages/durians.html#yummy" class="ajaxy ajaxy-page">Learn about Durians</a></li>
105 | </ul>
106 | <div id="result">
107 | <div id="content" style="max-height:100px;overflow:auto;"></div>
108 | <div id="current"></div>
109 | </div>
110 |
111 |
112 |
113 | Pretty simple isn't it. So let's explain what each element is.
114 | The div#demo is the container for our demo, such that we can separate it from this text explaining what it is.
115 | The ul#menu contains all our links which effectively act as tabs.
116 | Each li.ajaxy.ajaxy-page are all our tab links, they are all linked to the actual Ajax content we would like to load in - such that a search engine can still index our website, and we can be as graceful as possible in upgrading our site.
117 | The div#result is the container for the two result elements we will be using.
118 | The div#content is where our tab content will be loaded into.
119 | The div#current is where we display what the current state is of our web application.
120 |
121 |
122 |
123 | So it pretty much looks just like how you would code it up without Ajax doesn't it? With the exception of the CSS classnames we added.
124 | So what will this code do? Well when our page loads, Ajaxy will look for all elements with the ajaxy class name, and upgrade them into a Ajax link.
125 | Once that link is then clicked, Ajaxy will perform a Ajax request and let us know it's sent off a request such that we can do any fadeOuts or loading animations we would like.
126 | Once Ajaxy has received the response from our server, it'll then try and figure out what we received - did we received a 404 not found? invalid data? text or JSON? - Ajaxy will handle it all for us.
127 | It'll then inform us of a valid response or a error occurred and pass us all the data we need to display our fetched content in our page.
128 | - It may sound a bit complicated, but all of that complicated stuff is happening in the background and being handled by Ajaxy and not us! Youhou! So that means we can focus on what matters - telling Ajaxy which links we want to use with Ajax, and animating and populating our content when requests or responses are performed.
129 |
130 |
131 |
So now we got that out the way, let's get into the javascript. We've documented the code inline as detailed as we can, rather than splitting the code up into segments. As at least for me, reading comments inline is a lot better than detached segments of code as you can't see the big picture. So here you are the big picture with lots of comments.
132 |
133 |
The JavaScript:
134 |
135 | /**
136 | * Create a local noconflict scope.
137 | * This is important as perhaps we are running in an noConflict environment so $ is not defined, but jQuery is.
138 | * What this will do is alias $ to jQuery, such that we can still write our code exactly the same as if we weren't in a noConflict environment.
139 | * Another important thing is that this allows us to create a local scope.
140 | * Local scopes are important they avoid variable overwrites and keeps our code nice and tidy.
141 | */
142 | (function($){
143 | /**
144 | * Fetch the element we will be using.
145 | * We assign them to variables as that way they are cached in our local scope.
146 | * This is good as say if we do $('#menu') three times, that is 3 times that jQuery has to find the #menu element. Causing unecessary load.
147 | */
148 | var $body = $(document.body),
149 | $menu = $('#menu'),
150 | $content = $('#content'),
151 | $current = $('#current');
152 |
153 | /**
154 | * Configure Ajaxy
155 | * We now proceed to configure Ajaxy.
156 | * We have to do this as otherwise, Ajaxy wouldn't know what to do with our Ajax data!
157 | * It would just perform a request, receive a response, and then go... well what do I do now?
158 | * So here we tell Ajaxy how to handle the different types of states we will have in our application.
159 | */
160 | $.Ajaxy.configure({
161 | /**
162 | * Ajaxy supports a whole bunch of different configuration options.
163 | * By default some things are enabled such as "debug" etc - these should be turned turned off in production environments.
164 | * We don't cover any of the options in this demo as they are outside the demo's scope.
165 | * You can however learn about the options by reading the README.txt attached within the Ajaxy project.
166 | */
167 |
168 | /**
169 | * For this demo, it may be hosted on a server which does not support AJAX POST requests, so let's use AJAX GET requests instead
170 | * For production you'll want to use POST which is the default - as this will allow you to send forms via ajaxy too.
171 | */
172 | 'method': 'get',
173 |
174 | /**
175 | * Define our Ajaxy Controllers.
176 | * If you have ever done some work with the Model View Controller architecture for applications, then this should be quite familiar to you.
177 | * If not I'll explain it anyway :-)
178 | * Controllers are what handles our application states, so if a state has changed we will rely on the appropriate controller to tell us what to do.
179 | * We'll explain this more as we go along. But this is the core of building an Ajaxy application.
180 | */
181 | 'Controllers': {
182 | /**
183 | * The Essential Generic Controller
184 | * In jQuery Ajaxy, we will always have a "_generic" controller, hence why it is deemed essential.
185 | * This controller is called for every single request and response Ajaxy recieves.
186 | * You can use it to (and probably should) use it to display loading animations so our user knows something is happening when a Ajax request is performing, as well as using it to update the document.title with the current states title, and displaying error information.
187 | */
188 | '_generic': {
189 | /**
190 | * The Request Action
191 | * As this is part of our Generic Controller, this will be called for every Ajax request that is performed.
192 | * It allows us to do such things as display the loading animation, and debug requests.
193 | */
194 | request: function(){
195 | // Prepare
196 | var Ajaxy = $.Ajaxy;
197 | // Log what is happening
198 | if ( Ajaxy.options.debug ) window.console.debug('$.Ajaxy.Controllers._generic.request', [this,arguments]);
199 | // Loading
200 | $body.addClass('loading');
201 | // Done
202 | return true;
203 | },
204 |
205 | /**
206 | * The Response Action
207 | * This one will fire when a Ajax request receives a successful response, and as it is part of the Generic Controller it'll fire for every response.
208 | * It allows us to do such things as hide the loading animation, update the document.title with the current state's title, and debug responses.
209 | */
210 | response: function(){
211 | // Prepare
212 | var Ajaxy = $.Ajaxy; var data = this.State.Response.data; var state = this.state||'unknown';
213 | // Log what is happening
214 | if ( Ajaxy.options.debug ) window.console.debug('$.Ajaxy.Controllers._generic.response', [this,arguments], data, state);
215 | // Title
216 | var title = data.title||false; // if we have a title in the response JSON
217 | if ( !title && this.state||false ) title = 'jQuery Ajaxy - '+this.state; // if not use the state as the title
218 | if ( title ) document.title = title; // if we have a new title use it
219 | // Loaded
220 | $body.removeClass('loading');
221 | // Display State
222 | $('#current').text('Our current state is: ['+state+']');
223 | // Return true
224 | return true;
225 | },
226 |
227 | /**
228 | * The Error Action
229 | * This one will fire when a Ajax request fails (be it we got a 404, invalid data, or whatever).
230 | * It's important as it allows us to display a error message to the user.
231 | * If an error occurs, only the Error action will be called and not the Response action, as such we should still do generic things like remove the loading animation.
232 | */
233 | error: function(){
234 | // Prepare
235 | var Ajaxy = $.Ajaxy; var data = this.State.Error.data||this.State.Response.data; var state = this.state||'unknown';
236 | // Error
237 | var error = data.error||data.responseText||'Unknown Error.';
238 | var error_message = data.content||error;
239 | // Log what is happening
240 | window.console.error('$.Ajaxy.Controllers._generic.error', [this, arguments], error_message);
241 | // Loaded
242 | $body.removeClass('loading');
243 | // Display State
244 | $('#current').text('Our current state is: ['+state+']');
245 | // Done
246 | return true;
247 | }
248 | },
249 |
250 | /**
251 | * Our Page Controller
252 | * This is what makes the example in this demo come alive.
253 | * It handles our page requests to do with the three fruits (apricots,bananas and coconuts).
254 | * We can call this whatever we like.
255 | */
256 | 'page': {
257 | /**
258 | * Our Page Controller's Classname [optional]
259 | * This associates our controller with the particular elements which match this classname.
260 | * It allows for when one of our Ajax links to be clicked, Ajaxy will know to fire the Page Controller's Request action.
261 | * This is important as without this there would be no possible way for us to know that the Ajax Request is for our Controller.
262 | */
263 | classname: 'ajaxy-page',
264 |
265 | /**
266 | * Our Page Controller's Matches [optional]
267 | * This can be a string, an array of strings, or a regular expression which is used to match the applications state.
268 | * For this demo, we have chosen to use a regular expression that will match against anything which starts with "/pages"
269 | * This variable follows the same reasoning as providing a selector, as it covers some more uses cases which the selector does not and vice versa.
270 | * To provide ane example of such a use case. Consider our page was bookmarked with the following state active: "/pages/apricots.html"
271 | * This would cause Ajaxy to perform the Ajax request necessary to recreate that state when the page has loaded.
272 | * However, as this request has not come from a link, we cannot use the Controller's selector to associate the request with a particular controller.
273 | * Instead we use this to match against the proposed state, and if it does then we know that this is the controller that should be used.
274 | */
275 | matches: /^\/pages\/?/,
276 |
277 | /**
278 | * Our Page Controller's Request Action
279 | * This just like our Request Action in the Generic Controller will be fired for all Ajaxy requests.
280 | * However this will only be fired for those Ajaxy requests which are known to be for the Page controller.
281 | * For instance, we could have another Controller called "Subpage", if a request is determined to be for that controller, their request action will fire and not this one.
282 | * We use this to prepare our tab area for incoming content, so we deselect all items in the tab menu, and fade out the content.
283 | */
284 | request: function(){
285 | // Prepare
286 | var Ajaxy = $.Ajaxy;
287 | // Log what is happening
288 | if ( Ajaxy.options.debug ) window.console.debug('$.Ajaxy.Controllers.page.request', [this,arguments]);
289 | // Adjust Menu
290 | $menu.find('.active').removeClass('active');
291 | // Hide Content
292 | $content.stop(true,true).fadeOut(400);
293 | // Return true
294 | return true;
295 | },
296 |
297 | /**
298 | * Our Page Controller's Response Action
299 | * This is just like our Page Controller's Request Action, however for responses instead.
300 | * We will use this to mark the appropriate item in the tab menu as active, to load the content into the tab area, and fade it in.
301 | * This is all we have to do :-)
302 | */
303 | response: function(){
304 | // Prepare
305 | var Ajaxy = $.Ajaxy; var data = this.State.Response.data; var state = this.state; var State = this.State;
306 | // Log what is happening
307 | if ( Ajaxy.options.debug ) window.console.debug('$.Ajaxy.Controllers.page.response', [this,arguments], data, state);
308 | // Adjust Menu
309 | $menu.children(':has(a[href*="'+State.raw.state+'"])').addClass('active').siblings('.active').removeClass('active');
310 | // Show Content
311 | var Action = this;
312 | $content.html(data.content).fadeIn(400,function(){
313 | Action.documentReady($content);
314 | /**
315 | * The above line calls our Action's documentReady function.
316 | * This is a special function which is always there as it is automaticly provided by Ajaxy.
317 | * We assign this to the variable Action, as inside the callback function for our jQuery effect the variable this will be point to somewhere else then!
318 | *
319 | * So what does this function do?
320 | * 1. It tells Ajaxy that the document is now ready for post processing.
321 | * 2. Ajaxy will then determine if the state included a anchor that we want to scroll to and initiate, and do that.
322 | * 3. Ajaxy will ajaxify the new content (provided the option [auto_ajaxify_documentReady] is true).
323 | * 4. Ajaxy will sparkle the new contnet (provided the option [auto_sparkle_documentReady] is true, and jQuery Sparkle exists).
324 | * This is optional as there are no dependencies with jQuery Sparkle, but it is a nifty project which is worth a look:
325 | * http://www.balupton.com/projects/jquery-sparkle/
326 | */
327 | });
328 | // Return true
329 | return true;
330 | }
331 | }
332 | }
333 | });
334 |
335 | // All done
336 | })(jQuery);
337 | // Back to global scope
338 |
339 |
340 |
341 | Now that is quite long, but only because we have so many comments. Let's kill the comments, and let's see how long the entire demo is with the HTML and Javascript.
342 |
343 |
344 |
345 |
The HTML:
346 |
347 | <ul id="menu">
348 | <li><a href="./pages/apricots.html" class="ajaxy ajaxy-page">Learn about Apricots</a></li>
349 | <li><a href="./pages/bananas.html" class="ajaxy ajaxy-page">Lean about Bananas</a></li>
350 | <li><a href="./pages/coconuts.html" class="ajaxy ajaxy-page">Learn about Coconuts</a></li>
351 | <li><a href="./pages/durians.html#yummy" class="ajaxy ajaxy-page">Learn about Durians</a></li>
352 | </ul>
353 | <div id="result">
354 | <div id="content" style="max-height:100px;overflow:auto;"></div>
355 | <div id="current"></div>
356 | </div>
357 |
358 |
359 |
The JavaScript:
360 |
361 | (function($){
362 | var $body = $(document.body),
363 | $menu = $('#menu'),
364 | $content = $('#content'),
365 | $current = $('#current');
366 | $.Ajaxy.configure({
367 | 'Controllers': {
368 | '_generic': {
369 | request: function(){
370 | request: function(){
371 | // Loading
372 | $body.addClass('loading');
373 | // Done
374 | return true;
375 | },
376 | response: function(){
377 | // Prepare
378 | var Ajaxy = $.Ajaxy; var data = this.State.Response.data; var state = this.state||'unknown';
379 | // Title
380 | var title = data.title||false; // if we have a title in the response JSON
381 | if ( !title && this.state||false ) title = 'jQuery Ajaxy - '+this.state; // if not use the state as the title
382 | if ( title ) document.title = title; // if we have a new title use it
383 | // Loaded
384 | $body.removeClass('loading');
385 | // Display State
386 | $('#current').text('Our current state is: ['+state+']');
387 | // Return true
388 | return true;
389 | },
390 | error: function(){
391 | // Prepare
392 | var Ajaxy = $.Ajaxy; var data = this.State.Error.data||this.State.Response.data; var state = this.state||'unknown';
393 | // Error
394 | var error = data.error||data.responseText||'Unknown Error.';
395 | var error_message = data.content||error;
396 | // Log what is happening
397 | window.console.error('$.Ajaxy.Controllers._generic.error', [this, arguments], error_message);
398 | // Loaded
399 | $body.removeClass('loading');
400 | // Display State
401 | $('#current').text('Our current state is: ['+state+']');
402 | // Done
403 | return true;
404 | }
405 | },
406 | 'page': {
407 | classname: 'ajaxy-page',
408 | matches: /^\/pages\/?/,
409 | request: function(){
410 | // Prepare
411 | var Ajaxy = $.Ajaxy;
412 | // Adjust Menu
413 | $menu.find('.active').removeClass('active');
414 | // Hide Content
415 | $content.stop(true,true).fadeOut(400);
416 | // Return true
417 | return true;
418 | },
419 | response: function(){
420 | // Prepare
421 | var Ajaxy = $.Ajaxy; var data = this.State.Response.data; var state = this.state; var State = this.State;
422 | // Adjust Menu
423 | $menu.children(':has(a[href*="'+State.raw.state+'"])').addClass('active').siblings('.active').removeClass('active');
424 | // Show Content
425 | var Action = this;
426 | $content.html(data.content).fadeIn(400,function(){
427 | Action.documentReady($content);
428 | });
429 | // Return true
430 | return true;
431 | }
432 | }
433 | }
434 | });
435 | // All done
436 | })(jQuery);
437 | // Back to global scope
438 |
439 |
440 |
441 |
442 | See that is tiny considering what we have just accomplished. And especially considering the power and magnitude of what we have just unleashed into your web application. Now compare that to how many hundreds upon hundreds lines of codes it would have taken to write everything we did today from scratch, and then imagine just how messy that alternative could be! I've been there, it can be hideous! So just sit back right now, and just think of what is now accomplishable right there at your finger tips. And think of all the great fantastic new projects you can make. Or even think just how nice your code will be. You'll be the envy of everyone! Woohoo.
443 |
444 |
445 |
446 | So I do hope that you can truly see the awesomeness of what you have just gone through. You are now prepared with everything you need to get cracking on your own Web 2.0 applications. You can see installation details below. And you can always send us questions and feedback about this project and how you can use it, by clicking the support link up the top or the feedback button on the right. Happy coding! :-)
447 |
448 |
449 |
506 |
507 |
712 |
713 |
714 |
720 |
721 |
737 |
738 |
739 |
--------------------------------------------------------------------------------