Permalink
Browse files

Bringing docs into master

  • Loading branch information...
1 parent d72fe72 commit df3d3f21026d414fd62f952a438c2cb4fec31105 @arturadib committed Dec 11, 2011
View
2 CNAME
@@ -0,0 +1,2 @@
+agilityjs.com
+
View
7 ChangeLog
@@ -0,0 +1,7 @@
+2011.12.11, Version 0.1.1
+
+*
+
+2011.07.21, Version 0.1.0
+
+* First release
View
10 docs/LICENSE_HEADER
@@ -0,0 +1,10 @@
+/*
+
+ Agility.js
+ Copyright (c) Artur B. Adib, 2011
+ http://agilityjs.com
+
+ Licensed under the MIT license
+ http://www.opensource.org/licenses/mit-license.php
+
+*/
View
23 docs/Makefile
@@ -0,0 +1,23 @@
+VER = `cat VERSION` (`date '+%b %d %Y'`)
+
+all:
+ @echo "Making version $(VER)..."
+
+ @markdown_py -x codehilite _index.md > index-content.html
+ @sed -E -i.bak "s/__VERSION__/$(VER)/" index-content.html
+ @sed '/__INSERT_POINT__/ r index-content.html' _index.html > index.html
+
+ @markdown_py -x codehilite _docs.md > docs-content.html
+ @sed -E -i.bak "s/__VERSION__/$(VER)/" docs-content.html
+ @sed '/__INSERT_POINT__/ r docs-content.html' _docs.html > docs.html
+
+ @markdown_py -x codehilite _gallery.md > gallery-content.html
+ @sed -E -i.bak "s/__VERSION__/$(VER)/" gallery-content.html
+ @sed '/__INSERT_POINT__/ r gallery-content.html' _gallery.html > gallery.html
+
+ @rm -f *-content.html *.bak
+ @mv -f index.html ..
+
+ @echo "Minifying"...
+ @cat LICENSE_HEADER > agility.min.js
+ @jsmin < ../agility.js >> agility.min.js
View
20 docs/README.md
@@ -0,0 +1,20 @@
+# Agility docs
+
+## Contributing
+
+You should only have to modify the files `_*.md` (e.g. `_docs.md`, `_index.md`, etc).
+
+## Building
+
+Dependencies:
+
++ Python Markdown http://freewisdom.org/projects/python-markdown/
++ Pygments + CodeHilite http://freewisdom.org/projects/python-markdown/CodeHilite
++ JSMin http://www.crockford.com/javascript/jsmin.html
+
+On Mac OS X, you can do an `$ easy_install pip`, then:
+
++ `$ pip install markdown pygments` (sudo)
++ `$ brew install jsmin`
+
+Then do a `$ make`. This should generate all the necessary production files (`index.html`, etc).
View
1 docs/VERSION
@@ -0,0 +1 @@
+0.1.1
View
28 docs/_demo.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+
+<!--
+
+**********************************************************************
+*
+* DO NOT MODIFY THIS FILE IF ITS NAME DOES NOT START WITH '_' !
+*
+* Instead change the corresponding _file.md or _file.html in docs/
+* and follow the build instructions in docs/README.md
+*
+**********************************************************************
+
+-->
+
+<html>
+
+<head>
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8">
+ <title>agility.js</title>
+</head>
+
+<body>
+ <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js" type="text/javascript" charset="utf-8"></script>
+ <script src="agility.min.js" type="text/javascript" charset="utf-8"></script>
+</body>
+
+</html>
View
181 docs/_docs.html
@@ -0,0 +1,181 @@
+<!DOCTYPE html>
+
+<!--
+
+**********************************************************************
+*
+* DO NOT MODIFY THIS FILE IF ITS NAME DOES NOT START WITH '_' !
+*
+* Instead change the corresponding _file.md or _file.html in docs/
+* and follow the build instructions in docs/README.md
+*
+**********************************************************************
+
+-->
+
+<html>
+<head>
+ <title>
+ Agility.js - Documentation
+ </title>
+ <meta charset="utf-8" />
+
+ <link rel="icon" type="image/png" href="favicon.png">
+ <link type="text/css" href="highlight.css" rel="stylesheet" />
+
+ <style type="text/css">
+ html,body { background:#ebebeb; margin:0; padding:0; padding-bottom:50px; font-family:'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; font-size:16px; line-height:150%; }
+ pre { line-height:125%; }
+ div.center { width:700px; margin-left:280px; margin-top:30px; }
+ a { color:#0000A0; text-decoration:none; }
+ a:hover { text-decoration:underline; }
+
+ div#menu-wrapper {
+ position:fixed;
+ top:0;
+ left:0;
+ bottom:0;
+ width:200px;
+ padding:5px 10px;
+ background:white;
+ border-right:1px solid #aac;
+ overflow:auto;
+ }
+
+ div#menu-wrapper a.nav { font-size:90%; }
+ div#menu div { margin-top:5px; margin-left:5px; }
+ div#menu a { color:#000; }
+ div#menu a.section { display:block; font-weight:bold; margin-top:10px; }
+ div#menu a.subsection { margin-left:2px; font-size:90%; }
+ div#menu a.func { margin-left:20px; font-size:80%; }
+
+ div#decor { height:8px; background:#0000A0; }
+ div#top { font-size:19px; }
+ div#box {
+ margin-top:20px;
+ background:white;
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ border-radius: 3px;
+ -moz-box-shadow: 0px 0px 8px #999;
+ -webkit-box-shadow: 0px 0px 8px #999;
+ box-shadow: 0px 0px 8px #999;
+ }
+ div#box-wrapper { padding:10px 20px; background:white; }
+ div#box h1 { border-top:10px solid #0000A0; }
+ div#box h2 { border-top:4px solid #A0A0D0; }
+ div#box h3 { border-top:1px solid #A0A0D0; }
+ div#box-wrapper .download { background:#FFFFCC; padding:5px 10px; text-align:center;}
+
+ div#box p img { display:block; margin-left:auto; margin-right:auto; }
+
+ .demo { margin-top:-10px; margin-left:10px; font-size:80%; }
+ div#demo-wrapper { font-size:90%; font-family:Arial, sans; }
+ div#demo-wrapper #msg { margin-bottom:10px; }
+ iframe { width:600px; height:400px; border:1px solid #333; }
+ #simplemodal-overlay { background-color:#000; }
+ #simplemodal-container { background-color:white; border:8px solid #555; padding:12px; }
+ </style>
+
+ <script type="text/javascript">
+ if (window.location.href.search(/\?x/) < 0) {
+ var _gaq = _gaq || [];
+ _gaq.push(['_setAccount', 'UA-924459-9']);
+ _gaq.push(['_trackPageview']);
+ (function() {
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+ })();
+ }
+ </script>
+
+</head>
+
+<body>
+
+ <div id="decor"></div>
+
+ <div id="menu-wrapper">
+ <a href="../" class="nav">&laquo; Back to Home</a>
+ <p></p>
+ <div id="menu"></div>
+ </div>
+
+ <div id="top" class='center'>
+ <div><a href="../" style="text-decoration:none; border:none;"><img src="logo.png" style="border:none;"/></a></div>
+ <div><em>Documentation</em></div>
+ </div>
+
+ <div id="box" class='center'>
+ <div id='box-wrapper'>
+
+<!-- __INSERT_POINT__ -->
+
+
+ </div>
+ </div>
+
+ <div id="demo-wrapper" style="display:none"></div>
+
+ <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js" type="text/javascript" charset="utf-8"></script>
+ <script src="jquery.simplemodal.1.4.1.min.js" type="text/javascript" charset="utf-8"></script>
+ <script type="text/javascript">
+ (function($){
+ //
+ // Build menu
+ //
+ $('h1,h2,h3,h4').each(function(){
+ var href = $('a', this).attr('href');
+ var $link = $('<a/>').attr('href', href).html($(this).text());
+ $('div#menu').append($link);
+ $link.wrap('<div/>');
+ if ($(this).is('h1')) {
+ $link.addClass('section');
+ }
+ else if ($(this).is('h2')){
+ $link.addClass('subsection');
+ $link.html( '&rsaquo; '+$link.html() )
+ }
+ else {
+ $link.addClass('func');
+ }
+ $(this).attr('id', href.substr(1));
+ });
+ //
+ // Each "Run demo"
+ //
+ $('div.demo').each(function(){
+ var code = $(this).prevAll('.codehilite:first').text();
+ //
+ // On demo click
+ //
+ var $link = $("<button>Run demo</button>").click(function(){
+ var $frame = $('<iframe \/>').attr('src', '_demo.html');
+ $('div#demo-wrapper')
+ .hide()
+ .empty()
+ .append('<div id="msg">Loading ...</div>')
+ .append($frame)
+ .modal({overlayClose: true});
+ //
+ // Insert dynamic frame content (pure jQuery doesn't work with script tags)
+ //
+ $frame.load(function(){
+ $('div#demo-wrapper #msg').html('Press <b>esc</b> or click outside box to close')
+ var frameWindow = this.contentWindow ? this.contentWindow : this.contentDocument.defaultView;
+ var script = frameWindow.document.createElement("script");
+ script.type = "text/javascript";
+ script.text = '\n'+code+'\n';
+ frameWindow.document.body.appendChild(script);
+ });
+ return false;
+ }); // demo click
+ $(this).append($link);
+ }); // each demo
+ })(jQuery);
+ </script>
+
+</body>
+
+</html>
View
956 docs/_docs.md
@@ -0,0 +1,956 @@
+
+<p class='download'><b>Version: __VERSION__</b></p>
+
+# [Introduction](#intro)
+
+Agility is a [Model-View-Controller](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) (MVC) library for client-side Javascript with some specific design principles:
+
++ Convention over configuration (CoC);
++ Don't repeat yourself (DRY); and
++ Full object reusability.
+
+The overall goal is to improve code maintainability without sacrificing productivity. It is inspired by the principles behind [Ruby on Rails](http://en.wikipedia.org/wiki/Ruby_on_Rails) and [jQuery](http://www.jquery.com).
+
+Agility's programming model is framed around the concept of self-contained MVC objects, where each object can be the prototype of, as well as the container of other MVC objects. This level of abstraction should encompass most applications.
+
+See the [home page](/) for a quick overview of its syntax and usage.
+
+## [Why MVC?](#intro-mvc)
+
+One might wonder, since DOM-querying/Ajax libraries like jQuery make it so easy to whip up a dynamic web app, why bother with an additional layer of complexity?
+
+### [Short answer](#intro-mvc-quick)
+
+For those who have built a complex web app "organically", i.e. purely through DOM querying and manual Ajax calls, the answer is immediate: although you were able to get that app up to speed so quickly, you probably dread maintaining and relearning that intertwined code, and wish you had known better!
+
+### [Long answer](#intro-mvc-long)
+
+For those who haven't, some things you will likely end up doing with a pure jQuery-esque solution include: storing data in the DOM; querying the DOM to find your data; defining global callbacks to DOM events e.g. click/input change; having those callbacks neatly package your data to be sent to the server; retrieving data from the server and inserting them in the DOM with the right format and event handlers; etc.
+
+Though that's all fine initially, sooner or later you will start running into maintainability problems: storing data in the DOM is very brittle, e.g. changing an id/class or restructuring the DOM requires revisiting the code just about everywhere; DOM elements that are logically related and need to be always in sync require manual updates in all callbacks associated with them; global callbacks lead to name collisions and hence cumbersome function names, as well as difficulties in finding just what function is responsible for doing X or Y; defining functions that package data for, or present data after Ajax calls is unnecessarily repetitive; etc.
+
+One established answer to these problems is the [Model-View-Controller](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) approach, where your app is organized in "large" objects each having different parts responsible for managing content (Model), format/style (View), and behavior (Controller). For example, a series of DOM input elements such as Name, Address, Phone, etc, related to the abstract concept of "person" become part of an object "person", whose model contains the raw data, view contains the HTML/CSS presentation, and controller contains the actions that will be called in response to events in the former two.
+
+MVC libraries like Agility typically offer built-in model, view, and controller methods that encompass most use case scenarios, so you don't have to reinvent the wheel or repeat yourself. That way all functions, formatting, and data related to an abstract concept (e.g. "person") are all in one place, the DOM is always in sync with the data, and the data is always ready to be sent to/retrieved from the server in one call.
+
+## [Why Agility?](#intro-agility)
+
+In response to the difficulties above, in the last few years [several](http://www.sproutcore.com/) [superb](http://documentcloud.github.com/backbone/) [libraries](http://knockoutjs.com/) have been introduced to bring MVC (or a variant thereof) to the browser. Although they do a good job of refactoring apps in terms of content, format, and behavior - and hence lead to more maintainable code - they do so at the expense of development speed: Most are fairly verbose, require a considerable amount of repetition, and/or require large library includes.
+
+Agility borrows some useful concepts from the above frameworks, and makes rapid development a core part of its design principles. It's ["write less, do more"](http://www.jquery.com) with maintainability, if you will.
+
+### [Features](#intro-features)
+
+Here are some of the features that Agility.js has aggregated into a single framework:
+
++ Painless [two-way model-view bindings](#bindings);
++ Implicit [controller-event bindings](#events);
++ [Controller auto-proxying](#auto-proxy) for quick and consistent access to owner object;
++ [Format and style in-object](#format-style) for "copy-and-paste" reusability;
++ Small (<10K) single-library include;
++ Compact and efficient [syntax](#factory), inspired by jQuery;
++ Pure [prototypal inheritance](#inheritance);
++ [Strict MVC](#intro-architecture): core has no additional concepts other than M, V, and C.
+
+## [Architecture](#intro-architecture)
+
+Agility's architecture follows one of the simplest MVC patterns: users define Controller functions, which make direct calls to, and handle events from Models and Views. The diagram below illustrates this.
+
+![Architecture diagram](architecture.png)
+
+So for example, when a user clicks on a DOM element, an event signal is sent from the View to any Controller functions listening to it, and these functions in turn can make direct calls to Model and View functions.
+
+Additionally, as illustrated below, every Agility object can serve as a container of other Agility objects. This is a natural abstraction for most applications, including simple lists, interactive tables, picture/video catalogs, etc, where each individual item might contain enough functionality (e.g. edit/remove buttons, mouse hover behavior, etc) to deserve its own MVC object. And because Agility objects are lightweight in memory (through pervasive use of prototypes), this comes at little performance cost.
+
+![Hierarchy diagram](container.png)
+
+# [Getting started](#getting-started)
+
+Agility.js depends on a recent version of jQuery (tested with 1.6.x, Zepto support coming soon). Other than that dependency, a single `<script>` tag in your Javascript code is all that's required, e.g.:
+
+ :::html
+ <script src="agility.js" type="text/javascript" charset="utf-8"></script>
+
+Typically the `<body>` of your HTML will be empty, and will be populated programmatically by adding Agility objects to the [global object](#globals-document) `$$.document`.
+
+Here's the full source of a "hello world" example:
+
+ :::html
+ <!DOCTYPE html>
+ <html>
+
+ <head>
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8">
+ <title>Agility Hello World</title>
+
+ <script src="jquery.min.js" type="text/javascript" charset="utf-8"></script>
+ <script src="agility.js" type="text/javascript" charset="utf-8"></script>
+ </head>
+
+ <body>
+ <script type="text/javascript">
+ var message = $$({txt:'Hello World'}, '<div data-bind="txt"/>');
+ $$.document.append(message);
+ </script>
+ </body>
+
+ </html>
+
+The above template has been used for all examples throughout this documentation.
+
+## [Creating objects](#creating-objects)
+
+Agility is framed around the notion of all-in-one MVC objects, or simply "Agility objects". Such objects are created through the [factory function](#factory) `$$()`, either from scratch (by passing model, view, and/or controller initializers) or from a prototype object (by specifying an existing Agility object):
+
+ :::javascript
+ // Create object from scratch:
+ var proto = $$({}, '<p data-bind="name" style="color:red"/>');
+ // Create object from prototype object:
+ var obj = $$(proto, {name:'Joe Doe'});
+ $$.document.append(obj);
+<div class="demo"></div>
+
+Refer to the examples in the [home page](/) and elsewhere in this document for several different uses of the factory function, and the [factory function reference](#factory) for syntax details.
+
+## [Bindings](#bindings)
+
+Agility offers painless two-way bindings to keep Models and Views in sync. Binding a given DOM element to a model property is as simple as specifying a `data-bind` attribute for the desired element:
+
+ :::javascript
+ // Two-way input binding (text)
+ var obj = $$({name:'Joe Doe'}, '<p><input type="text" data-bind="name"/> You typed: <span data-bind="name"/></p>');
+ $$.document.append(obj);
+<div class="demo"></div>
+
+ :::javascript
+ // Two-way input binding (search)
+ var obj = $$({query:'Type of query'}, '<p><input type="search" data-bind="name"/> Instant model change: <span data-bind="name"/></p>');
+ $$.document.append(obj);
+<div class="demo"></div>
+
+More complex bindings are also supported for other input elements:
+
+ :::javascript
+ // Two-way input binding (radio)
+ var obj = $$(
+ {opt:'opt-a'},
+ "<div> \
+ <input type='radio' name='test' value='opt-a' data-bind='opt'>a</input> \
+ <input type='radio' name='test' value='opt-b' data-bind='opt'>b</input> \
+ You selected: <span data-bind='opt'/> \
+ </div>"
+ );
+ $$.document.append(obj);
+<div class="demo"></div>
+
+ :::javascript
+ // Two-way input binding (checkbox)
+ var obj = $$(
+ {a:false, b:true},
+ "<div> \
+ <input type='checkbox' name='test' data-bind='a'/> checked: <span data-bind='a'/><br/> \
+ <input type='checkbox' name='test' data-bind='b'/> checked: <span data-bind='b'/><br/> \
+ </div>"
+ );
+ $$.document.append(obj);
+<div class="demo"></div>
+
+ :::javascript
+ // Two-way input binding (select)
+ var obj = $$(
+ {opt:'opt-a'},
+ "<div> \
+ <select data-bind='opt'> \
+ <option value='opt-a'>Option A</option>\
+ <option value='opt-b'>Option B</option>\
+ </select> \
+ You selected: <span data-bind='opt'/> \
+ </div>"
+ );
+ $$.document.append(obj);
+<div class="demo"></div>
+
+## [Format and style](#format-style)
+
+Agility's views require the specification of `format` (HTML), and encourage the use of `style` (CSS) in-object. This leads to better object reusability and maintainability: there is no need to fish out HTML/CSS parts from different files to reuse an existing object in a new project, and no need to maintain ids/classes throughout separate files. Content, style, and behavior are all contained in one object.
+
+Formats are specified through an HTML string, containing one (and only one) root element that wraps all other elements, so the first two examples below are **not** valid:
+
+ :::javascript
+ // INVALID CODE!! (missing root view element)
+ var obj = $$({}, 'hey there');
+ $$.document.append(obj);
+
+ // INVALID CODE!! (more than one root elements)
+ var obj = $$({}, '<div>hey there</div> <button>OK</button>');
+ $$.document.append(obj);
+
+ // Valid code
+ var obj = $$({}, '<p>hey there</p>');
+ $$.document.append(obj);
+
+Formats should always be specified upon object creation. Refer to the [factory function](#factory) for examples on how to initialize the format.
+
+Specifying styles (CSS) in-object is optional, but again, it leads to better code reusability and maintainability. In-object CSS is implemented dynamically, so the object's style sheet is not introduced until the object is created.
+
+To ensure CSS selectors apply only to the intended object, *make sure all selectors are preceded by the root selector* `&`. (In future versions this might not be necessary anymore).
+
+ :::javascript
+ // ANTI-PATTERN!! (applies CSS style globally)
+ var obj = $$({}, "<p><div>Please don't do this</div></p>", 'div { color:red; }');
+ $$.document.append(obj);
+
+ // Correct: applies style locally
+ var obj = $$({}, '<p><div>Do this</div></p>', '& div { color:red; }');
+ $$.document.append(obj);
+<div class="demo"></div>
+
+More complex formats and styles can be organized in multiple lines:
+
+ :::javascript
+ var obj = $$({
+ view: {
+ format:'<div>\
+ <div id="hello">Hello</div>\
+ <div id="world">World</div>\
+ </div>',
+ style:'& { border:5px solid green; color:white; }\
+ & div { padding:10px 20px; }\
+ & #hello { background:blue; }\
+ & #world { background:red; }'
+ }
+ });
+ $$.document.append(obj);
+<div class="demo"></div>
+
+If your `format` and/or `style` are too large, it's probably time to split your object into more Agility objects. (Unless of course you are creating a mostly static page, in which case Agility is probably not the best solution).
+
+## [Events](#events)
+
+There are two types of events in Agility: DOM events, and Agility events, and both are implicitly bound to controller functions by matching function and event names. User-defined controllers extend (i.e. are called in addition to) built-in controllers.
+
+### [DOM events](#events-dom)
+
+Usual DOM events such as `click`, `dblclick`, `mouseenter`, etc are supported through jQuery's event API. Please consult jQuery's API for a [list of events](http://api.jquery.com/bind/) supported.
+
+When binding to controller functions, DOM events are distinguished from Agility events by the presence of a [jQuery selector](http://api.jquery.com/category/selectors/) using the syntax:
+
+ :::javascript
+ // DOM event syntax for controller functions
+ 'event selector': function(){}
+
+In addition to jQuery's selectors, the root selector `&` is also supported to pick the root element of the view:
+
+ :::javascript
+ var button = $$({msg:'Click me'}, '<button data-bind="msg"/>', {
+ 'click &': function() {
+ this.model.set({msg:"I've been clicked!"});
+ }
+ });
+ $$.document.append(button);
+<div class="demo"></div>
+
+### [Agility events](#events-agility)
+
+Agility events are fired by the object core, as well as Models and plugins. When binding to a controller function, they are never followed by a space:
+
+ :::javascript
+ // Agility event syntax for controller functions
+ 'event': function(){}
+ 'event:event_parameter': function(){}
+
+Presently, the following Agility events are fired:
+
++ `create`: Fired upon object creation.
++ `destroy`: Fired before object is destroyed.
++ `add`: Fired when a new Agility object is added to the object's container.
++ `remove`: Fired with an Agility object is removed from the object's container.
++ `change`: Fired when the model has changed.
++ `change:prop`: Fired when the property `prop` in the model has changed.
+
+The example below defines both a DOM and a Model event handler:
+
+ :::javascript
+ var catcher = $$({msg:'Hover over me'}, '<p><span data-bind="msg"/></p>', {
+ 'mouseenter span': function() {
+ this.model.set({msg:'Hovered!'});
+ },
+ 'change:msg': function() {
+ this.view.$().append('<p>Model changed!</p>');
+ }
+ });
+ $$.document.append(catcher);
+<div class="demo"></div>
+
+## [Auto-proxying](#auto-proxy)
+
+All user-defined controllers initialized by the factory function `$$()` have their `this` auto-proxied to the owner MVC object, for quick access and consistent behavior no matter what context:
+
+ :::javascript
+ var obj = $$({msg:'I only exist because of auto-proxying'}, '<div/>', {
+ 'myHandler': function(){
+ this.view.$().html( this.model.get('msg') );
+ }
+ });
+ $$.document.append(obj);
+
+ // Without auto-proxying the 'this' in myHandler would be 'window'
+ setTimeout(obj.controller.myHandler, 100);
+<div class="demo"></div>
+
+If necessary, properties from the original context should be passed to the controller function.
+
+## [Inheritance](#inheritance)
+
+Agility adopts prototype-based ([differential](http://en.wikipedia.org/wiki/Differential_inheritance)) inheritance. To create a new Agility object from an existing one, pass the latter as the prototype argument to the [factory function](#factory); additional model, view, controller initializers are passed as usual:
+
+ :::javascript
+ var proto = $$({}, '<p data-bind="msg"/>', '& {color:red}');
+ var obj = $$(proto, {msg:'Hey there!'});
+ $$.document.append(obj);
+<div class="demo"></div>
+
+Since derived objects reuse as much of their ancestors as possible, you can create large numbers of descendants from a prototype without worrying about memory bloat due to redundant storage:
+
+ :::javascript
+ // Prototype of cell object with empty model
+ var cell = $$({
+ model: {},
+ view: {
+ format: '<div data-bind="num"/>',
+ style: '& { float:left; width:50px; cursor:pointer; text-align:center; }\
+ &:hover { background:#ccf }'
+ },
+ controller: {
+ 'click &': function(){
+ this.destroy();
+ }
+ }
+ });
+ // Matrix of cell objects, all stemming from prototype above
+ var matrix = $$({}, '<div>Click to erase number: <div id="wrapper"/></div>', {
+ 'create': function(){
+ for (var i=0;i<500;i++) {
+ // Inherits from cell
+ var newCell = $$(cell, {num:i});
+ this.append(newCell, '#wrapper');
+ }
+ }
+ });
+ $$.document.append(matrix);
+<div class="demo"></div>
+
+
+## [Persistence](#persistence)
+
+Model persistence, such as server-side and local HTML5 storage, is bundled with the library as the plugin [persist](#persist). This is not included in the core so as to keep it as simple as possible.
+
+# [Reference](#reference)
+
+## [Factory $$()](#factory)
+
+_Creates a new MVC object from the given model, view, and controller arguments, and optionally a prototype object._
+
+**Compact syntax:**
+
+ :::javascript
+ $$([model [,view-format [,controller]]])
+ $$([model [,view-format [, view-style [,controller]]]])
+ $$([model [,view [,controller]]])
+ $$(prototype, [model [,view-format [,controller]]])
+ $$(prototype, [model [,view-format [, view-style [,controller]]]])
+ $$(prototype, [model [,view [,controller]]])
+
+**Verbose syntax:**
+
+ :::javascript
+ $$([prototype,] [{
+ model: {...},
+ view: {...},
+ controller: {...},
+ user_defined_property: {...}
+ }])
+
+where:
+
++ `model`: Javascript object containing the model key-value pairs;
++ `view-format`: String specifying HTML [format](#format-style) of the view;
++ `view-style`: String specifying CSS [style](#format-style) of the view;
++ `view`: Javascript object containing `format` and/or `style` properties;
++ `controller`: Javascript object containing named functions that match [event types](#events);
++ `prototype`: Agility object to serve as the prototype for new object;
++ `user_defined_property`: Any additional user-defined method/property for the object.
+
+**Examples:**
+
+Different view initialization methods:
+
+ :::javascript
+ // One string: format
+ var person1 = $$({name:'Foo Bar'}, '<div data-bind="name"/>');
+ // Two strings: format, style
+ var person2 = $$({name:'Foo Bar'}, '<div data-bind="name"/>', '& { color:red; font-weight:bold; }');
+ // Object: format, style
+ var person3 = $$(
+ { name:'Foo Bar' },
+ { format: '<div data-bind="name"/>', style: '& { color:blue; }' }
+ );
+ // Verbose
+ var person4 = $$({
+ model: {
+ name: 'Foo Bar'
+ },
+ view: {
+ format: '<div data-bind="name"/>',
+ style: '& { color:green; font-style:italic; }'
+ }
+ });
+ $$.document.append(person1);
+ $$.document.append(person2);
+ $$.document.append(person3);
+ $$.document.append(person4);
+<div class="demo"></div>
+
+Specifying controller functions - compact:
+
+ :::javascript
+ var button = $$({}, '<p><button>Click me</button></p>', {
+ 'click button': function(){
+ alert('You clicked me!');
+ }
+ });
+ $$.document.append(button);
+<div class="demo"></div>
+
+and verbose:
+
+ :::javascript
+ var dataHolder = $$({
+ model: {
+ first:'Joe',
+ last:'Doe'
+ },
+ view: {
+ format: '<p>Wait...</p>'
+ },
+ controller: {
+ 'change:first': function(){
+ alert('First name changed!');
+ }
+ }
+ });
+ $$.document.append(dataHolder);
+
+ setTimeout(function(){
+ dataHolder.model.set({first:'Mary'});
+ }, 2000);
+<div class="demo"></div>
+
+
+
+
+
+
+
+
+
+
+## [Core methods](#methods-core)
+
+### [.bind()](#core-bind)
+
+_Binds function to event._
+
+**Syntax:**
+
+ :::javascript
+ .bind(event, fn)
+
++ `event`: String specifying event type. See [events](#events) section for event syntax.
++ `fn`: function to be called upon event triggering.
+
+**Returns:**
+
+Owner Agility object (for chainable calls).
+
+### [.trigger()](#core-trigger)
+
+_Triggers event, optionally passing parameters to listeners._
+
+**Syntax:**
+
+ :::javascript
+ .trigger(event [,params])
+
++ `event`: String specifying event type. See [events](#events) section for event syntax.
++ `params`: parameters to be passed to listeners as function arguments.
+
+**Returns:**
+
+Owner Agility object (for chainable calls).
+
+### [.destroy()](#core-destroy)
+
+_Erases self view, removes self from parent container._
+
+**Syntax:**
+
+ :::javascript
+ .destroy()
+
+**Returns:**
+
+Nothing.
+
+
+
+
+### [&mdash;](#container)
+
+The methods below are specific to the object container.
+
+
+
+
+### [.append()](#core-append)
+
+_Adds an Agility object to the object's container, and appends its view to object's view._
+
+**Syntax:**
+
+ :::javascript
+ .append(object [,selector])
+
++ `object`: The Agility object to be added;
++ `selector`: A jQuery selector specifying where the object's root element should be appended in the object's view. Will append to root element if undefined.
+
+**Returns:**
+
+Owner Agility object (for chainable calls).
+
+### [.prepend()](#core-prepend)
+
+_Adds an Agility object to the object's container, and prepends its view to object's view._
+
+**Syntax:**
+
+ :::javascript
+ .prepend(object [,selector])
+
++ `object`: The Agility object to be added;
++ `selector`: A jQuery selector specifying where the object's root element should be prepended in the object's view. Will prepend to root element if undefined.
+
+**Returns:**
+
+Owner Agility object (for chainable calls).
+
+
+### [.remove()](#core-remove)
+
+_Removes an Agility object from the object's container. [This function should rarely be invoked by the user; call instead `.destroy()` within the object to be removed]._
+
+**Syntax:**
+
+ :::javascript
+ .remove(id)
+
++ `id`: id of the object to be removed (accessed via `._id` property).
+
+**Returns:**
+
+Owner Agility object (for chainable calls).
+
+### [.each()](#core-each)
+
+_Iterates over each Agility object in the object's container._
+
+**Syntax:**
+
+ :::javascript
+ .each(fn)
+
+where:
+
++ `fn`: Function to be called within the context of each contained object. Access the object via `this`.
+
+**Returns:**
+
+Owner Agility object (for chainable calls).
+
+### [.empty()](#core-empty)
+
+_Removes all Agility objects from object's container by issuing a `.destroy()` for each contained object._
+
+**Syntax:**
+
+ :::javascript
+ .empty()
+
+**Returns:**
+
+Owner Agility object (for chainable calls).
+
+### [.size()](#core-size)
+
+_Returns number of objects within the object's container._
+
+**Syntax:**
+
+ :::javascript
+ .size()
+
+**Returns:**
+
+Number of Agility objects in the object's container.
+
+
+
+
+
+
+## [Model methods](#methods-model)
+
+### [.model.set()](#model-set)
+
+_Sets the model data. If model already exists, it's extended._
+
+**Syntax:**
+
+ :::javascript
+ .model.set(object [,params])
+
++ `object`: The Javascript object containing the data, e.g. `{name:'Joe Doe', birthday:'08/11/71'}`.
++ `params`: Use `{silent:true}` to avoid firing a `change` event; use `{reset:true}` to overwrite model data (and not extend it).
+
+**Returns:**
+
+Owner Agility object (for chainable calls).
+
+### [.model.get()](#model-get)
+
+_Gets model data._
+
+**Syntax:**
+
+ :::javascript
+ .model.get([property])
+
++ `property`: Desired property, e.g. `'name'`.
+
+**Returns:**
+
+Desired property content if `property` is specified, or a Javascript object containing the entire model data if it's omitted.
+
+### [.model.reset()](#model-reset)
+
+_Resets model to its original value (at object creation time)._
+
+**Syntax:**
+
+ :::javascript
+ .model.reset()
+
+**Returns:**
+
+Owner Agility object (for chainable calls).
+
+### [.model.each()](#model-each)
+
+_Iterates over each model property._
+
+**Syntax:**
+
+ :::javascript
+ .model.each(fn)
+
++ `fn`: Function to be called with each model property, with arguments `fn(key, value)` where `key` is the property name, and `value` is its content.
+
+**Returns:**
+
+Owner Agility object (for chainable calls).
+
+### [.model.size()](#model-size)
+
+_Gets number of model properties._
+
+**Syntax:**
+
+ :::javascript
+ .model.size()
+
+**Returns:**
+
+Number of model properties.
+
+## [View methods](#methods-view)
+
+### [.view.$()](#view-jquery)
+
+_Shortcut to jQuery object corresponding to root element or to given selector in the current view._
+
+**Syntax:**
+
+ :::javascript
+ .view.$([selector])
+
++ `selector`: jQuery selector for the desired DOM element in the object's view.
+
+**Returns:**
+
+jQuery object of root element if no selector, jQuery object at given `selector` otherwise, restricted to the current view's DOM.
+
+### [.view.render()](#view-render)
+
+_Updates View's main jQuery object according to `.view.format`. Automatically called upon creation._
+
+**Syntax:**
+
+ :::javascript
+ .view.render()
+
+**Returns:**
+
+Owner Agility object (for chainable calls).
+
+### [.view.stylize()](#view-stylize)
+
+_Applies CSS dynamically according to `.view.style`. Automatically called upon creation._
+
+**Syntax:**
+
+ :::javascript
+ .view.stylize()
+
+**Returns:**
+
+Owner Agility object (for chainable calls).
+
+### [.view.sync()](#view-sync)
+
+_Synchronizes all view elements with model contents, according to established bindings. Automatically called upon creation._
+
+**Syntax:**
+
+ :::javascript
+ .view.sync()
+
+**Returns:**
+
+Owner Agility object (for chainable calls).
+
+
+## [Controller methods](#methods-controller)
+
+Built-in controllers are intended for internal use and typically shouldn't be called by the user.
+
+Please refer to the [factory function](#factory) and [event types](#events) for syntax and usage examples of user-defined controllers.
+
+## [Globals](#globals)
+
+### [$$.document](#globals-document)
+
+_Main Agility object representing the document's body._
+
+Typically you just `.append()` a new Agility object to it.
+
+# [Built-in plugins](#plugins)
+
+## [persist](#persist)
+
+The plugin `persist` lets you save and retrieve models to/from a storage server using a given adapter. Persistence is always manual, i.e. needs to be explicitly called by user.
+
+All methods fire the generic events:
+
++ `persist:start`: fired when starting a new request and no other requests are pending.
++ `persist:stop`: fired after all pending requests have stopped.
++ `persist:error`: fired when a persistence error has occurred.
+
+as well as the method-specific events:
+
++ `persist:METHOD:success`: fired after `METHOD` has successfully completed request.
++ `persist:METHOD:error`: fired if `METHOD` gave rise to an error.
+
+### [.persist()](#persist-persist)
+
+_Initializes persistence plugin, creates persistence methods for owner object._
+
+**Syntax:**
+
+ :::javascript
+ .persist([adapter, params])
+
+where:
+
++ `adapter`: Function containing the implementation of the persistence algorithms.
++ `params`: Parameters to be passed to adapter. Requires at least `{collection:'collection_name'}`.
+
+If the adapter-params pair is not given, the only method that can be invoked is [gather](#persist-gather).
+
+**Returns:**
+
+Owner Agility object (for chainable calls).
+
+### [.load()](#persist-load)
+
+_Refreshes model from server, using the id in the model property `id`._
+
+**Syntax:**
+
+ :::javascript
+ .load()
+
+**Examples:**
+
+Loads model from server:
+
+ :::javascript
+ var person = $$({id:123}, '<p>Name: <span data-bind="name"/></p>').persist($$.adapter.restful, {collection:'people'});
+
+ $$.document.append(person);
+ person.load();
+<div class="demo"></div>
+
+**Returns**
+
+Owner Agility object (for chainable calls), with updated model.
+
+### [.save()](#persist-save)
+
+_Updates model on the server if `id` is present in the model, creates a new resource otherwise._
+
+**Syntax:**
+
+ :::javascript
+ .save()
+
+If the resource is to be created (i.e. model has no `id`), the server is expected to send back the new `id` either in the body, e.g.
+
+ :::text
+ {"id":123}
+
+or in the `Location` header as the new resource URL, e.g.:
+
+ :::text
+ Location: http://your-site.com/api/people/123
+
+Agility will parse either to extract the new `id`, and set the model accordingly. That way, further calls to `.save()` will update the model on the server.
+
+**Examples:**
+
+Creates new model on server:
+
+ :::javascript
+ var person = $$({name:'Joe Doe'}, '<p>Name: <span data-bind="name"/></p>').persist($$.adapter.restful, {collection:'people'});
+
+ $$.document.append(person);
+ person.save();
+
+Updates model on server:
+
+ :::javascript
+ var person = $$({id:123, name:'Joe Doe'}, '<p>Name: <span data-bind="name"/></p>').persist($$.adapter.restful, {collection:'people'});
+
+ $$.document.append(person);
+ person.save(); // will update, since 'id' exists
+
+**Returns**
+
+Owner Agility object (for chainable calls), with new model `id` (if created new resource).
+
+### [.erase()](#persist-erase)
+
+_Erases model from server, using the `id` given in the model._
+
+**Syntax:**
+
+ :::javascript
+ .erase()
+
+**Returns**
+
+Owner Agility object (for chainable calls).
+
+### [.gather()](#persist-gather)
+
+_Loads a collection of models and appends/prepends into container, using given prototype._
+
+Each gathered MVC object will be added to the container, appended/prepended to the view (depending on specified method), and will be a direct descendant of given prototype object. All persistence information, including collection name, should be initialized in the prototype object.
+
+**Syntax:**
+
+ :::javascript
+ .gather(proto, method, [,selector] [,query])
+
+where:
+
++ `proto`: Prototypal Agility object with `persist` already initialized.
++ `method`: String containing name of method to be invoked with each new Agility object to be added. Typically `'append'` or `'prepend'`.
++ `selector`: jQuery selector indicating where the view of `proto` should be appended. Will append to root element if omitted.
++ `query`: Javascript object containing parameters to be passed to the adapter for e.g. HTTP queries, like `{orderBy:'name'}`.
+
+**Examples:**
+
+Loads a collection of persons from server:
+
+ :::javascript
+ // Prototype
+ var person = $$({}, '<li data-bind="name"/>').persist($$.adapter.restful, {collection:'people'});
+
+ // Container
+ var people = $$({}, '<div>People: <ul/></div>').persist();
+ $$.document.append(people);
+
+ people.gather(person, 'append', 'ul');
+<div class="demo"></div>
+
+Same as above, with load button and "Loading..." Ajax message:
+
+ :::javascript
+ // Prototype
+ var person = $$({}, '<li data-bind="name"/>').persist($$.adapter.restful, {collection:'people'});
+
+ // Container
+ var people = $$({
+ model: {},
+ view: {
+ format:
+ '<div>\
+ <span>Loading ...</span>\
+ <button>Load people</button><br/><br/>\
+ People: <ul/>\
+ </div>',
+ style:
+ '& {position:relative}\
+ & span {position:absolute; top:0; right:0; padding:3px 6px; background:red; color:white; display:none; }'
+ },
+ controller: {
+ 'click button': function(){
+ this.empty();
+ this.gather(person, 'append', 'ul');
+ },
+ 'persist:start': function(){
+ this.view.$('span').show();
+ },
+ 'persist:stop': function(){
+ this.view.$('span').hide();
+ }
+ }
+ }).persist();
+ $$.document.append(people);
+<div class="demo"></div>
+
+**Returns**
+
+Owner Agility object (for chainable calls), with container filled with new `proto` descendants.
+
+### [$$.adapter.restful](#persist-restful)
+
+_RESTful adapter._
+
+This adapter sends `GET`, `POST`, etc requests as per [RESTful specs](http://en.wikipedia.org/wiki/Representational_State_Transfer), and expects JSON responses.
+
+The default base URL is `api/`, but it can be overridden at initialization time with the parameter `{baseUrl:'your_url/'}` passed to `persist()`. The collection name and/or resource `id` will be appended to form URLs like
+
+ :::text
+ api/resource
+ api/resource/123
View
99 docs/_gallery.html
@@ -0,0 +1,99 @@
+<!DOCTYPE html>
+
+<!--
+
+**********************************************************************
+*
+* DO NOT MODIFY THIS FILE IF ITS NAME DOES NOT START WITH '_' !
+*
+* Instead change the corresponding _file.md or _file.html in docs/
+* and follow the build instructions in docs/README.md
+*
+**********************************************************************
+
+-->
+
+
+<html>
+<head>
+ <title>
+ Agility.js - Gallery
+ </title>
+ <meta charset="utf-8" />
+
+ <link rel="icon" type="image/png" href="favicon.png">
+ <link type="text/css" href="highlight.css" rel="stylesheet" />
+
+ <style type="text/css">
+ html,body { background:#ebebeb; margin:0; padding:0; padding-bottom:30px; font-family:'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; font-size:16px; }
+ div.center { width:850px; margin-left:auto; margin-right:auto; margin-top:30px; }
+ a { color:#0000A0; text-decoration:none; }
+ a:hover { text-decoration:underline; }
+ div#decor { height:8px; background:#0000A0; }
+ div#top { font-size:19px; color:#333; }
+ div#top a { font-size:15px; }
+ div#menu { text-align:center; }
+ div#menu li { display:inline; margin-right:40px; }
+ div#box {
+ margin-top:0;
+ background:white;
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ border-radius: 3px;
+ -moz-box-shadow: 0px 0px 8px #999;
+ -webkit-box-shadow: 0px 0px 8px #999;
+ box-shadow: 0px 0px 8px #999;
+ }
+ div#box-wrapper { padding:10px 20px; background:white; }
+ div#box h1 { border-top:10px solid #0000A0; }
+ div#box h2 { border-top:4px solid #A0A0D0; }
+ div#box h3 { border-top:1px solid #A0A0D0; }
+ div#box-wrapper img { border:2px solid black; }
+
+ .demo { margin-top:-10px; margin-left:10px; font-size:80%; }
+ div#demo-wrapper { font-size:90%; font-family:Arial, sans; }
+ div#demo-wrapper #msg { margin-bottom:10px; }
+ iframe { width:600px; height:400px; border:1px solid #333; }
+ #simplemodal-overlay { background-color:#000; }
+ #simplemodal-container { background-color:white; border:8px solid #555; padding:12px; }
+ </style>
+
+ <script type="text/javascript">
+ if (window.location.href.search(/\?x/) < 0) {
+ var _gaq = _gaq || [];
+ _gaq.push(['_setAccount', 'UA-924459-9']);
+ _gaq.push(['_trackPageview']);
+ (function() {
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+ })();
+ }
+ </script>
+
+</head>
+
+<body>
+
+ <div id="decor"></div>
+
+ <a href="http://github.com/arturadib/agility"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://gs1.wac.edgecastcdn.net/80460E/assets/img/30f550e0d38ceb6ef5b81500c64d970b7fb0f028/687474703a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f6f72616e67655f6666373630302e706e67" alt="Fork me on GitHub"></a>
+
+ <div id="top" class='center'>
+ <div><a href="../" style="text-decoration:none; border:none;"><img src="logo.png" style="border:none;"/></a></div>
+ <div><em>Gallery</em></div>
+ <p><a href="../" class="nav">&laquo; Back to Home</a></p>
+ </div>
+
+ <div id="box" class='center'>
+ <div id='box-wrapper'>
+
+<!-- __INSERT_POINT__ -->
+
+
+ </div>
+ </div>
+
+</body>
+
+</html>
View
14 docs/_gallery.md
@@ -0,0 +1,14 @@
+## [The Wall](http://thewall.agilityjs.com)
+
+_The Wall is a minimal Twitter clone, where everyone can post anonymously to a virtual "wall". It illustrates most features offered by Agility, including server-side persistence._
+
++ Demo: [http://thewall.agilityjs.com](http://thewall.agilityjs.com)
++ Full source: [https://github.com/arturadib/thewall/blob/master/public/app.js](https://github.com/arturadib/thewall/blob/master/public/app.js)
++ Screenshot:
+
+![Screenshot](gallery/thewall.png)
+
+
+### User submissions
+
+Have you created an awesome project that showcases Agility's capabilities? [Fork this site](https://github.com/arturadib/agility/tree/gh-pages), add a brief description of your project, and send a pull request. We'd love to see what you can do with Agility!
View
152 docs/_index.html
@@ -0,0 +1,152 @@
+<!DOCTYPE html>
+
+<!--
+
+**********************************************************************
+*
+* DO NOT MODIFY THIS FILE IF ITS NAME DOES NOT START WITH '_' !
+*
+* Instead change the corresponding _file.md or _file.html in docs/
+* and follow the build instructions in docs/README.md
+*
+**********************************************************************
+
+-->
+
+
+<html>
+<head>
+ <title>
+ Agility.js Javascript MVC library
+ </title>
+ <base href="docs/" target="_self" />
+
+ <meta charset="utf-8" />
+ <meta name="description" content="Agility.js is an MVC library for Javascript that lets you write maintainable and reusable browser code without the verbose or infrastructural overhead found in other MVC libraries."/>
+ <meta name="author" content="Artur Adib"/>
+
+ <link rel="icon" type="image/png" href="favicon.png">
+ <link type="text/css" href="highlight.css" rel="stylesheet" />
+
+ <style type="text/css">
+ html,body { background:#ebebeb; margin:0; padding:0; padding-bottom:15px; font-family:'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; font-size:16px; line-height:150%; }
+ pre { line-height:125%; }
+ div.center { width:850px; margin-left:auto; margin-right:auto; margin-top:30px; }
+ a { color:#0000A0; text-decoration:none; }
+ a:hover { text-decoration:underline; }
+ div#decor { height:8px; background:#0000A0; }
+ div#top { font-size:19px; color:#333; }
+ div#menu { text-align:center; }
+ div#menu li { display:inline; margin-right:40px; }
+ div#box {
+ margin-top:0;
+ background:white;
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ border-radius: 3px;
+ -moz-box-shadow: 0px 0px 8px #999;
+ -webkit-box-shadow: 0px 0px 8px #999;
+ box-shadow: 0px 0px 8px #999;
+ }
+ div#box-wrapper { padding:10px 20px; background:white; }
+ div#box-wrapper .download { background:#FFFFCC; padding:5px 10px; text-align:center;}
+
+ .demo { margin-top:-10px; margin-left:10px; font-size:80%; }
+ div#demo-wrapper { font-size:90%; font-family:Arial, sans; }
+ div#demo-wrapper #msg { margin-bottom:10px; }
+ iframe { width:600px; height:400px; border:1px solid #333; }
+ #simplemodal-overlay { background-color:#000; }
+ #simplemodal-container { background-color:white; border:8px solid #555; padding:12px; }
+ </style>
+
+ <script type="text/javascript">
+ if (window.location.href.search(/\?x/) < 0) {
+ var _gaq = _gaq || [];
+ _gaq.push(['_setAccount', 'UA-924459-9']);
+ _gaq.push(['_trackPageview']);
+ (function() {
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+ })();
+ }
+ </script>
+
+</head>
+
+<body>
+
+ <div id="decor"></div>
+
+ <a href="http://github.com/arturadib/agility"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://a248.e.akamai.net/assets.github.com/img/30f550e0d38ceb6ef5b81500c64d970b7fb0f028/687474703a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f6f72616e67655f6666373630302e706e67" alt="Fork me on GitHub"></a>
+
+ <div id="top" class='center'>
+ <div><img src="logo.png"/></div>
+ <div>Ship more. Code and maintain less.</div>
+ </div>
+
+ <div id="menu" class='center'>
+ <ul>
+ <li><a href="/">Home</a></li>
+ <li><a href="docs.html">Docs</a></li>
+ <li><a href="gallery.html">Gallery</a></li>
+ <li><a href="http://jsbin.com/agility/edit" target="_blank">Try it now!</a></li>
+ <li><a href="https://github.com/arturadib/agility" target="_blank"><b>Contribute</b></a></li>
+ </ul>
+ </div>
+
+ <div id="box" class='center'>
+ <div id='box-wrapper'>
+
+<!-- __INSERT_POINT__ -->
+
+
+ </div>
+ </div>
+
+
+ <p style="text-align:center; margin-top:30px; font-size:90%;">Agility.js is a project of <a href="http://arturadib.com">Artur Adib</a></p>
+
+ <div id="demo-wrapper" style="display:none"></div>
+
+ <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js" type="text/javascript" charset="utf-8"></script>
+ <script src="jquery.simplemodal.1.4.1.min.js" type="text/javascript" charset="utf-8"></script>
+ <script type="text/javascript">
+ (function($){
+ //
+ // Each "Run demo"
+ //
+ $('div.demo').each(function(){
+ var code = $(this).prevAll('.codehilite:first').text();
+ //
+ // On demo click
+ //
+ var $link = $("<button>Run demo</button>").click(function(){
+ var $frame = $('<iframe \/>').attr('src', '_demo.html');
+ $('div#demo-wrapper')
+ .hide()
+ .empty()
+ .append('<div id="msg">Loading ...</div>')
+ .append($frame)
+ .modal({overlayClose: true});
+ //
+ // Insert dynamic frame content (pure jQuery doesn't work with script tags)
+ //
+ $frame.load(function(){
+ $('div#demo-wrapper #msg').html('Press <b>esc</b> or click outside box to close')
+ var frameWindow = this.contentWindow ? this.contentWindow : this.contentDocument.defaultView;
+ var script = frameWindow.document.createElement("script");
+ script.type = "text/javascript";
+ script.text = '\n'+code+'\n';
+ frameWindow.document.body.appendChild(script);
+ });
+ return false;
+ }); // demo click
+ $(this).append($link);
+ }); // each demo
+ })(jQuery);
+ </script>
+
+</body>
+
+</html>
View
238 docs/_index.md
@@ -0,0 +1,238 @@
+
+Agility.js is an [MVC](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) library for Javascript that lets you write maintainable and reusable browser code without the verbose or infrastructural overhead found in [other](http://documentcloud.github.com/backbone/) [MVC](http://www.sproutcore.com/) [libraries](http://knockoutjs.com/). The goal is to enable developers to write web apps at least as quickly as with jQuery, while simplifying long-term maintainability through MVC objects.
+
+<div class='download'>
+ <div><b>Latest version: __VERSION__</b> | Compatible with IE7-8, Firefox, Chrome, Safari</div>
+ <div><a href="agility.min.js">Download minified js</a> (~4K packed + gzipped)</div>
+</div>
+
+The library itself is a small Javascript file (goal is to remain under 10K), and its only dependency is a recent version of jQuery (Zepto support coming soon). The project is licensed under the liberal [MIT license](https://github.com/arturadib/agility/blob/master/LICENSE).
+
+See the documentation for a more complete [list of features](docs.html#intro-features).
+
+## Quick tour
+
+Agility encourages (but does not require) writing your entire code in Javascript, that is, content (HTML), style (CSS), and behavior (JS) can all be contained within Javascript objects. The examples in this tour consist of such code. See [Getting started](docs.html#getting-started) in the docs for the HTML template adopted.
+
+### Object initialization
+
+Agility works with a single object type that contains a full model-view-controller stack. These MVC objects are built by passing initializers to the [factory function](docs.html#factory) `$$()`, which takes care of setting up default bindings, auto-proxying, etc. The example below initializes an MVC object with empty model, a simple view, and default controllers:
+
+ :::javascript
+ // Hello World
+ var message = $$({
+ model: {},
+ view: {
+ format: '<div>Hello World</div>'
+ },
+ controller: {}
+ });
+ $$.document.append(message);
+<div class="demo"></div>
+
+A more compact syntax is also supported. It's handy when dealing with simple objects:
+
+ :::javascript
+ // Hello World
+ // Compact syntax: initializers are passed in the order M, V, C:
+ var message = $$({}, '<div>Hello World</div>');
+ $$.document.append(message);
+<div class="demo"></div>
+
+### Model-view bindings
+
+The framework comes with a powerful model-view binder, so that views are always in sync with models and vice-versa. Establishing bindings is as simple as introducing a `data-bind` attribute in the desired DOM element; the factory function takes care of creating the necessary controllers:
+
+ :::javascript
+ // Bind model to element's HTML content
+ var message = $$({txt:"I'm text from a model"}, '<div data-bind="txt"/>');
+ $$.document.append(message);
+
+ // Bind model to element's attribute
+ var url = 'http://google.com/favicon.ico';
+ var icon = $$({path:url}, '<p>Image src from model: <img data-bind="src path"/></p>');
+ $$.document.append(icon);
+<div class="demo"></div>
+
+Bindings are always two-way (instant updates in both directions), and work with most input types:
+
+ :::javascript
+ // Two-way binding (checkbox)
+ var check = $$(
+ {a:false, b:true},
+ "<div> \
+ <input type='checkbox' name='test' data-bind='a'/> checked: <span data-bind='a'/><br/> \
+ <input type='checkbox' name='test' data-bind='b'/> checked: <span data-bind='b'/><br/> \
+ </div>"
+ );
+ $$.document.append(check);
+<div class="demo"></div>
+
+### Controller-event bindings
+
+User-defined controllers are bound to events by automatically matching function and event names. To handle DOM events, start with the event name followed by a DOM selector. Agility events (e.g. object creation, model change, etc) are never followed by a selector:
+
+ :::javascript
+ var person = $$({}, '<p><input type="text" data-bind="name"/> <span id="msg"/></p>', {
+ 'create': function(){
+ // Fired upon object creation
+ this.view.$('#msg').text('Enter name');
+ },
+ 'change': function(){
+ // Fired upon model change
+ this.view.$('#msg').text('Name changed to: '+this.model.get('name'));
+ },
+ 'focus input': function(){
+ // Fired upon DOM event 'focus' on element input
+ // 'this' is always auto-proxied to the owner MVC object
+ this.view.$('#msg').text('Focused on input!');
+ }
+ });
+ $$.document.append(person);
+<div class="demo"></div>
+
+### Inheritance and containers
+
+Agility adopts a simple object hierarchy model. Objects can serve as the prototype of other objects ([differential inheritance](http://en.wikipedia.org/wiki/Differential_inheritance)):
+
+ :::javascript
+ // Base object with empty model:
+ var proto = $$({}, '<p data-bind="name" style="color:red"/>');
+ // Derived objects with specified models:
+ var obj1 = $$(proto, {name:'Joe Doe'});
+ var obj2 = $$(proto, {name:'Foo Bar'});
+ $$.document.append(obj1);
+ $$.document.append(obj2);
+<div class="demo"></div>
+
+as well as containers for other Agility objects:
+
+ :::javascript
+ var counter = 0;
+ var item = $$({}, '<li>I\'m item #<span data-bind="id"/></li>');
+ var list = $$({}, '<div> <button>Click me</button> <ul></ul> </div>', {
+ 'click button': function(){
+ var newItem = $$(item, {id:counter});
+ this.prepend(newItem, 'ul'); // add object to container, prepend view at <ul>
+ counter++;
+ }
+ })
+ $$.document.append(list);
+<div class="demo"></div>
+
+### In-object content, style, and behavior
+
+Views don't require styles (CSS) to be declared in-object, but doing so leads to fully reusable objects and eliminates the need to maintain HTML and CSS files. For example, the object `clock` below can be copied-and-pasted into any Agility project - its content, style, and behavior are preserved without having to fish out HTML and CSS elements/selectors from different files:
+
+ :::javascript
+ var clock = $$({
+ model: {
+ time: '12:00:00'
+ },
+ view: {
+ format: '<span data-bind="time"/>',
+ style: '& { color:white; background:#88f; padding:4px 8px; }'
+ },
+ controller: {
+ 'create': function(){
+ var self = this;
+ setInterval(function(){
+ self.model.set({time: (new Date()).toLocaleTimeString()});
+ }, 1000);
+ }
+ }
+ });
+ $$.document.append(clock);
+<div class="demo"></div>
+
+### Persistence
+
+Server- and client-side persistence can be implemented through the built-in plugin [persist](docs.html#persist). You can setup and `load` a model from a RESTful service in one line (the view is automatically updated via model-view binding):
+
+ :::javascript
+ var person = $$({id:123}, '<p>Name: <span data-bind="name"/></p>', '& span {background:blue; color:white; padding:3px 6px;}');
+
+ // Initialize plugin with RESTful adapter, load model with above id:
+ person.persist($$.adapter.restful, {collection:'people'}).load();
+
+ $$.document.append(person);
+<div class="demo"></div>
+
+(View JSON server response used in the request above: [GET api/people/123](api/people/123)).
+
+You can also `gather` an entire collection of models and insert them as MVC objects (derived from a prototype) into an object's container, as illustrated below. This example also shows how Ajax "loading" icons/messages are implemented through two simple controllers:
+
+ :::javascript
+ //
+ // person: prototype (will be 'gathered' below)
+ //
+ var person = $$({}, '<li data-bind="name"/>').persist($$.adapter.restful, {collection:'people'});
+
+ //
+ // people: persons container
+ //
+ var people = $$({
+ model: {},
+ view: {
+ format:
+ '<div>\
+ <span>Loading ...</span>\
+ <button>Load people</button><br/><br/>\
+ People: <ul/>\
+ </div>',
+ style:
+ '& {position:relative}\
+ & span {position:absolute; top:0; right:0; padding:3px 6px; background:red; color:white; display:none; }'
+ },
+ controller: {
+ 'click button': function(){
+ this.empty();
+ this.gather(person, 'append', 'ul');
+ },
+ // Ajax loading message - start
+ 'persist:start': function(){
+ this.view.$('span').show();
+ },
+ // Ajax loading message - stop
+ 'persist:stop': function(){
+ this.view.$('span').hide();
+ }
+ }
+ }).persist(); // this makes .gather() available
+ $$.document.append(people);
+<div class="demo"></div>
+
+(View JSON server response used in the request above: [GET api/people/](api/people/)).
+
+
+### The infamous To-Do example
+
+Last but not least, no modern MVC library is complete without a simple To-Do list example. The fully functional example below has 17 lines of code:
+
+ :::javascript
+ //
+ // Item prototype
+ //
+ var item = $$({}, '<li><span data-bind="content"/> <button>x</button></li>', '& span { cursor:pointer; }', {
+ 'click span': function(){
+ var input = prompt('Edit to-do item:', this.model.get('content'));
+ if (!input) return;
+ this.model.set({content:input});
+ },
+ 'click button': function(){
+ this.destroy();
+ }
+ });
+
+ //
+ // List of items
+ //
+ var list = $$({}, '<div> <button id="new">New item</button> <ul></ul> </div>', {
+ 'click #new': function(){
+ var newItem = $$(item, {content:'Click to edit'});
+ this.append(newItem, 'ul'); // add to container, appending at <ul>
+ }
+ });
+
+ $$.document.append(list);
+<div class="demo"></div>
View
77 docs/agility.min.js
@@ -0,0 +1,77 @@
+/*
+
+ Agility.js
+ Copyright (c) Artur B. Adib, 2011
+ http://agilityjs.com
+
+ Licensed under the MIT license
+ http://www.opensource.org/licenses/mit-license.php
+
+*/
+
+(function(window,undefined){if(!window.jQuery){throw"agility.js: jQuery not found";}
+var document=window.document,location=window.location,$=jQuery,agility,util={},defaultPrototype={},idCounter=0,ROOT_SELECTOR='&';if(!Object.create||Object.create.toString().search(/native code/i)<0){Object.create=function(obj){var Aux=function(){};$.extend(Aux.prototype,obj);return new Aux();};}
+if(!Object.getPrototypeOf||Object.getPrototypeOf.toString().search(/native code/i)<0){if(typeof"test".__proto__==="object"){Object.getPrototypeOf=function(object){return object.__proto__;};}else{Object.getPrototypeOf=function(object){return object.constructor.prototype;};}}
+util.isAgility=function(obj){return obj._agility===true;};util.proxyAll=function(obj,dest){if(!obj||!dest){throw"agility.js: util.proxyAll needs two arguments";}
+for(var attr1 in obj){var proxied=obj[attr1];if(typeof obj[attr1]==='function'){proxied=obj[attr1]._noProxy?obj[attr1]:$.proxy(obj[attr1]._preProxy||obj[attr1],dest);proxied._preProxy=obj[attr1]._noProxy?undefined:(obj[attr1]._preProxy||obj[attr1]);obj[attr1]=proxied;}
+else if(typeof obj[attr1]==='object'){for(var attr2 in obj[attr1]){var proxied2=obj[attr1][attr2];if(typeof obj[attr1][attr2]==='function'){proxied2=obj[attr1][attr2]._noProxy?obj[attr1][attr2]:$.proxy(obj[attr1][attr2]._preProxy||obj[attr1][attr2],dest);proxied2._preProxy=obj[attr1][attr2]._noProxy?undefined:(obj[attr1][attr2]._preProxy||obj[attr1][attr2]);proxied[attr2]=proxied2;}}
+obj[attr1]=proxied;}}};util.reverseEvents=function(obj,eventType){var events=$(obj).data('events');if(events!==undefined&&events[eventType]!==undefined){var reverseEvents=[];for(var e in events[eventType]){if(!events[eventType].hasOwnProperty(e))continue;reverseEvents.unshift(events[eventType][e]);}
+events[eventType]=reverseEvents;}};util.size=function(obj){var size=0,key;for(key in obj){size++;}
+return size;};util.extendController=function(object){for(var controllerName in object.controller){(function(){var matches,extend,eventName,previousHandler,currentHandler,newHandler;if(typeof object.controller[controllerName]==='function'){matches=controllerName.match(/^(\~)*(.+)/);extend=matches[1];eventName=matches[2];if(!extend)return;previousHandler=object.controller[eventName]?(object.controller[eventName]._preProxy||object.controller[eventName]):undefined;currentHandler=object.controller[controllerName];newHandler=function(){if(previousHandler)previousHandler.apply(this,arguments);if(currentHandler)currentHandler.apply(this,arguments);};object.controller[eventName]=newHandler;delete object.controller[controllerName];}})();}};defaultPrototype={_agility:true,_container:{_insertObject:function(obj,selector,method){var self=this;if(!util.isAgility(obj)){throw"agility.js: append argument is not an agility object";}
+this._container.children[obj._id]=obj;this.trigger(method,[obj,selector]);obj.bind('destroy',function(event,id){self._container.remove(id);});return this;},append:function(obj,selector){return this._container._insertObject.call(this,obj,selector,'append');},prepend:function(obj,selector){return this._container._insertObject.call(this,obj,selector,'prepend');},after:function(obj,selector){return this._container._insertObject.call(this,obj,selector,'after');},before:function(obj,selector){return this._container._insertObject.call(this,obj,selector,'before');},remove:function(id){delete this._container.children[id];this.trigger('remove',id);return this;},each:function(fn){$.each(this._container.children,fn);return this;},empty:function(){this.each(function(){this.destroy();});return this;},size:function(){return util.size(this._container.children);}},_events:{parseEventStr:function(eventStr){var eventObj={type:eventStr},spacePos=eventStr.search(/\s/);if(spacePos>-1){eventObj.type=eventStr.substr(0,spacePos);eventObj.selector=eventStr.substr(spacePos+1);}
+return eventObj;},bind:function(eventStr,fn){var eventObj=this._events.parseEventStr(eventStr);if(eventObj.selector){if(eventObj.selector===ROOT_SELECTOR){this.view.$().bind(eventObj.type,fn);}
+else{this.view.$().delegate(eventObj.selector,eventObj.type,fn);}}
+else{$(this._events.data).bind(eventObj.type,fn);}
+return this;},trigger:function(eventStr,params){var eventObj=this._events.parseEventStr(eventStr);if(eventObj.selector){if(eventObj.selector===ROOT_SELECTOR){this.view.$().trigger(eventObj.type,params);}
+else{this.view.$().find(eventObj.selector).trigger(eventObj.type,params);}}
+else{$(this._events.data).trigger('_'+eventObj.type,params);util.reverseEvents(this._events.data,'pre:'+eventObj.type);$(this._events.data).trigger('pre:'+eventObj.type,params);util.reverseEvents(this._events.data,'pre:'+eventObj.type);$(this._events.data).trigger(eventObj.type,params);$(this._events.data).trigger('post:'+eventObj.type,params);}
+return this;}},model:{set:function(arg,params){var self=this;var modified=[];if(typeof arg==='object'){if(params&&params.reset){this.model._data=$.extend({},arg);}
+else{$.extend(this.model._data,arg);}
+for(var key in arg){modified.push(key);}}
+else{throw"agility.js: unknown argument type in model.set()";}
+if(params&&params.silent===true)return this;this.trigger('change');$.each(modified,function(index,val){self.trigger('change:'+val);});return this;},get:function(arg){if(arg===undefined){return this.model._data;}
+if(typeof arg==='string'){return this.model._data[arg];}
+throw'agility.js: unknown argument for getter';},reset:function(){this.model.set(this.model._initData,{reset:true});return this;},size:function(){return util.size(this.model._data);},each:function(fn){$.each(this.model._data,fn);return this;}},view:{format:'<div/>',style:'',$:function(selector){return(!selector||selector===ROOT_SELECTOR)?this.view.$root:this.view.$root.find(selector);},render:function(){if(this.view.format.length===0){throw"agility.js: empty format in view.render()";}
+if(this.view.$root.size()===0){this.view.$root=$(this.view.format);}
+else{this.view.$root.html($(this.view.format).html());}
+if(this.view.$root.size()===0){throw'agility.js: could not generate html from format';}
+return this;},_parseBindStr:function(str){var obj={key:null,attr:[]},pairs=str.split(','),regex=/(\w+)(?:\s+(\w+))?/,matched;if(pairs.length>0){matched=pairs[0].match(regex);if(matched){if(typeof(matched[2])==="undefined"||matched[2]===""){obj.key=matched[1];}else{obj.attr.push({attr:matched[1],attrVar:matched[2]});}}
+if(pairs.length>1){for(var i=1;i<pairs.length;i++){matched=pairs[i].match(regex);if(matched){if(typeof(matched[2])!=="undefined"){obj.attr.push({attr:matched[1],attrVar:matched[2]});}}}}}
+return obj;},bindings:function(){var self=this;var $rootNode=this.view.$().filter('[data-bind]');var $childNodes=this.view.$('[data-bind]');var createAttributePairClosure=function(bindData,node,i){var attrPair=bindData.attr[i];return function(){node.attr(attrPair.attr,self.model.get(attrPair.attrVar));};};$rootNode.add($childNodes).each(function(){var $node=$(this);var bindData=self.view._parseBindStr($node.data('bind'));var bindAttributesOneWay=function(){if(bindData.attr){for(var i=0;i<bindData.attr.length;i++){self.bind('_change:'+bindData.attr[i].attrVar,createAttributePairClosure(bindData,$node,i));}}};if($node.is('input[type="checkbox"]')){self.bind('_change:'+bindData.key,function(){$node.prop("checked",self.model.get(bindData.key));});$node.change(function(){var obj={};obj[bindData.key]=$(this).prop("checked");self.model.set(obj);});bindAttributesOneWay();}
+else if($node.is('select')){self.bind('_change:'+bindData.key,function(){var nodeName=$node.attr('name');var modelValue=self.model.get(bindData.key);$node.val(modelValue);});$node.change(function(){var obj={};obj[bindData.key]=$node.val();self.model.set(obj);});bindAttributesOneWay();}
+else if($node.is('input[type="radio"]')){self.bind('_change:'+bindData.key,function(){var nodeName=$node.attr('name');var modelValue=self.model.get(bindData.key);$node.siblings('input[name="'+nodeName+'"]').filter('[value="'+modelValue+'"]').prop("checked",true);});$node.change(function(){if(!$node.prop("checked"))return;var obj={};obj[bindData.key]=$node.val();self.model.set(obj);});bindAttributesOneWay();}
+else if($node.is('input[type="search"]')){self.bind('_change:'+bindData.key,function(){$node.val(self.model.get(bindData.key));});$node.keypress(function(){setTimeout(function(){var obj={};obj[bindData.key]=$node.val();self.model.set(obj);},50);});bindAttributesOneWay();}
+else if($node.is('input[type="text"], textarea')){self.bind('_change:'+bindData.key,function(){$node.val(self.model.get(bindData.key));});$node.change(function(){var obj={};obj[bindData.key]=$(this).val();self.model.set(obj);});bindAttributesOneWay();}
+else{if(bindData.key){self.bind('_change:'+bindData.key,function(){if(self.model.get(bindData.key)){$node.text(self.model.get(bindData.key).toString());}else{$node.text('');}});}
+bindAttributesOneWay();}});return this;},sync:function(){var self=this;this.model.each(function(key,val){self.trigger('_change:'+key);});if(this.model.size()>0){this.trigger('_change');}
+return this;},stylize:function(){var objClass,regex=new RegExp(ROOT_SELECTOR,'g');if(this.view.style.length===0||this.view.$().size()===0){return;}
+if(this.view.hasOwnProperty('style')){objClass='agility_'+this._id;var styleStr=this.view.style.replace(regex,'.'+objClass);$('head',window.document).append('<style type="text/css">'+styleStr+'</style>');this.view.$().addClass(objClass);}
+else{var ancestorWithStyle=function(object){while(object!==null){object=Object.getPrototypeOf(object);if(object.view.hasOwnProperty('style'))
+return object._id;}
+return undefined;};var ancestorId=ancestorWithStyle(this);objClass='agility_'+ancestorId;this.view.$().addClass(objClass);}
+return this;}},controller:{_create:function(event){this.view.stylize();this.view.bindings();this.view.sync();},_destroy:function(event){this._container.empty();this.view.$().remove();},_append:function(event,obj,selector){this.view.$(selector).append(obj.view.$());},_prepend:function(event,obj,selector){this.view.$(selector).prepend(obj.view.$());},_before:function(event,obj,selector){if(!selector)throw'agility.js: _before needs a selector';this.view.$(selector).before(obj.view.$());},_after:function(event,obj,selector){if(!selector)throw'agility.js: _after needs a selector';this.view.$(selector).after(obj.view.$());},_remove:function(event,id){},'_change':function(event){}},destroy:function(){this.trigger('destroy',this._id);},append:function(){this._container.append.apply(this,arguments);return this;},prepend:function(){this._container.prepend.apply(this,arguments);return this;},after:function(){this._container.after.apply(this,arguments);return this;},before:function(){this._container.before.apply(this,arguments);return this;},remove:function(){this._container.remove.apply(this,arguments);return this;},size:function(){return this._container.size.apply(this,arguments);},each:function(){return this._container.each.apply(this,arguments);},empty:function(){return this._container.empty.apply(this,arguments);},bind:function(){this._events.bind.apply(this,arguments);return this;},trigger:function(){this._events.trigger.apply(this,arguments);return this;}};agility=function(){var args=Array.prototype.slice.call(arguments,0),object={},prototype=defaultPrototype;if(typeof args[0]==="object"&&util.isAgility(args[0])){prototype=args[0];args.shift();}
+object=Object.create(prototype);object.model=Object.create(prototype.model);object.view=Object.create(prototype.view);object.controller=Object.create(prototype.controller);object._container=Object.create(prototype._container);object._events=Object.create(prototype._events);object._id=idCounter++;object._events.data={};object._container.children={};object.view.$root=$();object.model._data=prototype.model._data?$.extend(true,{},prototype.model._data):{};object._data=prototype._data?$.extend(true,{},prototype._data):{};if(args.length===0){}
+else if(args.length===1&&typeof args[0]==='object'&&(args[0].model||args[0].view||args[0].controller)){for(var prop in args[0]){if(prop==='model'){$.extend(object.model._data,args[0].model);}
+else if(prop==='view'){$.extend(object.view,args[0].view);}
+else if(prop==='controller'){$.extend(object.controller,args[0].controller);util.extendController(object);}
+else{object[prop]=args[0][prop];}}}
+else{if(typeof args[0]==='object'){$.extend(object.model._data,args[0]);}
+else if(args[0]){throw"agility.js: unknown argument type (model)";}
+if(typeof args[1]==='string'){object.view.format=args[1];}
+else if(typeof args[1]==='object'){$.extend(object.view,args[1]);}
+else if(args[1]){throw"agility.js: unknown argument type (view)";}
+if(typeof args[2]==='string'){object.view.style=args[2];args.splice(2,1);}
+if(typeof args[2]==='object'){$.extend(object.controller,args[2]);util.extendController(object);}
+else if(args[2]){throw"agility.js: unknown argument type (controller)";}}
+object.model._initData=$.extend({},object.model._data);util.proxyAll(object,object);object.view.render();for(var ev in object.controller){if(typeof object.controller[ev]==='function'){object.bind(ev,object.controller[ev]);}}
+object.trigger('create');return object;};agility.document=agility({view:{$:function(selector){return selector?$(selector,'body'):$('body');}},controller:{_create:function(){}}});agility.fn=defaultPrototype;agility.isAgility=function(obj){if(typeof obj!=='object')return false;return util.isAgility(obj);};window.agility=window.$$=agility;agility.fn.persist=function(adapter,params){var id='id';this._data.persist=$.extend({adapter:adapter},params);this._data.persist.openRequests=0;if(params&&params.id){id=params.id;}
+this.save=function(){var self=this;if(this._data.persist.openRequests===0){this.trigger('persist:start');}
+this._data.persist.openRequests++;this._data.persist.adapter.call(this,{type:this.model.get(id)?'PUT':'POST',id:this.model.get(id),data:this.model.get(),complete:function(){self._data.persist.openRequests--;if(self._data.persist.openRequests===0){self.trigger('persist:stop');}},success:function(data,textStatus,jqXHR){if(data[id]){self.model.set({id:data[id]},{silent:true});}
+else if(jqXHR.getResponseHeader('Location')){self.model.set({id:jqXHR.getResponseHeader('Location').match(/\/([0-9]+)$/)[1]},{silent:true});}
+self.trigger('persist:save:success');},error:function(){self.trigger('persist:error');self.trigger('persist:save:error');}});return this;};this.load=function(){var self=this;if(this.model.get(id)===undefined)throw'agility.js: load() needs model id';if(this._data.persist.openRequests===0){this.trigger('persist:start');}
+this._data.persist.openRequests++;this._data.persist.adapter.call(this,{type:'GET',id:this.model.get(id),complete:function(){self._data.persist.openRequests--;if(self._data.persist.openRequests===0){self.trigger('persist:stop');}},success:function(data,textStatus,jqXHR){self.model.set(data);self.trigger('persist:load:success');},error:function(){self.trigger('persist:error');self.trigger('persist:load:error');}});return this;};this.erase=function(){var self=this;if(this.model.get(id)===undefined)throw'agility.js: erase() needs model id';if(this._data.persist.openRequests===0){this.trigger('persist:start');}
+this._data.persist.openRequests++;this._data.persist.adapter.call(this,{type:'DELETE',id:this.model.get(id),complete:function(){self._data.persist.openRequests--;if(self._data.persist.openRequests===0){self.trigger('persist:stop');}},success:function(data,textStatus,jqXHR){self.destroy();self.trigger('persist:erase:success');},error:function(){self.trigger('persist:error');self.trigger('persist:erase:error');}});return this;};this.gather=function(proto,method,selectorOrQuery,query){var selector,self=this;if(!proto)throw"agility.js plugin persist: gather() needs object prototype";if(!proto._data.persist)throw"agility.js plugin persist: prototype doesn't seem to contain persist() data";if(query){selector=selectorOrQuery;}
+else{if(typeof selectorOrQuery==='string'){selector=selectorOrQuery;}
+else{selector=undefined;query=selectorOrQuery;}}
+if(this._data.persist.openRequests===0){this.trigger('persist:start');}
+this._data.persist.openRequests++;proto._data.persist.adapter.call(proto,{type:'GET',data:query,complete:function(){self._data.persist.openRequests--;if(self._data.persist.openRequests===0){self.trigger('persist:stop');}},success:function(data){$.each(data,function(index,entry){var obj=$$(proto,entry);if(typeof method==='string'){self[method](obj,selector);}});self.trigger('persist:gather:success',{data:data});},error:function(){self.trigger('persist:error');self.trigger('persist:gather:error');}});return this;};return this;};agility.adapter={};agility.adapter.restful=function(_params){var params=$.extend({dataType:'json',url:(this._data.persist.baseUrl||'api/')+this._data.persist.collection+(_params.id?'/'+_params.id:'')},_params);$.ajax(params);};})(window);
View
1 docs/api/people/123/index.html
@@ -0,0 +1 @@
+{"id":"123","name":"Joe Doe"}
View
1 docs/api/people/index.html
@@ -0,0 +1 @@
+[{"id":"123","name":"Joe Doe"},{"id":"1","name":"John Smith"},{"id":"2","name":"Zoe Doe"}]
View
BIN docs/architecture.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN docs/container.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
960 docs/docs.html
@@ -0,0 +1,960 @@
+<!DOCTYPE html>
+
+<!--
+
+**********************************************************************
+*
+* DO NOT MODIFY THIS FILE IF ITS NAME DOES NOT START WITH '_' !
+*
+* Instead change the corresponding _file.md or _file.html in docs/
+* and follow the build instructions in docs/README.md
+*
+**********************************************************************
+
+-->
+
+<html>
+<head>
+ <title>
+ Agility.js - Documentation
+ </title>
+ <meta charset="utf-8" />
+
+ <link rel="icon" type="image/png" href="favicon.png">
+ <link type="text/css" href="highlight.css" rel="stylesheet" />
+
+ <style type="text/css">
+ html,body { background:#ebebeb; margin:0; padding:0; padding-bottom:50px; font-family:'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; font-size:16px; line-height:150%; }
+ pre { line-height:125%; }
+ div.center { width:700px; margin-left:280px; margin-top:30px; }
+ a { color:#0000A0; text-decoration:none; }
+ a:hover { text-decoration:underline; }
+
+ div#menu-wrapper {
+ position:fixed;
+ top:0;
+ left:0;
+ bottom:0;
+ width:200px;
+ padding:5px 10px;
+ background:white;
+ border-right:1px solid #aac;
+ overflow:auto;
+ }
+
+ div#menu-wrapper a.nav { font-size:90%; }
+ div#menu div { margin-top:5px; margin-left:5px; }
+ div#menu a { color:#000; }
+ div#menu a.section { display:block; font-weight:bold; margin-top:10px; }
+ div#menu a.subsection { margin-left:2px; font-size:90%; }
+ div#menu a.func { margin-left:20px; font-size:80%; }
+
+ div#decor { height:8px; background:#0000A0; }
+ div#top { font-size:19px; }
+ div#box {
+ margin-top:20px;
+ background:white;
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ border-radius: 3px;
+ -moz-box-shadow: 0px 0px 8px #999;
+ -webkit-box-shadow: 0px 0px 8px #999;
+ box-shadow: 0px 0px 8px #999;
+ }
+ div#box-wrapper { padding:10px 20px; background:white; }
+ div#box h1 { border-top:10px solid #0000A0; }
+ div#box h2 { border-top:4px solid #A0A0D0; }
+ div#box h3 { border-top:1px solid #A0A0D0; }
+ div#box-wrapper .download { background:#FFFFCC; padding:5px 10px; text-align:center;}
+
+ div#box p img { display:block; margin-left:auto; margin-right:auto; }
+
+ .demo { margin-top:-10px; margin-left:10px; font-size:80%; }
+ div#demo-wrapper { font-size:90%; font-family:Arial, sans; }
+ div#demo-wrapper #msg { margin-bottom:10px; }
+ iframe { width:600px; height:400px; border:1px solid #333; }
+ #simplemodal-overlay { background-color:#000; }
+ #simplemodal-container { background-color:white; border:8px solid #555; padding:12px; }
+ </style>
+
+ <script type="text/javascript">
+ if (window.location.href.search(/\?x/) < 0) {
+ var _gaq = _gaq || [];
+ _gaq.push(['_setAccount', 'UA-924459-9']);
+ _gaq.push(['_trackPageview']);
+ (function() {
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+ })();
+ }
+ </script>
+
+</head>
+
+<body>
+
+ <div id="decor"></div>
+
+ <div id="menu-wrapper">
+ <a href="../" class="nav">&laquo; Back to Home</a>
+ <p></p>
+ <div id="menu"></div>
+ </div>
+
+ <div id="top" class='center'>
+ <div><a href="../" style="text-decoration:none; border:none;"><img src="logo.png" style="border:none;"/></a></div>
+ <div><em>Documentation</em></div>
+ </div>
+
+ <div id="box" class='center'>
+ <div id='box-wrapper'>
+
+<!-- __INSERT_POINT__ -->
+<p class='download'><b>Version: 0.1.1 (Dec 11 2011)</b></p>
+
+<h1><a href="#intro">Introduction</a></h1>
+<p>Agility is a <a href="http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller">Model-View-Controller</a> (MVC) library for client-side Javascript with some specific design principles:</p>
+<ul>
+<li>Convention over configuration (CoC);</li>
+<li>Don't repeat yourself (DRY); and</li>
+<li>Full object reusability.</li>
+</ul>
+<p>The overall goal is to improve code maintainability without sacrificing productivity. It is inspired by the principles behind <a href="http://en.wikipedia.org/wiki/Ruby_on_Rails">Ruby on Rails</a> and <a href="http://www.jquery.com">jQuery</a>.</p>
+<p>Agility's programming model is framed around the concept of self-contained MVC objects, where each object can be the prototype of, as well as the container of other MVC objects. This level of abstraction should encompass most applications.</p>
+<p>See the <a href="/">home page</a> for a quick overview of its syntax and usage.</p>
+<h2><a href="#intro-mvc">Why MVC?</a></h2>
+<p>One might wonder, since DOM-querying/Ajax libraries like jQuery make it so easy to whip up a dynamic web app, why bother with an additional layer of complexity?</p>
+<h3><a href="#intro-mvc-quick">Short answer</a></h3>
+<p>For those who have built a complex web app "organically", i.e. purely through DOM querying and manual Ajax calls, the answer is immediate: although you were able to get that app up to speed so quickly, you probably dread maintaining and relearning that intertwined code, and wish you had known better!</p>
+<h3><a href="#intro-mvc-long">Long answer</a></h3>
+<p>For those who haven't, some things you will likely end up doing with a pure jQuery-esque solution include: storing data in the DOM; querying the DOM to find your data; defining global callbacks to DOM events e.g. click/input change; having those callbacks neatly package your data to be sent to the server; retrieving data from the server and inserting them in the DOM with the right format and event handlers; etc.</p>
+<p>Though that's all fine initially, sooner or later you will start running into maintainability problems: storing data in the DOM is very brittle, e.g. changing an id/class or restructuring the DOM requires revisiting the code just about everywhere; DOM elements that are logically related and need to be always in sync require manual updates in all callbacks associated with them; global callbacks lead to name collisions and hence cumbersome function names, as well as difficulties in finding just what function is responsible for doing X or Y; defining functions that package data for, or present data after Ajax calls is unnecessarily repetitive; etc.</p>
+<p>One established answer to these problems is the <a href="http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller">Model-View-Controller</a> approach, where your app is organized in "large" objects each having different parts responsible for managing content (Model), format/style (View), and behavior (Controller). For example, a series of DOM input elements such as Name, Address, Phone, etc, related to the abstract concept of "person" become part of an object "person", whose model contains the raw data, view contains the HTML/CSS presentation, and controller contains the actions that will be called in response to events in the former two.</p>
+<p>MVC libraries like Agility typically offer built-in model, view, and controller methods that encompass most use case scenarios, so you don't have to reinvent the wheel or repeat yourself. That way all functions, formatting, and data related to an abstract concept (e.g. "person") are all in one place, the DOM is always in sync with the data, and the data is always ready to be sent to/retrieved from the server in one call.</p>
+<h2><a href="#intro-agility">Why Agility?</a></h2>
+<p>In response to the difficulties above, in the last few years <a href="http://www.sproutcore.com/">several</a> <a href="http://documentcloud.github.com/backbone/">superb</a> <a href="http://knockoutjs.com/">libraries</a> have been introduced to bring MVC (or a variant thereof) to the browser. Although they do a good job of refactoring apps in terms of content, format, and behavior - and hence lead to more maintainable code - they do so at the expense of development speed: Most are fairly verbose, require a considerable amount of repetition, and/or require large library includes.</p>
+<p>Agility borrows some useful concepts from the above frameworks, and makes rapid development a core part of its design principles. It's <a href="http://www.jquery.com">"write less, do more"</a> with maintainability, if you will.</p>
+<h3><a href="#intro-features">Features</a></h3>
+<p>Here are some of the features that Agility.js has aggregated into a single framework:</p>
+<ul>
+<li>Painless <a href="#bindings">two-way model-view bindings</a>;</li>
+<li>Implicit <a href="#events">controller-event bindings</a>;</li>
+<li><a href="#auto-proxy">Controller auto-proxying</a> for quick and consistent access to owner object;</li>
+<li><a href="#format-style">Format and style in-object</a> for "copy-and-paste" reusability;</li>
+<li>Small (&lt;10K) single-library include;</li>
+<li>Compact and efficient <a href="#factory">syntax</a>, inspired by jQuery;</li>
+<li>Pure <a href="#inheritance">prototypal inheritance</a>;</li>
+<li><a href="#intro-architecture">Strict MVC</a>: core has no additional concepts other than M, V, and C.</li>
+</ul>
+<h2><a href="#intro-architecture">Architecture</a></h2>
+<p>Agility's architecture follows one of the simplest MVC patterns: users define Controller functions, which make direct calls to, and handle events from Models and Views. The diagram below illustrates this.</p>
+<p><img alt="Architecture diagram" src="architecture.png" /></p>
+<p>So for example, when a user clicks on a DOM element, an event signal is sent from the View to any Controller functions listening to it, and these functions in turn can make direct calls to Model and View functions.</p>
+<p>Additionally, as illustrated below, every Agility object can serve as a container of other Agility objects. This is a natural abstraction for most applications, including simple lists, interactive tables, picture/video catalogs, etc, where each individual item might contain enough functionality (e.g. edit/remove buttons, mouse hover behavior, etc) to deserve its own MVC object. And because Agility objects are lightweight in memory (through pervasive use of prototypes), this comes at little performance cost.</p>
+<p><img alt="Hierarchy diagram" src="container.png" /></p>
+<h1><a href="#getting-started">Getting started</a></h1>
+<p>Agility.js depends on a recent version of jQuery (tested with 1.6.x, Zepto support coming soon). Other than that dependency, a single <code>&lt;script&gt;</code> tag in your Javascript code is all that's required, e.g.:</p>
+<div class="codehilite"><pre><span class="nt">&lt;script </span><span class="na">src=</span><span class="s">&quot;agility.js&quot;</span> <span class="na">type=</span><span class="s">&quot;text/javascript&quot;</span> <span class="na">charset=</span><span class="s">&quot;utf-8&quot;</span><span class="nt">&gt;&lt;/script&gt;</span>
+</pre></div>
+
+
+<p>Typically the <code>&lt;body&gt;</code> of your HTML will be empty, and will be populated programmatically by adding Agility objects to the <a href="#globals-document">global object</a> <code>$$.document</code>.</p>
+<p>Here's the full source of a "hello world" example:</p>
+<div class="codehilite"><pre><span class="cp">&lt;!DOCTYPE html&gt;</span>
+<span class="nt">&lt;html&gt;</span>
+
+<span class="nt">&lt;head&gt;</span>
+ <span class="nt">&lt;meta</span> <span class="na">http-equiv=</span><span class="s">&quot;Content-type&quot;</span> <span class="na">content=</span><span class="s">&quot;text/html; charset=utf-8&quot;</span><span class="nt">&gt;</span>
+ <span class="nt">&lt;title&gt;</span>Agility Hello World<span class="nt">&lt;/title&gt;</span>
+
+ <span class="nt">&lt;script </span><span class="na">src=</span><span class="s">&quot;jquery.min.js&quot;</span> <span class="na">type=</span><span class="s">&quot;text/javascript&quot;</span> <span class="na">charset=</span><span class="s">&quot;utf-8&quot;</span><span class="nt">&gt;&lt;/script&gt;</span>
+ <span class="nt">&lt;script </span><span class="na">src=</span><span class="s">&quot;agility.js&quot;</span> <span class="na">type=</span><span class="s">&quot;text/javascript&quot;</span> <span class="na">charset=</span><span class="s">&quot;utf-8&quot;</span><span class="nt">&gt;&lt;/script&gt;</span>
+<span class="nt">&lt;/head&gt;</span>
+
+<span class="nt">&lt;body&gt;</span>
+ <span class="nt">&lt;script </span><span class="na">type=</span><span class="s">&quot;text/javascript&quot;</span><span class="nt">&gt;</span>
+ <span class="kd">var</span> <span class="nx">message</span> <span class="o">=</span> <span class="nx">$$</span><span class="p">({</span><span class="nx">txt</span><span class="o">:</span><span class="s1">&#39;Hello World&#39;</span><span class="p">},</span> <span class="s1">&#39;&lt;div data-bind=&quot;txt&quot;/&gt;&#39;</span><span class="p">);</span>
+ <span class="nx">$$</span><span class="p">.</span><span class="nb">document</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="nx">message</span><span class="p">);</span>
+ <span class="nt">&lt;/script&gt;</span>
+<span class="nt">&lt;/body&gt;</span>
+
+<span class="nt">&lt;/html&gt;</span>
+</pre></div>
+
+
+<p>The above template has been used for all examples throughout this documentation.</p>
+<h2><a href="#creating-objects">Creating objects</a></h2>
+<p>Agility is framed around the notion of all-in-one MVC objects, or simply "Agility objects". Such objects are created through the <a href="#factory">factory function</a> <code>$$()</code>, either from scratch (by passing model, view, and/or controller initializers) or from a prototype object (by specifying an existing Agility object):</p>
+<div class="codehilite"><pre><span class="c1">// Create object from scratch:</span>
+<span class="kd">var</span> <span class="nx">proto</span> <span class="o">=</span> <span class="nx">$$</span><span class="p">({},</span> <span class="s1">&#39;&lt;p data-bind=&quot;name&quot; style=&quot;color:red&quot;/&gt;&#39;</span><span class="p">);</span>
+<span class="c1">// Create object from prototype object:</span>
+<span class="kd">var</span> <span class="nx">obj</span> <span class="o">=</span> <span class="nx">$$</span><span class="p">(</span><span class="nx">proto</span><span class="p">,</span> <span class="p">{</span><span class="nx">name</span><span class="o">:</span><span class="s1">&#39;Joe Doe&#39;</span><span class="p">});</span>
+<span class="nx">$$</span><span class="p">.</span><span class="nb">document</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="nx">obj</span><span class="p">);</span>
+</pre></div>
+
+
+<p><div class="demo"></div></p>
+<p>Refer to the examples in the <a href="/">home page</a> and elsewhere in this document for several different uses of the factory function, and the <a href="#factory">factory function reference</a> for syntax details.</p>
+<h2><a href="#bindings">Bindings</a></h2>
+<p>Agility offers painless two-way bindings to keep Models and Views in sync. Binding a given DOM element to a model property is as simple as specifying a <code>data-bind</code> attribute for the desired element:</p>
+<div class="codehilite"><pre><span class="c1">// Two-way input binding (text)</span>
+<span class="kd">var</span> <span class="nx">obj</span> <span class="o">=</span> <span class="nx">$$</span><span class="p">({</span><span class="nx">name</span><span class="o">:</span><span class="s1">&#39;Joe Doe&#39;</span><span class="p">},</span> <span class="s1">&#39;&lt;p&gt;&lt;input type=&quot;text&quot; data-bind=&quot;name&quot;/&gt; You typed: &lt;span data-bind=&quot;name&quot;/&gt;&lt;/p&gt;&#39;</span><span class="p">);</span>
+<span class="nx">$$</span><span class="p">.</span><span class="nb">document</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="nx">obj</span><span class="p">);</span>
+</pre></div>
+
+
+<p><div class="demo"></div></p>
+<div class="codehilite"><pre><span class="c1">// Two-way input binding (search)</span>
+<span class="kd">var</span> <span class="nx">obj</span> <span class="o">=</span> <span class="nx">$$</span><span class="p">({</span><span class="nx">query</span><span class="o">:</span><span class="s1">&#39;Type of query&#39;</span><span class="p">},</span> <span class="s1">&#39;&lt;p&gt;&lt;input type=&quot;search&quot; data-bind=&quot;name&quot;/&gt; Instant model change: &lt;span data-bind=&quot;name&quot;/&gt;&lt;/p&gt;&#39;</span><span class="p">);</span>
+<span class="nx">$$</span><span class="p">.</span><span class="nb">document</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="nx">obj</span><span class="p">);</span>
+</pre></div>
+