diff --git a/index.html b/index.html
index 7dbffb5..eeb15dd 100644
--- a/index.html
+++ b/index.html
@@ -3,45 +3,14 @@
"
+ * $('.foo').html(html);
+ *
+ *
+ * @param {Function} cb a callback function to be called with the element
+ * @param {Number} the hookup number
+ */
+ hookup: function( cb ) {
+ var myid = ++id;
+ $view.hookups[myid] = cb;
+ return myid;
+ },
+ /**
+ * @attribute cached
+ * @hide
+ * Cached are put in this object
+ */
+ cached: {},
+ /**
+ * @attribute cache
+ * Should the views be cached or reloaded from the server. Defaults to true.
+ */
+ cache: true,
+ /**
+ * @function register
+ * Registers a template engine to be used with
+ * view helpers and compression.
+ *
+ * ## Example
+ *
+ * @codestart
+ * $.View.register({
+ * suffix : "tmpl",
+ * plugin : "jquery/view/tmpl",
+ * renderer: function( id, text ) {
+ * return function(data){
+ * return jQuery.render( text, data );
+ * }
+ * },
+ * script: function( id, text ) {
+ * var tmpl = $.tmpl(text).toString();
+ * return "function(data){return ("+
+ * tmpl+
+ * ").call(jQuery, jQuery, data); }";
+ * }
+ * })
+ * @codeend
+ * Here's what each property does:
+ *
+ * * plugin - the location of the plugin
+ * * suffix - files that use this suffix will be processed by this template engine
+ * * renderer - returns a function that will render the template provided by text
+ * * script - returns a string form of the processed template function.
+ *
+ * @param {Object} info a object of method and properties
+ *
+ * that enable template integration:
+ *
+ * - plugin - the location of the plugin. EX: 'jquery/view/ejs'
+ * - suffix - the view extension. EX: 'ejs'
+ * - script(id, src) - a function that returns a string that when evaluated returns a function that can be
+ * used as the render (i.e. have func.call(data, data, helpers) called on it).
+ * - renderer(id, text) - a function that takes the id of the template and the text of the template and
+ * returns a render function.
+ *
+ */
+ register: function( info ) {
+ this.types["." + info.suffix] = info;
+
+ if ( window.steal ) {
+ steal.type(info.suffix + " view js", function( options, success, error ) {
+ var type = $view.types["." + options.type],
+ id = toId(options.rootSrc+'');
+
+ options.text = type.script(id, options.text)
+ success();
+ })
+ }
+ },
+ types: {},
+ /**
+ * @attribute ext
+ * The default suffix to use if none is provided in the view's url.
+ * This is set to .ejs by default.
+ */
+ ext: ".ejs",
+ /**
+ * Returns the text that
+ * @hide
+ * @param {Object} type
+ * @param {Object} id
+ * @param {Object} src
+ */
+ registerScript: function( type, id, src ) {
+ return "$.View.preload('" + id + "'," + $view.types["." + type].script(id, src) + ");";
+ },
+ /**
+ * @hide
+ * Called by a production script to pre-load a renderer function
+ * into the view cache.
+ * @param {String} id
+ * @param {Function} renderer
+ */
+ preload: function( id, renderer ) {
+ $view.cached[id] = function( data, helpers ) {
+ return renderer.call(data, data, helpers);
+ };
+ }
+
+ });
+ if ( window.steal ) {
+ steal.type("view js", function( options, success, error ) {
+ var type = $view.types["." + options.type],
+ id = toId(options.rootSrc+'');
+
+ options.text = "steal('" + (type.plugin || "jquery/view/" + options.type) + "').then(function($){" + "$.View.preload('" + id + "'," + options.text + ");\n})";
+ success();
+ })
+ }
+
+ //---- ADD jQUERY HELPERS -----
+ //converts jquery functions to use views
+ var convert, modify, isTemplate, isHTML, isDOM, getCallback, hookupView, funcs,
+ // text and val cannot produce an element, so don't run hookups on them
+ noHookup = {'val':true,'text':true};
+
+ convert = function( func_name ) {
+ // save the old jQuery helper
+ var old = $.fn[func_name];
+
+ // replace it wiht our new helper
+ $.fn[func_name] = function() {
+
+ var args = makeArray(arguments),
+ callbackNum,
+ callback,
+ self = this,
+ result;
+
+ // if the first arg is a deferred
+ // wait until it finishes, and call
+ // modify with the result
+ if ( isDeferred(args[0]) ) {
+ args[0].done(function( res ) {
+ modify.call(self, [res], old);
+ })
+ return this;
+ }
+ //check if a template
+ else if ( isTemplate(args) ) {
+
+ // if we should operate async
+ if ((callbackNum = getCallback(args))) {
+ callback = args[callbackNum];
+ args[callbackNum] = function( result ) {
+ modify.call(self, [result], old);
+ callback.call(self, result);
+ };
+ $view.apply($view, args);
+ return this;
+ }
+ // call view with args (there might be deferreds)
+ result = $view.apply($view, args);
+
+ // if we got a string back
+ if (!isDeferred(result) ) {
+ // we are going to call the old method with that string
+ args = [result];
+ } else {
+ // if there is a deferred, wait until it is done before calling modify
+ result.done(function( res ) {
+ modify.call(self, [res], old);
+ })
+ return this;
+ }
+ }
+ return noHookup[func_name] ? old.apply(this,args) :
+ modify.call(this, args, old);
+ };
+ };
+
+ // modifies the content of the element
+ // but also will run any hookup
+ modify = function( args, old ) {
+ var res, stub, hooks;
+
+ //check if there are new hookups
+ for ( var hasHookups in $view.hookups ) {
+ break;
+ }
+
+ //if there are hookups, get jQuery object
+ if ( hasHookups && args[0] && isHTML(args[0]) ) {
+ hooks = $view.hookups;
+ $view.hookups = {};
+ args[0] = $(args[0]);
+ }
+ res = old.apply(this, args);
+
+ //now hookup the hookups
+ if ( hooks
+ /* && args.length*/
+ ) {
+ hookupView(args[0], hooks);
+ }
+ return res;
+ };
+
+ // returns true or false if the args indicate a template is being used
+ // $('#foo').html('/path/to/template.ejs',{data})
+ // in general, we want to make sure the first arg is a string
+ // and the second arg is data
+ isTemplate = function( args ) {
+ // save the second arg type
+ var secArgType = typeof args[1];
+
+ // the first arg is a string
+ return typeof args[0] == "string" &&
+ // the second arg is an object or function
+ (secArgType == 'object' || secArgType == 'function') &&
+ // but it is not a dom element
+ !isDOM(args[1]);
+ };
+ // returns true if the arg is a jQuery object or HTMLElement
+ isDOM = function(arg){
+ return arg.nodeType || arg.jquery
+ };
+ // returns whether the argument is some sort of HTML data
+ isHTML = function( arg ) {
+ if ( isDOM(arg) ) {
+ // if jQuery object or DOM node we're good
+ return true;
+ } else if ( typeof arg === "string" ) {
+ // if string, do a quick sanity check that we're HTML
+ arg = $.trim(arg);
+ return arg.substr(0, 1) === "<" && arg.substr(arg.length - 1, 1) === ">" && arg.length >= 3;
+ } else {
+ // don't know what you are
+ return false;
+ }
+ };
+
+ //returns the callback arg number if there is one (for async view use)
+ getCallback = function( args ) {
+ return typeof args[3] === 'function' ? 3 : typeof args[2] === 'function' && 2;
+ };
+
+ hookupView = function( els, hooks ) {
+ //remove all hookups
+ var hookupEls, len, i = 0,
+ id, func;
+ els = els.filter(function() {
+ return this.nodeType != 3; //filter out text nodes
+ })
+ hookupEls = els.add("[data-view-id]", els);
+ len = hookupEls.length;
+ for (; i < len; i++ ) {
+ if ( hookupEls[i].getAttribute && (id = hookupEls[i].getAttribute('data-view-id')) && (func = hooks[id]) ) {
+ func(hookupEls[i], id);
+ delete hooks[id];
+ hookupEls[i].removeAttribute('data-view-id');
+ }
+ }
+ //copy remaining hooks back
+ $.extend($view.hookups, hooks);
+ };
+
+ /**
+ * @add jQuery.fn
+ * @parent jQuery.View
+ * Called on a jQuery collection that was rendered with $.View with pending hookups. $.View can render a
+ * template with hookups, but not actually perform the hookup, because it returns a string without actual DOM
+ * elements to hook up to. So hookup performs the hookup and clears the pending hookups, preventing errors in
+ * future templates.
+ *
+ * @codestart
+ * $($.View('//views/recipes.ejs',recipeData)).hookup()
+ * @codeend
+ */
+ $.fn.hookup = function() {
+ var hooks = $view.hookups;
+ $view.hookups = {};
+ hookupView(this, hooks);
+ return this;
+ };
+
+ /**
+ * @add jQuery.fn
+ */
+ $.each([
+ /**
+ * @function prepend
+ * @parent jQuery.View
+ *
+ * Extending the original [http://api.jquery.com/prepend/ jQuery().prepend()]
+ * to render [jQuery.View] templates inserted at the beginning of each element in the set of matched elements.
+ *
+ * $('#test').prepend('path/to/template.ejs', { name : 'javascriptmvc' });
+ *
+ * @param {String|Object|Function} content A template filename or the id of a view script tag
+ * or a DOM element, array of elements, HTML string, or jQuery object.
+ * @param {Object} [data] The data to render the view with.
+ * If rendering a view template this parameter always has to be present
+ * (use the empty object initializer {} for no data).
+ */
+ "prepend",
+ /**
+ * @function append
+ * @parent jQuery.View
+ *
+ * Extending the original [http://api.jquery.com/append/ jQuery().append()]
+ * to render [jQuery.View] templates inserted at the end of each element in the set of matched elements.
+ *
+ * $('#test').append('path/to/template.ejs', { name : 'javascriptmvc' });
+ *
+ * @param {String|Object|Function} content A template filename or the id of a view script tag
+ * or a DOM element, array of elements, HTML string, or jQuery object.
+ * @param {Object} [data] The data to render the view with.
+ * If rendering a view template this parameter always has to be present
+ * (use the empty object initializer {} for no data).
+ */
+ "append",
+ /**
+ * @function after
+ * @parent jQuery.View
+ *
+ * Extending the original [http://api.jquery.com/after/ jQuery().after()]
+ * to render [jQuery.View] templates inserted after each element in the set of matched elements.
+ *
+ * $('#test').after('path/to/template.ejs', { name : 'javascriptmvc' });
+ *
+ * @param {String|Object|Function} content A template filename or the id of a view script tag
+ * or a DOM element, array of elements, HTML string, or jQuery object.
+ * @param {Object} [data] The data to render the view with.
+ * If rendering a view template this parameter always has to be present
+ * (use the empty object initializer {} for no data).
+ */
+ "after",
+ /**
+ * @function before
+ * @parent jQuery.View
+ *
+ * Extending the original [http://api.jquery.com/before/ jQuery().before()]
+ * to render [jQuery.View] templates inserted before each element in the set of matched elements.
+ *
+ * $('#test').before('path/to/template.ejs', { name : 'javascriptmvc' });
+ *
+ * @param {String|Object|Function} content A template filename or the id of a view script tag
+ * or a DOM element, array of elements, HTML string, or jQuery object.
+ * @param {Object} [data] The data to render the view with.
+ * If rendering a view template this parameter always has to be present
+ * (use the empty object initializer {} for no data).
+ */
+ "before",
+ /**
+ * @function text
+ * @parent jQuery.View
+ *
+ * Extending the original [http://api.jquery.com/text/ jQuery().text()]
+ * to render [jQuery.View] templates as the content of each matched element.
+ * Unlike [jQuery.fn.html] jQuery.fn.text also works with XML, escaping the provided
+ * string as necessary.
+ *
+ * $('#test').text('path/to/template.ejs', { name : 'javascriptmvc' });
+ *
+ * @param {String|Object|Function} content A template filename or the id of a view script tag
+ * or a DOM element, array of elements, HTML string, or jQuery object.
+ * @param {Object} [data] The data to render the view with.
+ * If rendering a view template this parameter always has to be present
+ * (use the empty object initializer {} for no data).
+ */
+ "text",
+ /**
+ * @function html
+ * @parent jQuery.View
+ *
+ * Extending the original [http://api.jquery.com/html/ jQuery().html()]
+ * to render [jQuery.View] templates as the content of each matched element.
+ *
+ * $('#test').html('path/to/template.ejs', { name : 'javascriptmvc' });
+ *
+ * @param {String|Object|Function} content A template filename or the id of a view script tag
+ * or a DOM element, array of elements, HTML string, or jQuery object.
+ * @param {Object} [data] The data to render the view with.
+ * If rendering a view template this parameter always has to be present
+ * (use the empty object initializer {} for no data).
+ */
+ "html",
+ /**
+ * @function replaceWith
+ * @parent jQuery.View
+ *
+ * Extending the original [http://api.jquery.com/replaceWith/ jQuery().replaceWith()]
+ * to render [jQuery.View] templates replacing each element in the set of matched elements.
+ *
+ * $('#test').replaceWith('path/to/template.ejs', { name : 'javascriptmvc' });
+ *
+ * @param {String|Object|Function} content A template filename or the id of a view script tag
+ * or a DOM element, array of elements, HTML string, or jQuery object.
+ * @param {Object} [data] The data to render the view with.
+ * If rendering a view template this parameter always has to be present
+ * (use the empty object initializer {} for no data).
+ */
+ "replaceWith", "val"],function(i, func){
+ convert(func);
+ });
+
+ //go through helper funcs and convert
+
+
+})(jQuery);
+(function( $ ) {
+ /**
+ * @add jQuery.String
+ */
+ $.String.
+ /**
+ * Splits a string with a regex correctly cross browser
+ *
+ * $.String.rsplit("a.b.c.d", /\./) //-> ['a','b','c','d']
+ *
+ * @param {String} string The string to split
+ * @param {RegExp} regex A regular expression
+ * @return {Array} An array of strings
+ */
+ rsplit = function( string, regex ) {
+ var result = regex.exec(string),
+ retArr = [],
+ first_idx, last_idx;
+ while ( result !== null ) {
+ first_idx = result.index;
+ last_idx = regex.lastIndex;
+ if ( first_idx !== 0 ) {
+ retArr.push(string.substring(0, first_idx));
+ string = string.slice(first_idx);
+ }
+ retArr.push(result[0]);
+ string = string.slice(result[0].length);
+ result = regex.exec(string);
+ }
+ if ( string !== '' ) {
+ retArr.push(string);
+ }
+ return retArr;
+ };
+})(jQuery);
+(function( $ ) {
+
+ // HELPER METHODS ==============
+ var myEval = function( script ) {
+ eval(script);
+ },
+ // removes the last character from a string
+ // this is no longer needed
+ // chop = function( string ) {
+ // return string.substr(0, string.length - 1);
+ //},
+ rSplit = $.String.rsplit,
+ extend = $.extend,
+ isArray = $.isArray,
+ // regular expressions for caching
+ returnReg = /\r\n/g,
+ retReg = /\r/g,
+ newReg = /\n/g,
+ nReg = /\n/,
+ slashReg = /\\/g,
+ quoteReg = /"/g,
+ singleQuoteReg = /'/g,
+ tabReg = /\t/g,
+ leftBracket = /\{/g,
+ rightBracket = /\}/g,
+ quickFunc = /\s*\(([\$\w]+)\)\s*->([^\n]*)/,
+ // escapes characters starting with \
+ clean = function( content ) {
+ return content.replace(slashReg, '\\\\').replace(newReg, '\\n').replace(quoteReg, '\\"').replace(tabReg, '\\t');
+ },
+ // escapes html
+ // - from prototype http://www.prototypejs.org/
+ escapeHTML = function( content ) {
+ return content.replace(/&/g, '&').replace(//g, '>').replace(quoteReg, '"').replace(singleQuoteReg, "'");
+ },
+ $View = $.View,
+ bracketNum = function(content){
+ var lefts = content.match(leftBracket),
+ rights = content.match(rightBracket);
+
+ return (lefts ? lefts.length : 0) -
+ (rights ? rights.length : 0);
+ },
+ /**
+ * @class jQuery.EJS
+ *
+ * @plugin jquery/view/ejs
+ * @parent jQuery.View
+ * @download http://jmvcsite.heroku.com/pluginify?plugins[]=jquery/view/ejs/ejs.js
+ * @test jquery/view/ejs/qunit.html
+ *
+ *
+ * Ejs provides
ERB
+ * style client side templates. Use them with controllers to easily build html and inject
+ * it into the DOM.
+ *
+ * ### Example
+ *
+ * The following generates a list of tasks:
+ *
+ * @codestart html
+ * <ul>
+ * <% for(var i = 0; i < tasks.length; i++){ %>
+ * <li class="task <%= tasks[i].identity %>"><%= tasks[i].name %></li>
+ * <% } %>
+ * </ul>
+ * @codeend
+ *
+ * For the following examples, we assume this view is in
'views\tasks\list.ejs'.
+ *
+ *
+ * ## Use
+ *
+ * ### Loading and Rendering EJS:
+ *
+ * You should use EJS through the helper functions [jQuery.View] provides such as:
+ *
+ * - [jQuery.fn.after after]
+ * - [jQuery.fn.append append]
+ * - [jQuery.fn.before before]
+ * - [jQuery.fn.html html],
+ * - [jQuery.fn.prepend prepend],
+ * - [jQuery.fn.replaceWith replaceWith], and
+ * - [jQuery.fn.text text].
+ *
+ * or [jQuery.Controller.prototype.view].
+ *
+ * ### Syntax
+ *
+ * EJS uses 5 types of tags:
+ *
+ * -
<% CODE %>
- Runs JS Code.
+ * For example:
+ *
+ * <% alert('hello world') %>
+ *
+ * -
<%= CODE %>
- Runs JS Code and writes the _escaped_ result into the result of the template.
+ * For example:
+ *
+ *
<%= 'hello world' %>
+ *
+ * -
<%== CODE %>
- Runs JS Code and writes the _unescaped_ result into the result of the template.
+ * For example:
+ *
+ *
<%== 'hello world' %>
+ *
+ * -
<%%= CODE %>
- Writes <%= CODE %> to the result of the template. This is very useful for generators.
+ *
+ * <%%= 'hello world' %>
+ *
+ * -
<%# CODE %>
- Used for comments. This does nothing.
+ *
+ * <%# 'hello world' %>
+ *
+ * ## Hooking up controllers
+ *
+ * After drawing some html, you often want to add other widgets and plugins inside that html.
+ * View makes this easy. You just have to return the Contoller class you want to be hooked up.
+ *
+ * @codestart
+ * <ul <%= Mxui.Tabs%>>...<ul>
+ * @codeend
+ *
+ * You can even hook up multiple controllers:
+ *
+ * @codestart
+ * <ul <%= [Mxui.Tabs, Mxui.Filler]%>>...<ul>
+ * @codeend
+ *
+ * To hook up a controller with options or any other jQuery plugin use the
+ * [jQuery.EJS.Helpers.prototype.plugin | plugin view helper]:
+ *
+ * @codestart
+ * <ul <%= plugin('mxui_tabs', { option: 'value' }) %>>...<ul>
+ * @codeend
+ *
+ * Don't add a semicolon when using view helpers.
+ *
+ *
+ *
View Helpers
+ * View Helpers return html code. View by default only comes with
+ * [jQuery.EJS.Helpers.prototype.view view] and [jQuery.EJS.Helpers.prototype.text text].
+ * You can include more with the view/helpers plugin. But, you can easily make your own!
+ * Learn how in the [jQuery.EJS.Helpers Helpers] page.
+ *
+ * @constructor Creates a new view
+ * @param {Object} options A hash with the following options
+ *
+ * Option | Default | Description |
+ *
+ * text |
+ * |
+ * uses the provided text as the template. Example:
new View({text: '<%=user%>'})
+ * |
+ *
+ *
+ * type |
+ * '<' |
+ * type of magic tags. Options are '<' or '['
+ * |
+ *
+ *
+ * name |
+ * the element ID or url |
+ * an optional name that is used for caching.
+ * |
+ *
+ *
+ */
+ EJS = function( options ) {
+ // If called without new, return a function that
+ // renders with data and helpers like
+ // EJS({text: '<%= message %>'})({message: 'foo'});
+ // this is useful for steal's build system
+ if ( this.constructor != EJS ) {
+ var ejs = new EJS(options);
+ return function( data, helpers ) {
+ return ejs.render(data, helpers);
+ };
+ }
+ // if we get a function directly, it probably is coming from
+ // a steal-packaged view
+ if ( typeof options == "function" ) {
+ this.template = {
+ fn: options
+ };
+ return;
+ }
+ //set options on self
+ extend(this, EJS.options, options);
+ this.template = compile(this.text, this.type, this.name);
+ };
+ // add EJS to jQuery if it exists
+ window.jQuery && (jQuery.EJS = EJS);
+ /**
+ * @Prototype
+ */
+ EJS.prototype.
+ /**
+ * Renders an object with view helpers attached to the view.
+ *
+ * new EJS({text: "<%= message %>"}).render({
+ * message: "foo"
+ * },{helper: function(){ ... }})
+ *
+ * @param {Object} object data to be rendered
+ * @param {Object} [extraHelpers] an object with view helpers
+ * @return {String} returns the result of the string
+ */
+ render = function( object, extraHelpers ) {
+ object = object || {};
+ this._extra_helpers = extraHelpers;
+ var v = new EJS.Helpers(object, extraHelpers || {});
+ return this.template.fn.call(object, object, v);
+ };
+ /**
+ * @Static
+ */
+
+ extend(EJS, {
+ /**
+ * Used to convert what's in <%= %> magic tags to a string
+ * to be inserted in the rendered output.
+ *
+ * Typically, it's a string, and the string is just inserted. However,
+ * if it's a function or an object with a hookup method, it can potentially be
+ * be ran on the element after it's inserted into the page.
+ *
+ * This is a very nice way of adding functionality through the view.
+ * Usually this is done with [jQuery.EJS.Helpers.prototype.plugin]
+ * but the following fades in the div element after it has been inserted:
+ *
+ * @codestart
+ * <%= function(el){$(el).fadeIn()} %>
+ * @codeend
+ *
+ * @param {String|Object|Function} input the value in between the
+ * write magic tags: <%= %>
+ * @return {String} returns the content to be added to the rendered
+ * output. The content is different depending on the type:
+ *
+ * * string - the original string
+ * * null or undefined - the empty string ""
+ * * an object with a hookup method - the attribute "data-view-id='XX'", where XX is a hookup number for jQuery.View
+ * * a function - the attribute "data-view-id='XX'", where XX is a hookup number for jQuery.View
+ * * an array - the attribute "data-view-id='XX'", where XX is a hookup number for jQuery.View
+ */
+ text: function( input ) {
+ // if it's a string, return
+ if ( typeof input == 'string' ) {
+ return input;
+ }
+ // if has no value
+ if ( input === null || input === undefined ) {
+ return '';
+ }
+
+ // if it's an object, and it has a hookup method
+ var hook = (input.hookup &&
+ // make a function call the hookup method
+
+ function( el, id ) {
+ input.hookup.call(input, el, id);
+ }) ||
+ // or if it's a function, just use the input
+ (typeof input == 'function' && input) ||
+ // of it its an array, make a function that calls hookup or the function
+ // on each item in the array
+ (isArray(input) &&
+ function( el, id ) {
+ for ( var i = 0; i < input.length; i++ ) {
+ input[i].hookup ? input[i].hookup(el, id) : input[i](el, id);
+ }
+ });
+ // finally, if there is a funciton to hookup on some dom
+ // pass it to hookup to get the data-view-id back
+ if ( hook ) {
+ return "data-view-id='" + $View.hookup(hook) + "'";
+ }
+ // finally, if all else false, toString it
+ return input.toString ? input.toString() : "";
+ },
+ /**
+ * Escapes the text provided as html if it's a string.
+ * Otherwise, the value is passed to EJS.text(text).
+ *
+ * @param {String|Object|Array|Function} text to escape. Otherwise,
+ * the result of [jQuery.EJS.text] is returned.
+ * @return {String} the escaped text or likely a $.View data-view-id attribute.
+ */
+ clean: function( text ) {
+ //return sanatized text
+ if ( typeof text == 'string' ) {
+ return escapeHTML(text);
+ } else if ( typeof text == 'number' ) {
+ return text;
+ } else {
+ return EJS.text(text);
+ }
+ },
+ /**
+ * @attribute options
+ * Sets default options for all views.
+ *
+ * $.EJS.options.type = '['
+ *
+ * Only one option is currently supported: type.
+ *
+ * Type is the left hand magic tag.
+ */
+ options: {
+ type: '<',
+ ext: '.ejs'
+ }
+ });
+ // ========= SCANNING CODE =========
+ // Given a scanner, and source content, calls block with each token
+ // scanner - an object of magicTagName : values
+ // source - the source you want to scan
+ // block - function(token, scanner), called with each token
+ var scan = function( scanner, source, block ) {
+ // split on /\n/ to have new lines on their own line.
+ var source_split = rSplit(source, nReg),
+ i = 0;
+ for (; i < source_split.length; i++ ) {
+ scanline(scanner, source_split[i], block);
+ }
+
+ },
+ scanline = function( scanner, line, block ) {
+ scanner.lines++;
+ var line_split = rSplit(line, scanner.splitter),
+ token;
+ for ( var i = 0; i < line_split.length; i++ ) {
+ token = line_split[i];
+ if ( token !== null ) {
+ block(token, scanner);
+ }
+ }
+ },
+ // creates a 'scanner' object. This creates
+ // values for the left and right magic tags
+ // it's splitter property is a regexp that splits content
+ // by all tags
+ makeScanner = function( left, right ) {
+ var scanner = {};
+ extend(scanner, {
+ left: left + '%',
+ right: '%' + right,
+ dLeft: left + '%%',
+ dRight: '%%' + right,
+ eeLeft: left + '%==',
+ eLeft: left + '%=',
+ cmnt: left + '%#',
+ scan: scan,
+ lines: 0
+ });
+ scanner.splitter = new RegExp("(" + [scanner.dLeft, scanner.dRight, scanner.eeLeft, scanner.eLeft, scanner.cmnt, scanner.left, scanner.right + '\n', scanner.right, '\n'].join(")|(").
+ replace(/\[/g, "\\[").replace(/\]/g, "\\]") + ")");
+ return scanner;
+ },
+ // compiles a template where
+ // source - template text
+ // left - the left magic tag
+ // name - the name of the template (for debugging)
+ // returns an object like: {out : "", fn : function(){ ... }} where
+ // out - the converted JS source of the view
+ // fn - a function made from the JS source
+ compile = function( source, left, name ) {
+ // make everything only use \n
+ source = source.replace(returnReg, "\n").replace(retReg, "\n");
+ // if no left is given, assume <
+ left = left || '<';
+
+ // put and insert cmds are used for adding content to the template
+ // currently they are identical, I am not sure why
+ var put_cmd = "___v1ew.push(",
+ insert_cmd = put_cmd,
+ // the text that starts the view code (or block function)
+ startTxt = 'var ___v1ew = [];',
+ // the text that ends the view code (or block function)
+ finishTxt = "return ___v1ew.join('')",
+ // initialize a buffer
+ buff = new EJS.Buffer([startTxt], []),
+ // content is used as the current 'processing' string
+ // this is the content between magic tags
+ content = '',
+ // adds something to be inserted into the view template
+ // this comes out looking like __v1ew.push("CONENT")
+ put = function( content ) {
+ buff.push(put_cmd, '"', clean(content), '");');
+ },
+ // the starting magic tag
+ startTag = null,
+ // cleans the running content
+ empty = function() {
+ content = ''
+ },
+ // what comes after clean or text
+ doubleParen = "));",
+ // a stack used to keep track of how we should end a bracket }
+ // once we have a <%= %> with a leftBracket
+ // we store how the file should end here (either '))' or ';' )
+ endStack =[];
+
+ // start going token to token
+ scan(makeScanner(left, left === '[' ? ']' : '>'), source || "", function( token, scanner ) {
+ // if we don't have a start pair
+ var bn;
+ if ( startTag === null ) {
+ switch ( token ) {
+ case '\n':
+ content = content + "\n";
+ put(content);
+ buff.cr();
+ empty();
+ break;
+ // set start tag, add previous content (if there is some)
+ // clean content
+ case scanner.left:
+ case scanner.eLeft:
+ case scanner.eeLeft:
+ case scanner.cmnt:
+ // a new line, just add whatever content w/i a clean
+ // reset everything
+ startTag = token;
+ if ( content.length > 0 ) {
+ put(content);
+ }
+ empty();
+ break;
+
+ case scanner.dLeft:
+ // replace <%% with <%
+ content += scanner.left;
+ break;
+ default:
+ content += token;
+ break;
+ }
+ }
+ else {
+ //we have a start tag
+ switch ( token ) {
+ case scanner.right:
+ // %>
+ switch ( startTag ) {
+ case scanner.left:
+ // <%
+
+ // get the number of { minus }
+ bn = bracketNum(content);
+ // how are we ending this statement
+ var last =
+ // if the stack has value and we are ending a block
+ endStack.length && bn == -1 ?
+ // use the last item in the block stack
+ endStack.pop() :
+ // or use the default ending
+ ";";
+
+ // if we are ending a returning block
+ // add the finish text which returns the result of the
+ // block
+ if(last === doubleParen) {
+ buff.push(finishTxt)
+ }
+ // add the remaining content
+ buff.push(content, last);
+
+ // if we have a block, start counting
+ if(bn === 1 ){
+ endStack.push(";")
+ }
+ break;
+ case scanner.eLeft:
+ // <%= clean content
+ bn = bracketNum(content);
+ if( bn ) {
+ endStack.push(doubleParen)
+ }
+ if(quickFunc.test(content)){
+ var parts = content.match(quickFunc)
+ content = "function(__){var "+parts[1]+"=$(__);"+parts[2]+"}"
+ }
+ buff.push(insert_cmd, "jQuery.EJS.clean(", content,bn ? startTxt : doubleParen);
+ break;
+ case scanner.eeLeft:
+ // <%== content
+
+ // get the number of { minus }
+ bn = bracketNum(content);
+ // if we have more {, it means there is a block
+ if( bn ){
+ // when we return to the same # of { vs } end wiht a doubleParen
+ endStack.push(doubleParen)
+ }
+
+ buff.push(insert_cmd, "jQuery.EJS.text(", content,
+ // if we have a block
+ bn ?
+ // start w/ startTxt "var _v1ew = [])"
+ startTxt :
+ // if not, add doubleParent to close push and text
+ doubleParen
+ );
+ break;
+ }
+ startTag = null;
+ empty();
+ break;
+ case scanner.dRight:
+ content += scanner.right;
+ break;
+ default:
+ content += token;
+ break;
+ }
+ }
+ })
+ if ( content.length > 0 ) {
+ // Should be content.dump in Ruby
+ buff.push(put_cmd, '"', clean(content) + '");');
+ }
+ var template = buff.close(),
+ out = {
+ out: 'try { with(_VIEW) { with (_CONTEXT) {' + template + " "+finishTxt+"}}}catch(e){e.lineNumber=null;throw e;}"
+ };
+ //use eval instead of creating a function, b/c it is easier to debug
+ myEval.call(out, 'this.fn = (function(_CONTEXT,_VIEW){' + out.out + '});\r\n//# sourceURL="' + name + '.js"');
+
+ return out;
+ };
+
+
+ // A Buffer used to add content to.
+ // This is useful for performance and simplifying the
+ // code above.
+ // We also can use this so we know line numbers when there
+ // is an error.
+ // pre_cmd - code that sets up the buffer
+ // post - code that finalizes the buffer
+ EJS.Buffer = function( pre_cmd, post ) {
+ // the current line we are on
+ this.line = [];
+ // the combined content added to this buffer
+ this.script = [];
+ // content at the end of the buffer
+ this.post = post;
+ // add the pre commands to the first line
+ this.push.apply(this, pre_cmd);
+ };
+ EJS.Buffer.prototype = {
+ // add content to this line
+ // need to maintain your own semi-colons (for performance)
+ push: function() {
+ this.line.push.apply(this.line, arguments);
+ },
+ // starts a new line
+ cr: function() {
+ this.script.push(this.line.join(''), "\n");
+ this.line = [];
+ },
+ //returns the script too
+ close: function() {
+ // if we have ending line content, add it to the script
+ if ( this.line.length > 0 ) {
+ this.script.push(this.line.join(''));
+ this.line = [];
+ }
+ // if we have ending content, add it
+ this.post.length && this.push.apply(this, this.post);
+ // always end in a ;
+ this.script.push(";");
+ return this.script.join("");
+ }
+
+ };
+
+ /**
+ * @class jQuery.EJS.Helpers
+ * @parent jQuery.EJS
+ * By adding functions to jQuery.EJS.Helpers.prototype, those functions will be available in the
+ * views.
+ *
+ * The following helper converts a given string to upper case:
+ *
+ * $.EJS.Helpers.prototype.toUpper = function(params)
+ * {
+ * return params.toUpperCase();
+ * }
+ *
+ * Use it like this in any EJS template:
+ *
+ * <%= toUpper('javascriptmvc') %>
+ *
+ * To access the current DOM element return a function that takes the element as a parameter:
+ *
+ * $.EJS.Helpers.prototype.upperHtml = function(params)
+ * {
+ * return function(el) {
+ * $(el).html(params.toUpperCase());
+ * }
+ * }
+ *
+ * In your EJS view you can then call the helper on an element tag:
+ *
+ *
>
+ *
+ *
+ * @constructor Creates a view helper. This function
+ * is called internally. You should never call it.
+ * @param {Object} data The data passed to the
+ * view. Helpers have access to it through this._data
+ */
+ EJS.Helpers = function( data, extras ) {
+ this._data = data;
+ this._extras = extras;
+ extend(this, extras);
+ };
+ /**
+ * @prototype
+ */
+ EJS.Helpers.prototype = {
+ /**
+ * Hooks up a jQuery plugin on.
+ * @param {String} name the plugin name
+ */
+ plugin: function( name ) {
+ var args = $.makeArray(arguments),
+ widget = args.shift();
+ return function( el ) {
+ var jq = $(el);
+ jq[widget].apply(jq, args);
+ };
+ },
+ /**
+ * Renders a partial view. This is deprecated in favor of
$.View()
.
+ */
+ view: function( url, data, helpers ) {
+ helpers = helpers || this._extras;
+ data = data || this._data;
+ return $View(url, data, helpers); //new EJS(options).render(data, helpers);
+ }
+ };
+
+ // options for steal's build
+ $View.register({
+ suffix: "ejs",
+ //returns a function that renders the view
+ script: function( id, src ) {
+ return "jQuery.EJS(function(_CONTEXT,_VIEW) { " + new EJS({
+ text: src,
+ name: id
+ }).template.out + " })";
+ },
+ renderer: function( id, text ) {
+ return EJS({
+ text: text,
+ name: id
+ });
+ }
+ });
+})(jQuery);
\ No newline at end of file
diff --git a/views/task-item.ejs b/views/task-item.ejs
new file mode 100644
index 0000000..47a3346
--- /dev/null
+++ b/views/task-item.ejs
@@ -0,0 +1 @@
+
<%= title %>
\ No newline at end of file
diff --git a/views/tasks.ejs b/views/tasks.ejs
new file mode 100644
index 0000000..1ea969e
--- /dev/null
+++ b/views/tasks.ejs
@@ -0,0 +1,16 @@
+
Tasks
+
\ No newline at end of file