jQuery Ajaxy allows you to easily upgrade your web site into a full featured Ajax web application. Anyone who has tried to do this themselves would no doubt have spent countless hours debugging tedious javascript ajax requests, trying to figure out how to get ajax pages bookmarkable and working with back and forward buttons, or even struggled to add javascript effects to help manage your pages. jQuery Ajaxy was developed to solve all these problems, as well as some you may have never encountered such as; How do I keep my page indexable by Search Engines while making it an Ajax Application, How do I keep my site accessible to those who have javascript disabled? How I ensure I send the correct page back to the browser?
All of those problems are extremely difficult to solve, which is why we've solved them for you. We've made it so:
my/web/page.html#the-content-to-scroll-to
We've included a nice demo below so you can see all of this in action.
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.
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.
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.
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:
The HTML:
<ul id="menu"> <li><a href="./pages/apricots.html" class="ajaxy ajaxy-page">Learn about Apricots</a></li> <li><a href="./pages/bananas.html" class="ajaxy ajaxy-page">Lean about Bananas</a></li> <li><a href="./pages/coconuts.html" class="ajaxy ajaxy-page">Learn about Coconuts</a></li> <li><a href="./pages/durians.html#yummy" class="ajaxy ajaxy-page">Learn about Durians</a></li> </ul> <div id="result"> <div id="content" style="max-height:100px;overflow:auto;"></div> <div id="current"></div> </div>
Pretty simple isn't it. So let's explain what each element is.
The div#demo
is the container for our demo, such that we can separate it from this text explaining what it is.
The ul#menu
contains all our links which effectively act as tabs.
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.
The div#result
is the container for the two result elements we will be using.
The div#content
is where our tab content will be loaded into.
The div#current
is where we display what the current state is of our web application.
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.
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.
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.
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.
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.
- 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.
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.
The JavaScript:
/** * Create a local noconflict scope. * This is important as perhaps we are running in an noConflict environment so $ is not defined, but jQuery is. * 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. * Another important thing is that this allows us to create a local scope. * Local scopes are important they avoid variable overwrites and keeps our code nice and tidy. */ (function($){ /** * Fetch the element we will be using. * We assign them to variables as that way they are cached in our local scope. * 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. */ var $body = $(document.body), $menu = $('#menu'), $content = $('#content'), $current = $('#current'); /** * Configure Ajaxy * We now proceed to configure Ajaxy. * We have to do this as otherwise, Ajaxy wouldn't know what to do with our Ajax data! * It would just perform a request, receive a response, and then go... well what do I do now? * So here we tell Ajaxy how to handle the different types of states we will have in our application. */ $.Ajaxy.configure({ /** * Ajaxy supports a whole bunch of different configuration options. * By default some things are enabled such as "debug" etc - these should be turned turned off in production environments. * We don't cover any of the options in this demo as they are outside the demo's scope. * You can however learn about the options by reading the README.txt attached within the Ajaxy project. */ /** * 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 * For production you'll want to use POST which is the default - as this will allow you to send forms via ajaxy too. */ 'method': 'get', /** * Define our Ajaxy Controllers. * If you have ever done some work with the Model View Controller architecture for applications, then this should be quite familiar to you. * If not I'll explain it anyway :-) * 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. * We'll explain this more as we go along. But this is the core of building an Ajaxy application. */ 'Controllers': { /** * The Essential Generic Controller * In jQuery Ajaxy, we will always have a "_generic" controller, hence why it is deemed essential. * This controller is called for every single request and response Ajaxy recieves. * 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. */ '_generic': { /** * The Request Action * As this is part of our Generic Controller, this will be called for every Ajax request that is performed. * It allows us to do such things as display the loading animation, and debug requests. */ request: function(){ // Prepare var Ajaxy = $.Ajaxy; // Log what is happening if ( Ajaxy.options.debug ) window.console.debug('$.Ajaxy.Controllers._generic.request', [this,arguments]); // Loading $body.addClass('loading'); // Done return true; }, /** * The Response Action * 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. * 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. */ response: function(){ // Prepare var Ajaxy = $.Ajaxy; var data = this.State.Response.data; var state = this.state||'unknown'; // Log what is happening if ( Ajaxy.options.debug ) window.console.debug('$.Ajaxy.Controllers._generic.response', [this,arguments], data, state); // Title var title = data.title||false; // if we have a title in the response JSON if ( !title && this.state||false ) title = 'jQuery Ajaxy - '+this.state; // if not use the state as the title if ( title ) document.title = title; // if we have a new title use it // Loaded $body.removeClass('loading'); // Display State $('#current').text('Our current state is: ['+state+']'); // Return true return true; }, /** * The Error Action * This one will fire when a Ajax request fails (be it we got a 404, invalid data, or whatever). * It's important as it allows us to display a error message to the user. * 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. */ error: function(){ // Prepare var Ajaxy = $.Ajaxy; var data = this.State.Error.data||this.State.Response.data; var state = this.state||'unknown'; // Error var error = data.error||data.responseText||'Unknown Error.'; var error_message = data.content||error; // Log what is happening window.console.error('$.Ajaxy.Controllers._generic.error', [this, arguments], error_message); // Loaded $body.removeClass('loading'); // Display State $('#current').text('Our current state is: ['+state+']'); // Done return true; } }, /** * Our Page Controller * This is what makes the example in this demo come alive. * It handles our page requests to do with the three fruits (apricots,bananas and coconuts). * We can call this whatever we like. */ 'page': { /** * Our Page Controller's Classname [optional] * This associates our controller with the particular elements which match this classname. * It allows for when one of our Ajax links to be clicked, Ajaxy will know to fire the Page Controller's Request action. * This is important as without this there would be no possible way for us to know that the Ajax Request is for our Controller. */ classname: 'ajaxy-page', /** * Our Page Controller's Matches [optional] * This can be a string, an array of strings, or a regular expression which is used to match the applications state. * For this demo, we have chosen to use a regular expression that will match against anything which starts with "/pages" * 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. * To provide ane example of such a use case. Consider our page was bookmarked with the following state active: "/pages/apricots.html" * This would cause Ajaxy to perform the Ajax request necessary to recreate that state when the page has loaded. * 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. * 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. */ matches: /^\/pages\/?/, /** * Our Page Controller's Request Action * This just like our Request Action in the Generic Controller will be fired for all Ajaxy requests. * However this will only be fired for those Ajaxy requests which are known to be for the Page controller. * 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. * 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. */ request: function(){ // Prepare var Ajaxy = $.Ajaxy; // Log what is happening if ( Ajaxy.options.debug ) window.console.debug('$.Ajaxy.Controllers.page.request', [this,arguments]); // Adjust Menu $menu.find('.active').removeClass('active'); // Hide Content $content.stop(true,true).fadeOut(400); // Return true return true; }, /** * Our Page Controller's Response Action * This is just like our Page Controller's Request Action, however for responses instead. * 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. * This is all we have to do :-) */ response: function(){ // Prepare var Ajaxy = $.Ajaxy; var data = this.State.Response.data; var state = this.state; var State = this.State; // Log what is happening if ( Ajaxy.options.debug ) window.console.debug('$.Ajaxy.Controllers.page.response', [this,arguments], data, state); // Adjust Menu $menu.children(':has(a[href*="'+State.raw.state+'"])').addClass('active').siblings('.active').removeClass('active'); // Show Content var Action = this; $content.html(data.content).fadeIn(400,function(){ Action.documentReady($content); /** * The above line calls our Action's documentReady function. * This is a special function which is always there as it is automaticly provided by Ajaxy. * 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! * * So what does this function do? * 1. It tells Ajaxy that the document is now ready for post processing. * 2. Ajaxy will then determine if the state included a anchor that we want to scroll to and initiate, and do that. * 3. Ajaxy will ajaxify the new content (provided the option [auto_ajaxify_documentReady] is true). * 4. Ajaxy will sparkle the new contnet (provided the option [auto_sparkle_documentReady] is true, and jQuery Sparkle exists). * This is optional as there are no dependencies with jQuery Sparkle, but it is a nifty project which is worth a look: * http://www.balupton.com/projects/jquery-sparkle/ */ }); // Return true return true; } } } }); // All done })(jQuery); // Back to global scope
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.
The HTML:
<ul id="menu"> <li><a href="./pages/apricots.html" class="ajaxy ajaxy-page">Learn about Apricots</a></li> <li><a href="./pages/bananas.html" class="ajaxy ajaxy-page">Lean about Bananas</a></li> <li><a href="./pages/coconuts.html" class="ajaxy ajaxy-page">Learn about Coconuts</a></li> <li><a href="./pages/durians.html#yummy" class="ajaxy ajaxy-page">Learn about Durians</a></li> </ul> <div id="result"> <div id="content" style="max-height:100px;overflow:auto;"></div> <div id="current"></div> </div>
The JavaScript:
(function($){ var $body = $(document.body), $menu = $('#menu'), $content = $('#content'), $current = $('#current'); $.Ajaxy.configure({ 'Controllers': { '_generic': { request: function(){ request: function(){ // Loading $body.addClass('loading'); // Done return true; }, response: function(){ // Prepare var Ajaxy = $.Ajaxy; var data = this.State.Response.data; var state = this.state||'unknown'; // Title var title = data.title||false; // if we have a title in the response JSON if ( !title && this.state||false ) title = 'jQuery Ajaxy - '+this.state; // if not use the state as the title if ( title ) document.title = title; // if we have a new title use it // Loaded $body.removeClass('loading'); // Display State $('#current').text('Our current state is: ['+state+']'); // Return true return true; }, error: function(){ // Prepare var Ajaxy = $.Ajaxy; var data = this.State.Error.data||this.State.Response.data; var state = this.state||'unknown'; // Error var error = data.error||data.responseText||'Unknown Error.'; var error_message = data.content||error; // Log what is happening window.console.error('$.Ajaxy.Controllers._generic.error', [this, arguments], error_message); // Loaded $body.removeClass('loading'); // Display State $('#current').text('Our current state is: ['+state+']'); // Done return true; } }, 'page': { classname: 'ajaxy-page', matches: /^\/pages\/?/, request: function(){ // Prepare var Ajaxy = $.Ajaxy; // Adjust Menu $menu.find('.active').removeClass('active'); // Hide Content $content.stop(true,true).fadeOut(400); // Return true return true; }, response: function(){ // Prepare var Ajaxy = $.Ajaxy; var data = this.State.Response.data; var state = this.state; var State = this.State; // Adjust Menu $menu.children(':has(a[href*="'+State.raw.state+'"])').addClass('active').siblings('.active').removeClass('active'); // Show Content var Action = this; $content.html(data.content).fadeIn(400,function(){ Action.documentReady($content); }); // Return true return true; } } } }); // All done })(jQuery); // Back to global scope
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.
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! :-)
jquery-ajaxy
and move all the files and directories into that to continue.jquery-ajaxy
which has a whole bunch of stuff in it, then that is fine and you can continue onto the next step.jquery-ajaxy
then you must rename it to jquery-ajaxy
to continue.jquery-ajaxy
directory to somewhere on your webserverBe sure to always keep the entire jquery-ajaxy
directory intact; this means if you were to only move some of the files instead of moving the entire directory, then you would run into problems due to the cross directory references would no longer be working.
If your page already has jQuery included then you can skip this step.
<!-- Include jQuery (Ajaxy Requirement) --> <script type="text/javascript" src="http://www.yoursite.com/somewhere/on/your/webserver/jquery-ajaxy/scripts/jquery-1.4.2.min.js"></script>
<!-- Include jQuery Ajaxy --> <script type="text/javascript" src="http://www.yoursite.com/somewhere/on/your/webserver/jquery-ajaxy/scripts/jquery.ajaxy.min.js"></script>
If anything isn't working the way you want it to, or if you would like to request a feature or provide praise or general feedback then be sure to click the feedback button to the right, or the "Get Support" link up the top of this page.
This work is powered by coffee and distributed for free. Donations are how we afford our coffee. Coffee is how we stay awake after our day job hours to work on things like this free open-source project which you're looking at. So go ahead, and get that warm fuzzy feeling knowing you just helped some poor fellow working after hours for free to buy his coffee. You only need to spare a few dollars, or as much as you feel this piece of work is worth. Thanks so much. Alternatively; if you are not in a donating mood, then spreading the word about this project on twitter, your blog, or whatever is just as good. You can also give praise by clicking the feedback button or visiting our "Get Support" link. Thanks a bunch, we appreciate the help deeply.
This work is licensed under a GNU Affero General Public License.