//jquery.view.js (function( $ ) { // converts to an ok dom id var toId = function( src ) { return src.replace(/^\/\//, "").replace(/[\/\.]/g, "_"); }, // used for hookup ids id = 1; /** * @class jQuery.View * @tag core * @plugin jquery/view * @test jquery/view/qunit.html * @download dist/jquery.view.js * * View provides a uniform interface for using templates with * jQuery. When template engines [jQuery.View.register register] * themselves, you are able to: * * - Use views with jQuery extensions [jQuery.fn.after after], [jQuery.fn.append append], * [jQuery.fn.before before], [jQuery.fn.html html], [jQuery.fn.prepend prepend], * [jQuery.fn.replaceWith replaceWith], [jQuery.fn.text text]. * - Template loading from html elements and external files. * - Synchronous and asynchronous template loading. * - Deferred Rendering. * - Template caching. * - Bundling of processed templates in production builds. * - Hookup jquery plugins directly in the template. * * ## Use * * * When using views, you're almost always wanting to insert the results * of a rendered template into the page. jQuery.View overwrites the * jQuery modifiers so using a view is as easy as: * * $("#foo").html('mytemplate.ejs',{message: 'hello world'}) * * This code: * * - Loads the template a 'mytemplate.ejs'. It might look like: *
<h2><%= message %></h2>
* * - Renders it with {message: 'hello world'}, resulting in: *
<div id='foo'>"<h2>hello world</h2></div>
* * - Inserts the result into the foo element. Foo might look like: *
<div id='foo'><h2>hello world</h2></div>
* * ## jQuery Modifiers * * You can use a template with the following jQuery modifiers: * * * * * * * * * *
[jQuery.fn.after after] $('#bar').after('temp.jaml',{});
[jQuery.fn.after append] $('#bar').append('temp.jaml',{});
[jQuery.fn.after before] $('#bar').before('temp.jaml',{});
[jQuery.fn.after html] $('#bar').html('temp.jaml',{});
[jQuery.fn.after prepend] $('#bar').prepend('temp.jaml',{});
[jQuery.fn.after replaceWith] $('#bar').replaceWidth('temp.jaml',{});
[jQuery.fn.after text] $('#bar').text('temp.jaml',{});
* * You always have to pass a string and an object (or function) for the jQuery modifier * to user a template. * * ## Template Locations * * View can load from script tags or from files. * * ## From Script Tags * * To load from a script tag, create a script tag with your template and an id like: * *
<script type='text/ejs' id='recipes'>
	 * <% for(var i=0; i < recipes.length; i++){ %>
	 *   <li><%=recipes[i].name %></li>
	 * <%} %>
	 * </script>
* * Render with this template like: * * @codestart * $("#foo").html('recipes',recipeData) * @codeend * * Notice we passed the id of the element we want to render. * * ## From File * * You can pass the path of a template file location like: * * $("#foo").html('templates/recipes.ejs',recipeData) * * However, you typically want to make the template work from whatever page they * are called from. To do this, use // to look up templates from JMVC root: * * $("#foo").html('//app/views/recipes.ejs',recipeData) * * Finally, the [jQuery.Controller.prototype.view controller/view] plugin can make looking * up a thread (and adding helpers) even easier: * * $("#foo").html( this.view('recipes', recipeData) ) * * ## Packaging Templates * * If you're making heavy use of templates, you want to organize * them in files so they can be reused between pages and applications. * * But, this organization would come at a high price * if the browser has to * retrieve each template individually. The additional * HTTP requests would slow down your app. * * Fortunately, [steal.static.views steal.views] can build templates * into your production files. You just have to point to the view file like: * * steal.views('path/to/the/view.ejs'); * * ## Asynchronous * * By default, retrieving requests is done synchronously. This is * fine because StealJS packages view templates with your JS download. * * However, some people might not be using StealJS or want to delay loading * templates until necessary. If you have the need, you can * provide a callback paramter like: * * $("#foo").html('recipes',recipeData, function(result){ * this.fadeIn() * }); * * The callback function will be called with the result of the * rendered template and 'this' will be set to the original jQuery object. * * ## Deferreds (3.0.6) * * If you pass deferreds to $.View or any of the jQuery * modifiers, the view will wait until all deferreds resolve before * rendering the view. This makes it a one-liner to make a request and * use the result to render a template. * * The following makes a request for todos in parallel with the * todos.ejs template. Once todos and template have been loaded, it with * render the view with the todos. * * $('#todos').html("todos.ejs",Todo.findAll()); * * ## Just Render Templates * * Sometimes, you just want to get the result of a rendered * template without inserting it, you can do this with $.View: * * var out = $.View('path/to/template.jaml',{}); * * ## Preloading Templates * * You can preload templates asynchronously like: * * $.get('path/to/template.jaml',{},function(){},'view'); * * ## Supported Template Engines * * JavaScriptMVC comes with the following template languages: * * - EmbeddedJS *
<h2><%= message %></h2>
* * - JAML *
h2(data.message);
* * - Micro *
<h2>{%= message %}</h2>
* * - jQuery.Tmpl *
<h2>${message}</h2>
* * The popular Mustache * template engine is supported in a 2nd party plugin. * * ## Using other Template Engines * * It's easy to integrate your favorite template into $.View and Steal. Read * how in [jQuery.View.register]. * * @constructor * * Looks up a template, processes it, caches it, then renders the template * with data and optional helpers. * * With [stealjs StealJS], views are typically bundled in the production build. * This makes it ok to use views synchronously like: * * @codestart * $.View("//myplugin/views/init.ejs",{message: "Hello World"}) * @codeend * * If you aren't using StealJS, it's best to use views asynchronously like: * * @codestart * $.View("//myplugin/views/init.ejs", * {message: "Hello World"}, function(result){ * // do something with result * }) * @codeend * * @param {String} view The url or id of an element to use as the template's source. * @param {Object} data The data to be passed to the view. * @param {Object} [helpers] Optional helper functions the view might use. Not all * templates support helpers. * @param {Object} [callback] Optional callback function. If present, the template is * retrieved asynchronously. This is a good idea if you aren't compressing the templates * into your view. * @return {String} The rendered result of the view or if deferreds are passed, a deferred that will contain * the rendered result of the view. */ var $view, render, checkText, get, getRenderer isDeferred = function(obj){ return obj && $.isFunction(obj.always) // check if obj is a $.Deferred }, // gets an array of deferreds from an object // this only goes one level deep getDeferreds = function(data){ var deferreds = []; // pull out deferreds if(isDeferred(data)){ return [data] }else{ for(var prop in data) { if(isDeferred(data[prop])) { deferreds.push(data[prop]); } } } return deferreds; }, // gets the useful part of deferred // this is for Models and $.ajax that give arrays usefulPart = function(resolved){ return $.isArray(resolved) && resolved.length ===3 && resolved[1] === 'success' ? resolved[0] : resolved }; $view = $.View = function( view, data, helpers, callback ) { if ( typeof helpers === 'function' ) { callback = helpers; helpers = undefined; } // see if we got passed any deferreds var deferreds = getDeferreds(data); if(deferreds.length) { // does data contain any deferreds? // the deferred that resolves into the rendered content ... var deferred = $.Deferred(); // add the view request to the list of deferreds deferreds.push(get(view, true)) // wait for the view and all deferreds to finish $.when.apply($, deferreds).then(function(resolved) { var objs = $.makeArray(arguments), renderer = objs.pop()[0], result; //get the view render function // make data look like the resolved deferreds if (isDeferred(data)) { data = usefulPart(resolved); } else { for (var prop in data) { if (isDeferred(data[prop])) { data[prop] = usefulPart(objs.shift()); } } } result = renderer(data, helpers); //resolve with the rendered view deferred.resolve( result ); // this does not work as is... callback && callback(result); }); // return the deferred .... return deferred.promise(); } else { var response, async = typeof callback === "function", deferred = get(view, async); if(async){ response = deferred; deferred.done(function(renderer){ callback(renderer(data, helpers)) }) } else { deferred.done(function(renderer){ response = renderer(data, helpers); }); } return response; } }; // makes sure there's a template checkText = function( text, url ) { if (!text.match(/[^\s]/) ) { throw "$.View ERROR: There is no template or an empty template at " + url; } }; get = function(url , async){ return $.ajax({ url: url, dataType : "view", async : async }); }; // you can request a view renderer (a function you pass data to and get html) $.ajaxTransport("view", function(options, orig){ var view = orig.url, suffix = view.match(/\.[\w\d]+$/), type, el, id, renderer, url = view, jqXHR, response = function(text){ var func = type.renderer(id, text); if ( $view.cache ) { $view.cached[id] = func; } return { view: func }; }; // if we have an inline template, derive the suffix from the 'text/???' part // this only supports '' tags if ( el = document.getElementById(view)) { suffix = el.type.match(/\/[\d\w]+$/)[0].replace(/^\//, '.'); } //if there is no suffix, add one if (!suffix ) { suffix = $view.ext; url = url + $view.ext; } //convert to a unique and valid id id = toId(url); //if a absolute path, use steal to get it if ( url.match(/^\/\//) ) { if (typeof steal === "undefined") { url = "/"+url.substr(2); } else { url = steal.root.join(url.substr(2)); } } //get the template engine type = $view.types[suffix]; return { send : function(headers, callback){ if($view.cached[id]){ return callback( 200, "success", {view: $view.cached[id]} ); } else if( el ) { callback( 200, "success", response(el.innerHTML) ); } else { jqXHR = $.ajax({ async : orig.async, url: url, dataType: "text", error: function() { checkText("", url); callback(404); }, success: function( text ) { checkText(text, url); callback(200, "success", response(text) ) } }); } }, abort : function(){ jqXHR && jqXHR.abort(); } } }) $.extend($view, { /** * @attribute hookups * @hide * A list of pending 'hookups' */ hookups: {}, /** * @function hookup * Registers a hookup function that can be called back after the html is * put on the page. Typically this is handled by the template engine. Currently * only EJS supports this functionality. * * var id = $.View.hookup(function(el){ * //do something with el * }), * html = "
" * $('.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", * 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: * * * 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: * */ register: function( info ) { this.types["." + info.suffix] = info; }, 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); }; } }); //---- ADD jQUERY HELPERS ----- //converts jquery functions to use views var convert, modify, isTemplate, getCallback, hookupView, funcs; convert = function( func_name ) { var old = $.fn[func_name]; $.fn[func_name] = function() { var args = $.makeArray(arguments), callbackNum, callback, self = this, result; //check if a template 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; } result = $view.apply($view, args); if(!isDeferred( result ) ){ args = [result]; }else{ result.done(function(res){ modify.call(self, [res], old); }) return this; } //otherwise do the template now } return modify.call(this, args, old); }; }; // modifies the html of the element 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 ) { hooks = $view.hookups; $view.hookups = {}; args[0] = $(args[0]); } res = old.apply(this, args); //now hookup hookups if ( hasHookups ) { hookupView(args[0], hooks); } return res; }; // returns true or false if the args indicate a template is being used isTemplate = function( args ) { var secArgType = typeof args[1]; return typeof args[0] == "string" && (secArgType == 'object' || secArgType == 'function') && !args[1].nodeType && !args[1].jquery; }; //returns the callback 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 */ funcs = [ /** * @function prepend * @parent jQuery.View * abc */ "prepend", /** * @function append * @parent jQuery.View * abc */ "append", /** * @function after * @parent jQuery.View * abc */ "after", /** * @function before * @parent jQuery.View * abc */ "before", /** * @function text * @parent jQuery.View * abc */ "text", /** * @function html * @parent jQuery.View * abc */ "html", /** * @function replaceWith * @parent jQuery.View * abc */ "replaceWith", "val"]; //go through helper funcs and convert for ( var i = 0; i < funcs.length; i++ ) { convert(funcs[i]); } })(jQuery); //jquery.lang.js (function( $ ) { // Several of the methods in this plugin use code adapated from Prototype // Prototype JavaScript framework, version 1.6.0.1 // (c) 2005-2007 Sam Stephenson var regs = { undHash: /_|-/, colons: /::/, words: /([A-Z]+)([A-Z][a-z])/g, lowUp: /([a-z\d])([A-Z])/g, dash: /([a-z\d])([A-Z])/g, replacer: /\{([^\}]+)\}/g, dot: /\./ }, getNext = function(current, nextPart, add){ return current[nextPart] || ( add && (current[nextPart] = {}) ); }, isContainer = function(current){ var type = typeof current; return type && ( type == 'function' || type == 'object' ); }, getObject = function( objectName, roots, add ) { var parts = objectName ? objectName.split(regs.dot) : [], length = parts.length, currents = $.isArray(roots) ? roots : [roots || window], current, ret, i, c = 0, type; if(length == 0){ return currents[0]; } while(current = currents[c++]){ for (i =0; i < length - 1 && isContainer(current); i++ ) { current = getNext(current, parts[i], add); } if( isContainer(current) ) { ret = getNext(current, parts[i], add); if( ret !== undefined ) { if ( add === false ) { delete current[parts[i]]; } return ret; } } } }, /** * @class jQuery.String * * A collection of useful string helpers. * */ str = $.String = $.extend( $.String || {} , { /** * @function * Gets an object from a string. * @param {String} name the name of the object to look for * @param {Array} [roots] an array of root objects to look for the name * @param {Boolean} [add] true to add missing objects to * the path. false to remove found properties. undefined to * not modify the root object */ getObject : getObject, /** * Capitalizes a string * @param {String} s the string. * @return {String} a string with the first character capitalized. */ capitalize: function( s, cache ) { return s.charAt(0).toUpperCase() + s.substr(1); }, /** * Capitalizes a string from something undercored. Examples: * @codestart * jQuery.String.camelize("one_two") //-> "oneTwo" * "three-four".camelize() //-> threeFour * @codeend * @param {String} s * @return {String} a the camelized string */ camelize: function( s ) { s = str.classize(s); return s.charAt(0).toLowerCase() + s.substr(1); }, /** * Like camelize, but the first part is also capitalized * @param {String} s * @return {String} the classized string */ classize: function( s , join) { var parts = s.split(regs.undHash), i = 0; for (; i < parts.length; i++ ) { parts[i] = str.capitalize(parts[i]); } return parts.join(join || ''); }, /** * Like [jQuery.String.classize|classize], but a space separates each 'word' * @codestart * jQuery.String.niceName("one_two") //-> "One Two" * @codeend * @param {String} s * @return {String} the niceName */ niceName: function( s ) { str.classize(parts[i],' '); }, /** * Underscores a string. * @codestart * jQuery.String.underscore("OneTwo") //-> "one_two" * @codeend * @param {String} s * @return {String} the underscored string */ underscore: function( s ) { return s.replace(regs.colons, '/').replace(regs.words, '$1_$2').replace(regs.lowUp, '$1_$2').replace(regs.dash, '_').toLowerCase(); }, /** * Returns a string with {param} replaced values from data. * * $.String.sub("foo {bar}",{bar: "far"}) * //-> "foo far" * * @param {String} s The string to replace * @param {Object} data The data to be used to look for properties. If it's an array, multiple * objects can be used. * @param {Boolean} [remove] if a match is found, remove the property from the object */ sub: function( s, data, remove ) { var obs = []; obs.push(s.replace(regs.replacer, function( whole, inside ) { //convert inside to type var ob = getObject(inside, data, typeof remove == 'boolean' ? !remove : remove), type = typeof ob; if((type === 'object' || type === 'function') && type !== null){ obs.push(ob); return ""; }else{ return ""+ob; } })); return obs.length <= 1 ? obs[0] : obs; } }); })(jQuery); //jquery.lang.rsplit.js (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); //jquery.view.ejs.js (function( $ ) { var myEval = function(script){ eval(script); }, chop = function( string ) { return string.substr(0, string.length - 1); }, rSplit = $.String.rsplit, extend = $.extend, isArray = $.isArray, clean = function( content ) { return content.replace(/\\/g, '\\\\').replace(/\n/g, '\\n').replace(/"/g, '\\"'); } // from prototype http://www.prototypejs.org/ escapeHTML = function(content){ return content.replace(/&/g,'&') .replace(//g,'>') .replace(/"/g, '"') .replace(/'/g, "'"); }, EJS = function( options ) { //returns a renderer function if ( this.constructor != EJS ) { var ejs = new EJS(options); return function( data, helpers ) { return ejs.render(data, helpers); }; } //so we can set the processor if ( typeof options == "function" ) { this.template = {}; this.template.process = options; return; } //set options on self extend(this, EJS.options, options); this.template = compile(this.text, this.type, this.name); }; /** * @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 result into the result of the template. * For example: * *

<%= 'hello world' %>

* * - <%~ CODE %> - Runs JS Code and writes the _escaped_ 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 * *

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 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
OptionDefaultDescription
url loads the template from a file. This path should be relative to [jQuery.root]. *
text uses the provided text as the template. Example:
new View({text: '<%=user%>'}) *
element loads a template from the innerHTML or value of the element. *
type'<'type of magic tags. Options are '<' or '[' *
namethe element ID or url an optional name that is used for caching. *
cachetrue in production mode, false in other modestrue to cache template. *
*/ $.EJS = EJS; /** * @Prototype */ EJS.prototype = { constructor: EJS, /** * Renders an object with extra view helpers attached to the view. * @param {Object} object data to be rendered * @param {Object} extra_helpers an object with additonal 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.process.call(object, object, v); } }; /* @Static */ 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 majic tags: <%= %> * @return {String} returns the content to be added to the rendered * output. The content is different depending on the type: * * * string - a bac * * foo - bar */ text = function( input ) { if ( typeof input == 'string' ) { return input; } if ( input === null || input === undefined ) { return ''; } var hook = (input.hookup && function( el, id ) { input.hookup.call(input, el, id); }) || (typeof input == 'function' && input) || (isArray(input) && function( el, id ) { for ( var i = 0; i < input.length; i++ ) { var stub; stub = input[i].hookup ? input[i].hookup(el, id) : input[i](el, id); } }); if(hook){ return "data-view-id='" + $.View.hookup(hook) + "'"; } return input.toString ? input.toString() : ""; }; EJS.clean = function(text){ //return sanatized text if(typeof text == 'string'){ return escapeHTML(text) }else{ return ""; } } //returns something you can call scan on var scan = function(scanner, source, block ) { var source_split = rSplit(source, /\n/), 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); } } }, makeScanner = function(left, right){ var scanner = {}; extend(scanner, { left: left + '%', right: '%' + right, dLeft: left + '%%', dRight: '%%' + right, eeLeft : left + '%==', eLeft: left + '%=', cmnt: left + '%#', cleanLeft: left+"%~", scan : scan, lines : 0 }); scanner.splitter = new RegExp("(" + [scanner.dLeft, scanner.dRight, scanner.eeLeft, scanner.eLeft, scanner.cleanLeft, scanner.cmnt, scanner.left, scanner.right + '\n', scanner.right, '\n'].join(")|("). replace(/\[/g,"\\[").replace(/\]/g,"\\]") + ")"); return scanner; }, // compiles a template compile = function( source, left, name ) { source = source.replace(/\r\n/g, "\n").replace(/\r/g, "\n"); //normalize line endings left = left || '<'; var put_cmd = "___v1ew.push(", insert_cmd = put_cmd, buff = new EJS.Buffer(['var ___v1ew = [];'], []), content = '', put = function( content ) { buff.push(put_cmd, '"', clean(content), '");'); }, startTag = null, empty = function(){ content = '' }; scan( makeScanner(left, left === '[' ? ']' : '>') , source||"", function( token, scanner ) { // if we don't have a start pair if ( startTag === null ) { switch ( token ) { case '\n': content = content + "\n"; put(content); buff.cr(); empty(); break; case scanner.left: case scanner.eLeft: case scanner.eeLeft: case scanner.cleanLeft: case scanner.cmnt: startTag = token; if ( content.length > 0 ) { put(content); } empty(); break; // replace <%% with <% case scanner.dLeft: content += scanner.left; break; default: content += token; break; } } else { switch ( token ) { case scanner.right: switch ( startTag ) { case scanner.left: if ( content[content.length - 1] == '\n' ) { content = chop(content); buff.push(content, ";"); buff.cr(); } else { buff.push(content, ";"); } break; case scanner.cleanLeft : buff.push(insert_cmd, "(jQuery.EJS.clean(", content, ")));"); break; case scanner.eLeft: buff.push(insert_cmd, "(jQuery.EJS.text(", content, ")));"); break; case scanner.eeLeft: buff.push(insert_cmd, "(jQuery.EJS.text(", content, ")));"); 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 + " return ___v1ew.join('');}}}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.process = (function(_CONTEXT,_VIEW){' + out.out + '});\r\n//@ sourceURL='+name+".js"); return out; }; // a line and script buffer // we use this so we know line numbers when there // is an error. // pre and post are setup and teardown for the buffer EJS.Buffer = function( pre_cmd, post ) { this.line = []; this.script = []; this.post = post; // add the pre commands to the first line this.push.apply(this, pre_cmd); }; EJS.Buffer.prototype = { //need to maintain your own semi-colons (for performance) push: function() { this.line.push.apply(this.line, arguments); }, cr: function() { this.script.push(this.line.join(''), "\n"); this.line = []; }, //returns the script too close: function() { var stub; if ( this.line.length > 0 ) { this.script.push(this.line.join('')); this.line = []; } stub = this.post.length && this.push.apply(this, this.post); this.script.push(";"); //makes sure we always have an ending / return this.script.join(""); } }; //type, cache, folder /** * @attribute options * Sets default options for all views * * * * * * * * * * * * *
OptionDefaultDescription
type'<'type of magic tags. Options are '<' or '[' *
cachetrue in production mode, false in other modestrue to cache template. *
* */ EJS.options = { type: '<', ext: '.ejs' }; /** * @class jQuery.EJS.Helpers * @parent jQuery.EJS * By adding functions to jQuery.EJS.Helpers.prototype, those functions will be available in the * views. * @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); } }; $.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 }).template.out + " })"; }, renderer: function( id, text ) { var ejs = new EJS({ text: text, name: id }); return function( data, helpers ) { return ejs.render.call(ejs, data, helpers); }; } }); })(jQuery);