< documentation
Guides
- basics
Project
- app
- app/boot
- app/boot/client.js
- app/boot/server.js
- app/configs
- app/configs/config.js
- app/configs/environments.js
- app/configs/filters.js
- app/configs/logging.js
- app/configs/routes.js
- app/controllers
- app/controllers/example.js
- app/models
- app/models/example.js
- app/services
- app/services/example.js
- app/templates
- app/templates/example.js
- app/views
- app/views/example.js
- index.html
Getting Started 1.0.x
creating a new project
To follow along with this step create this folder structure with a couple javascript files:
/* example project */
\example
\app
\boot
- client.js
\configs
- config.js
\lib
- jquery.js
- jquery.claypool.js
- index.html
-
scanning
Scanning is a railable pattern that allows you to setup a project with as little configuration as possible, and as you progress, it saves you some of the redundency of configuration by making use of some simple conventions. The following shows all the configuration we need to get going.
/** config.js **/ var Example = { Models:{}, Views:{}, Controllers:{} }; (function($){ $.scan([ "Example.Models", "Example.Views", "Example.Controllers" ]); })(jQuery);
-
boot strapping
Booting the application should be wrapped in the usual jquery ready. We'll revisit the boot process a little later but for the purposes of our little intro, this is good enough to get us going.
/** client.js **/ jQuery.noConflict(); (function($){ $(document).ready(function(){ $.boot(); }); })(jQuery);
/** index.html **/ <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>First jQuery-Claypool App</title> <script src="lib/jquery.js" type="text/javascript"></script> <script src="lib/jquery.claypool.js" type="text/javascript"></script> <script src="app/configs/config.js" type="text/javascript"></script> <script src="app/boot/client" type="text/javascript"></script> </head> <body> <div>Hello World!</div> </body> </html>
adding a route
Routes are basically where we return to each time we want to add a feature. It's also the first place you'll look when you want to find out where you need to look to find something in your application code. Routers are a common framework pattern to provide a consistent layer of delegation and a clear mapping of events to application controllers.
-
basic paginator
To get started we just need to decide on a feature. For this example we are going to add a 'paginator', which is really just a list of links.
Here our html:/** pagination markup **/ <div id='pages'> <a href='#example/page/1'>1</a> | <a href='#example/page/2'>2</a> | <a href='#example/page/3'>3</a> | <a href='#example/page/4'>4</a> | <a href='#example/page/5'>5</a> </div>
/* example project */ \example \app \configs - routes.js
/** routes.js **/ (function($){ $.mvc({ "hijax:a" : [{ id:"#example-hash-routes", active:false, filter:"[href*=#example]", hijaxMap: [{urls:"page/|:id|$", controller:"#pagesController"}] }] }); })(jQuery);
lazy event handling
As we create our first simple event handler, try to keep a couple key concepts in mind.
- First, it looks like a normal event handler, uses simple prototype patterns, but has its own closure. The closure will come in handy very soon aside from it's basic ability to provide shorthand names for jQuery and Example.Controllers ($ and $C respectively).
- Second, notice that we never create an instance of the event handler. jquery-claypool will create a single instance the first time the event needs to be routed to that controller, and will reuse that instance thereafter.
-
writing your first event handler
You are about to create your first application event handler, or controller . First we need a new folder and file. (Don't forget to add the additional script tag to your index.html)
/* example project */ \example \app \controllers - pages.js
/* pages.js */ (function($, $C){ $C.Pages = function(options){ $.extend(true, this, options); }; $.extend($C.Pages.prototype, { handle:function(event){ var id = event.params('id'); alert('page '+id); } }); })(jQuery, Example.Controllers);
adding some logging
Adding alerts to event handlers to see if they are getting triggered correctly is about as useful long-term as adding console.log statements that you comment in and out everytime you want to see the event in your console.
A much better approach is 'instrumented logging' which allows you to leave the log statements in your code, and turn them on or off from a single external location. As you start adding features to projects and accidently break some old feature, you will find it much faster to just turn up logging for a moment to see where the flow breaks, instead of walking through the code in a debugger. You may still need a debugger eventually, but isolating where something is breaking is much faster with category logging.
-
keeping it private
It's actually very easy to add logging and we are already going to take advantage of that closure we created for our event handler, aka our first controller. We are simply going to use the very common privacy pattern:
/* pages.js a la logging */ (function($, $C){ var log; $C.Pages = function(options){ $.extend(true, this, options); log = $.logger('Example.Controllers.Pages'); }; $.extend($C.Pages.prototype, { handle:function(event){ var id = event.params('id'); log.debug('got page %s', id); } }); })(jQuery, Example.Controllers);
For now just go with the idea that a logger is created with $.logger using the name of the class it belongs to.
Also notice we replace the alert in the event handler and use a sprintf-style message. This prevents logging statements from ever being constructed when logging is turned off (more on that in the intermediate guide) so leaving the logging in your code has near-zero net impact on performance. -
tuning the instrumentation
before we actually get any messages from the logging system, we have to turn it on. To do this we'll create a file just for logging configuration so we always know where to look for it. (Don't forget to include this new file to your index.html)
/* example project */ \example \app \configs - logging.js
/** logging.js **/ (function($){ $.logging([ { category:"Example", level:"INFO" }, { category:"Example.Models", level:"DEBUG" }, { category:"Example.Views", level:"DEBUG" }, { category:"Example.Controllers", level:"DEBUG" }, { category:"Claypool", level:"INFO" }, { category:"Claypool.MVC", level:"INFO" }, { category:"root", level:"WARN" } ]); })(jQuery);
You should now be able to open up your firebug console and see the messages. Dont be surprised if you see more, jquery-claypool is fully instrumented with logging too, so you can peer under the hood when you need or want too. Note, this is all true for server-side jquery-claypool as well, but messages are written to the server logs.
updating the page
The primary role of the controller is to create a basket of state, sometimes modifying it in the process. Like any event handler it may get some of that state from the event itself, use ajax (via a 'model') or inspect the dom.
The 'view' is where we draw a little line in the sand, and pass all that information we gathered in the controller over the fence. The controller doesn't care what is done with that infomation, and likewise the view doesn't care how the controller got the data. The views job is to simply make use of the data to update the page as the end user sees it.
-
some loremipsum
To get us started on rendering something useful we're going to 'fake' some information (don't forget to include it in your index.html). Here is how we will modify our pages controller
/* controllers/pages.js */ (function($, $C){ var log; $C.Pages = function(options){ $.extend(true, this, options); log = $.logger('Example.Controllers.Pages'); }; $.extend($C.Pages.prototype, { handle:function(event){ var id = event.params('id'); log.debug('got id %s', id); event.m({ index:id, title:$.titled(3, false), description:$.paragraphs(3, false).join('\n') }).render(); } }); })(jQuery, Example.Controllers);
-
a point of view
Views and Controllers are, generally, closely related, and that relationship is analogous to a Client and Server relationship. The server exposes data to the client, what the client does with it is of little concern to the server. Likewise the controller doesnt concern itself with what the view does.
We'll need to create a new file for our first view. (Don't forget to include this new file to your index.html)/* example project */ \example \app \views - pages.js
/** pagination markup **/ <body> <div>Hello World!</div> <div id='pages'> <a href='#example/page/1'>1</a> | <a href='#example/page/2'>2</a> | <a href='#example/page/3'>3</a> | <a href='#example/page/4'>4</a> | <a href='#example/page/5'>5</a> <h2 id='title'>title</h2> <h4>Page (<span id='index'>?</span>)</h4> <p id='description'> description </p> </div> </body>
/* views/pages.js */ (function($, $V){ var log; $V.Pages = function(options){ $.extend(true, this, options); log = $.logger('Example.Views.Pages'); }; $.extend($V.Pages.prototype, { update:function(model){ log.debug('updating page %s', model.index); $('#index').text(model.index); $('#title', this).text(model.title); $('#description', this).text(model.description); } }); })(jQuery, Example.Views);
gathering data
In this step we will use some simple AJAX routines to load data for our app.
In larger apps it tends to be very helpful to consolidate your AJAX routines to an object usually refered to as a Model . The Model just provides a convenient place to isolate validatation routines, and network abstractions for retreiving data from the server or some restful network storage.
-
just ajax
We'll need to create a new file for our first view. (Don't forget to include this new file to your index.html)
/* example project */ \example \app \models - pages.js
/* models/pages.js*/ (function($, $M){ var log; $M.Pages = function(options){ $.extend(true, this, options); log = $.logger('Example.Models.Pages'); }; $.extend($M.Pages.prototype, { get:function(id, options){ log.debug('getting page %s', id); $.ajax({ url:'./data/'+id+'.json', dataType:'json', success:function(page){ log.debug('got page %s', id); if(options && options.success ){ options.success(page); } } }); } return this; } }); })(jQuery, Example.Models);
/* controllers/pages.js */ (function($, $C){ var log, Pages; $C.Pages = function(options){ $.extend(true, this, options); log = $.logger('Example.Controllers.Pages'); Pages = $.$('#pagesModel'); }; $.extend($C.Pages.prototype, { handle:function(event){ var id = event.params('id'); log.debug('got id %s', id); Pages.get(id,{ success: function(page){ event.m({ index:id, title:page.title, description:page.description }).render(); } }); } }); })(jQuery, Example.Controllers);
-
cache only
Without getting fancy we are going to demonstrate one reason it's nice to seperate the model. Without modifying the controller which uses the model, we can add a simple caching mechanism so we don't repeat AJAX calls we don't have too.
/* models/pages.js */ (function($, $M){ var log, cache; $M.Pages = function(options){ $.extend(true, this, options); log = $.logger('Example.Models.Pages'); cache = {}; }; $.extend($M.Pages.prototype, { get:function(id, options){ log.debug('getting page %s', id); if(!cache[id]){ $.ajax({ url:'./data/'+id+'.json', dataType:'json', success:function(page){ log.debug('got page %s', id); cache[id] = page; if(options && options.success ){ options.success(page); } }, error:function(xhr,status,e){ log.error('error getting page %s', id). exception(e); if(options && options.error){ options.error({ status: status, msg: 'network error'+e }); } } }); }else{ log.debug('got cached page %s', id); if(options && options.success){ options.success(cache[id]); } } return this; } }); })(jQuery, Example.Models);
working well with others
jQuery-Claypool provides a very simple and powerful framework for managing environments. Environments are defined and used in order to isolate all application specific settings that may vary depending on where the application is being used. Common examples include development, testing, QA, and production environments.
-
defining environments
We'll need to create a new file for our first view. (Don't forget to include this new file in your index.html)
/* example project */ \example \app \configs - environments.js
/* configs/environments.js */ (function($){ $.env({ defaults:{ version:'0.0.0' }, dev:{ client:{ pages:'/apps/tutorial/5/data/' } }, test:{ client:{ pages:'/jquery-claypool/apps/tutorial/5/data/' } }, qa:{ client:{ pages:'/jquery-claypool-qa/apps/tutorial/5/data/' } }, prod:{ client:{ pages:'/data/' } } }); })(jQuery);
-
selecting environments
All we need to do now to take advantage of our environmental settings is choose one. Changing environments should allows only require a one line change in one file.
/* boot/client.js */ jQuery.noConflict(); (function($){ //A static logger for any initialization routines we might add here var log = $.logger("Example"); //The environments are described in environments.js try{ $.env('defaults', "dev.client"); }catch(e){ log.error("Environmental selection is invalid!").exception(e); } $(document).ready(function(){ $.boot(); }); })(jQuery);
/* models/pages.js */ (function($, $M){ var log, cache; $M.Pages = function(options){ $.extend(true, this, options); log = $.logger('Example.Models.Pages'); cache = {}; }; $.extend($M.Pages.prototype, { get:function(id, options){ log.debug('getting page %s', id); if(!cache[id]){ $.ajax({ url:$.env('pages')+id+'.json', dataType:'json', success:function(page){ log.debug('got page %s', id); cache[id] = page; if(options && options.success ){ options.success(page); } }, error:function(xhr,status,e){ log.error('error getting page %s', id). exception(e); if(options && options.error){ options.error({ status: status, msg: 'network error'+e }); } } }); }else{ log.debug('got cached page %s', id); if(options && options.success){ options.success(cache[id]); } } return this; } }); })(jQuery, Example.Models);
whats next
So far we've covered some high level concepts and that should be enough to get you started. There is much more to learn though so your project can take advantage of every feature you need without reinventing it.
-
intermediate topics
Intermediate topics include:
- Route your heart out
- Re: Modeling
- Template technologies.
- Control Flow with event.m().v().c()
- Application Filters
- Services are Server-Side Event Handlers
-
advanced topics
Advanced topics include:
- Writing Routers
- Backending to SDB
- Deploying to the Cloud
- Recycling, or Javascript in the Ether
- Framework Extension Points
- Inversion of Control
- The Claypool Proxy Server
Plugins
Extension Points
This guide is applicable to both the jquery-claypool client and server application frameworks. Where the two differ functionally the documentation will provide notes and examples of usage in each environment.