You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by rm...@apache.org on 2011/07/21 23:42:54 UTC
svn commit: r1149378 [4/5] - in /openejb/trunk/openejb3/examples/webapps: ./
rest-example/ rest-example/src/ rest-example/src/main/
rest-example/src/main/java/ rest-example/src/main/java/org/
rest-example/src/main/java/org/superbiz/ rest-example/src/ma...
Added: openejb/trunk/openejb3/examples/webapps/rest-example/src/main/webapp/jquery/lang/json/json.js
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/examples/webapps/rest-example/src/main/webapp/jquery/lang/json/json.js?rev=1149378&view=auto
==============================================================================
--- openejb/trunk/openejb3/examples/webapps/rest-example/src/main/webapp/jquery/lang/json/json.js (added)
+++ openejb/trunk/openejb3/examples/webapps/rest-example/src/main/webapp/jquery/lang/json/json.js Thu Jul 21 21:42:07 2011
@@ -0,0 +1,201 @@
+/*
+ * jQuery JSON Plugin
+ * version: 2.1 (2009-08-14)
+ *
+ * This document is licensed as free software under the terms of the
+ * MIT License: http://www.opensource.org/licenses/mit-license.php
+ *
+ * Brantley Harris wrote this plugin. It is based somewhat on the JSON.org
+ * website's http://www.json.org/json2.js, which proclaims:
+ * "NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.", a sentiment that
+ * I uphold.
+ *
+ * It is also influenced heavily by MochiKit's serializeJSON, which is
+ * copyrighted 2005 by Bob Ippolito.
+ */
+ steal.plugins('jquery').then(function(){
+(function($) {
+ /** jQuery.toJSON( json-serializble )
+ Converts the given argument into a JSON respresentation.
+
+ If an object has a "toJSON" function, that will be used to get the representation.
+ Non-integer/string keys are skipped in the object, as are keys that point to a function.
+
+ json-serializble:
+ The *thing* to be converted.
+ **/
+ $.toJSON = function(o, replacer, space, recurse)
+ {
+ if (typeof(JSON) == 'object' && JSON.stringify)
+ return JSON.stringify(o, replacer, space);
+
+ if (!recurse && $.isFunction(replacer))
+ o = replacer("", o);
+
+ if (typeof space == "number")
+ space = " ".substring(0, space);
+ space = (typeof space == "string") ? space.substring(0, 10) : "";
+
+ var type = typeof(o);
+
+ if (o === null)
+ return "null";
+
+ if (type == "undefined" || type == "function")
+ return undefined;
+
+ if (type == "number" || type == "boolean")
+ return o + "";
+
+ if (type == "string")
+ return $.quoteString(o);
+
+ if (type == 'object')
+ {
+ if (typeof o.toJSON == "function")
+ return $.toJSON( o.toJSON(), replacer, space, true );
+
+ if (o.constructor === Date)
+ {
+ var month = o.getUTCMonth() + 1;
+ if (month < 10) month = '0' + month;
+
+ var day = o.getUTCDate();
+ if (day < 10) day = '0' + day;
+
+ var year = o.getUTCFullYear();
+
+ var hours = o.getUTCHours();
+ if (hours < 10) hours = '0' + hours;
+
+ var minutes = o.getUTCMinutes();
+ if (minutes < 10) minutes = '0' + minutes;
+
+ var seconds = o.getUTCSeconds();
+ if (seconds < 10) seconds = '0' + seconds;
+
+ var milli = o.getUTCMilliseconds();
+ if (milli < 100) milli = '0' + milli;
+ if (milli < 10) milli = '0' + milli;
+
+ return '"' + year + '-' + month + '-' + day + 'T' +
+ hours + ':' + minutes + ':' + seconds +
+ '.' + milli + 'Z"';
+ }
+
+ var process = ($.isFunction(replacer)) ?
+ function (k, v) { return replacer(k, v); } :
+ function (k, v) { return v; },
+ nl = (space) ? "\n" : "",
+ sp = (space) ? " " : "";
+
+ if (o.constructor === Array)
+ {
+ var ret = [];
+ for (var i = 0; i < o.length; i++)
+ ret.push(( $.toJSON( process(i, o[i]), replacer, space, true ) || "null" ).replace(/^/gm, space));
+
+ return "[" + nl + ret.join("," + nl) + nl + "]";
+ }
+
+ var pairs = [], proplist;
+ if ($.isArray(replacer)) {
+ proplist = $.map(replacer, function (v) {
+ return (typeof v == "string" || typeof v == "number") ?
+ v + "" :
+ null;
+ });
+ }
+ for (var k in o) {
+ var name, val, type = typeof k;
+
+ if (proplist && $.inArray(k + "", proplist) == -1)
+ continue;
+
+ if (type == "number")
+ name = '"' + k + '"';
+ else if (type == "string")
+ name = $.quoteString(k);
+ else
+ continue; //skip non-string or number keys
+
+ val = $.toJSON( process(k, o[k]), replacer, space, true );
+
+ if (typeof val == "undefined")
+ continue; //skip pairs where the value is a function.
+
+ pairs.push((name + ":" + sp + val).replace(/^/gm, space));
+ }
+
+ return "{" + nl + pairs.join("," + nl) + nl + "}";
+ }
+ };
+
+ /** jQuery.evalJSON(src)
+ Evaluates a given piece of json source.
+ **/
+ $.evalJSON = function(src)
+ {
+ if (typeof(JSON) == 'object' && JSON.parse)
+ return JSON.parse(src);
+ return eval("(" + src + ")");
+ };
+
+ /** jQuery.secureEvalJSON(src)
+ Evals JSON in a way that is *more* secure.
+ **/
+ $.secureEvalJSON = function(src)
+ {
+ if (typeof(JSON) == 'object' && JSON.parse)
+ return JSON.parse(src);
+
+ var filtered = src;
+ filtered = filtered.replace(/\\["\\\/bfnrtu]/g, '@');
+ filtered = filtered.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']');
+ filtered = filtered.replace(/(?:^|:|,)(?:\s*\[)+/g, '');
+
+ if (/^[\],:{}\s]*$/.test(filtered))
+ return eval("(" + src + ")");
+ else
+ throw new SyntaxError("Error parsing JSON, source is not valid.");
+ };
+
+ /** jQuery.quoteString(string)
+ Returns a string-repr of a string, escaping quotes intelligently.
+ Mostly a support function for toJSON.
+
+ Examples:
+ >>> jQuery.quoteString("apple")
+ "apple"
+
+ >>> jQuery.quoteString('"Where are we going?", she asked.')
+ "\"Where are we going?\", she asked."
+ **/
+ $.quoteString = function(string)
+ {
+ if (string.match(_escapeable))
+ {
+ return '"' + string.replace(_escapeable, function (a)
+ {
+ var c = _meta[a];
+ if (typeof c === 'string') return c;
+ c = a.charCodeAt();
+ return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
+ }) + '"';
+ }
+ return '"' + string + '"';
+ };
+
+ var _escapeable = /["\\\x00-\x1f\x7f-\x9f]/g;
+
+ var _meta = {
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',
+ '\\': '\\\\'
+ };
+})(jQuery);
+})
\ No newline at end of file
Added: openejb/trunk/openejb3/examples/webapps/rest-example/src/main/webapp/jquery/lang/lang.js
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/examples/webapps/rest-example/src/main/webapp/jquery/lang/lang.js?rev=1149378&view=auto
==============================================================================
--- openejb/trunk/openejb3/examples/webapps/rest-example/src/main/webapp/jquery/lang/lang.js (added)
+++ openejb/trunk/openejb3/examples/webapps/rest-example/src/main/webapp/jquery/lang/lang.js Thu Jul 21 21:42:07 2011
@@ -0,0 +1,160 @@
+//string helpers
+steal.plugins('jquery').then(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;
+ }
+ });
+
+});
\ No newline at end of file
Added: openejb/trunk/openejb3/examples/webapps/rest-example/src/main/webapp/jquery/lang/openajax/openajax.js
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/examples/webapps/rest-example/src/main/webapp/jquery/lang/openajax/openajax.js?rev=1149378&view=auto
==============================================================================
--- openejb/trunk/openejb3/examples/webapps/rest-example/src/main/webapp/jquery/lang/openajax/openajax.js (added)
+++ openejb/trunk/openejb3/examples/webapps/rest-example/src/main/webapp/jquery/lang/openajax/openajax.js Thu Jul 21 21:42:07 2011
@@ -0,0 +1,202 @@
+//@steal-clean
+/*******************************************************************************
+ * OpenAjax.js
+ *
+ * Reference implementation of the OpenAjax Hub, as specified by OpenAjax Alliance.
+ * Specification is under development at:
+ *
+ * http://www.openajax.org/member/wiki/OpenAjax_Hub_Specification
+ *
+ * Copyright 2006-2008 OpenAjax Alliance
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0 . Unless
+ * required by applicable law or agreed to in writing, software distributed
+ * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ ******************************************************************************/
+steal.then(function(){
+// prevent re-definition of the OpenAjax object
+if(!window["OpenAjax"]){
+ /**
+ * @class OpenAjax
+ * Use OpenAjax.hub to publish and subscribe to messages.
+ */
+ OpenAjax = new function(){
+ var t = true;
+ var f = false;
+ var g = window;
+ var ooh = "org.openajax.hub.";
+
+ var h = {};
+ this.hub = h;
+ h.implementer = "http://openajax.org";
+ h.implVersion = "1.0";
+ h.specVersion = "1.0";
+ h.implExtraData = {};
+ var libs = {};
+ h.libraries = libs;
+
+ h.registerLibrary = function(prefix, nsURL, version, extra){
+ libs[prefix] = {
+ prefix: prefix,
+ namespaceURI: nsURL,
+ version: version,
+ extraData: extra
+ };
+ this.publish(ooh+"registerLibrary", libs[prefix]);
+ }
+ h.unregisterLibrary = function(prefix){
+ this.publish(ooh+"unregisterLibrary", libs[prefix]);
+ delete libs[prefix];
+ }
+
+ h._subscriptions = { c:{}, s:[] };
+ h._cleanup = [];
+ h._subIndex = 0;
+ h._pubDepth = 0;
+
+ h.subscribe = function(name, callback, scope, subscriberData, filter)
+ {
+ if(!scope){
+ scope = window;
+ }
+ var handle = name + "." + this._subIndex;
+ var sub = { scope: scope, cb: callback, fcb: filter, data: subscriberData, sid: this._subIndex++, hdl: handle };
+ var path = name.split(".");
+ this._subscribe(this._subscriptions, path, 0, sub);
+ return handle;
+ }
+
+ h.publish = function(name, message)
+ {
+ var path = name.split(".");
+ this._pubDepth++;
+ this._publish(this._subscriptions, path, 0, name, message);
+ this._pubDepth--;
+ if((this._cleanup.length > 0) && (this._pubDepth == 0)) {
+ for(var i = 0; i < this._cleanup.length; i++)
+ this.unsubscribe(this._cleanup[i].hdl);
+ delete(this._cleanup);
+ this._cleanup = [];
+ }
+ }
+
+ h.unsubscribe = function(sub)
+ {
+ var path = sub.split(".");
+ var sid = path.pop();
+ this._unsubscribe(this._subscriptions, path, 0, sid);
+ }
+
+ h._subscribe = function(tree, path, index, sub)
+ {
+ var token = path[index];
+ if(index == path.length)
+ tree.s.push(sub);
+ else {
+ if(typeof tree.c == "undefined")
+ tree.c = {};
+ if(typeof tree.c[token] == "undefined") {
+ tree.c[token] = { c: {}, s: [] };
+ this._subscribe(tree.c[token], path, index + 1, sub);
+ }
+ else
+ this._subscribe( tree.c[token], path, index + 1, sub);
+ }
+ }
+
+ h._publish = function(tree, path, index, name, msg, pcb, pcid) {
+ if(typeof tree != "undefined") {
+ var node;
+ if(index == path.length) {
+ node = tree;
+ } else {
+ this._publish(tree.c[path[index]], path, index + 1, name, msg, pcb, pcid);
+ this._publish(tree.c["*"], path, index + 1, name, msg, pcb, pcid);
+ node = tree.c["**"];
+ }
+ if(typeof node != "undefined") {
+ var callbacks = node.s;
+ var max = callbacks.length;
+ for(var i = 0; i < max; i++) {
+ if(callbacks[i].cb) {
+ var sc = callbacks[i].scope;
+ var cb = callbacks[i].cb;
+ var fcb = callbacks[i].fcb;
+ var d = callbacks[i].data;
+ var sid = callbacks[i].sid;
+ var scid = callbacks[i].cid;
+ if(typeof cb == "string"){
+ // get a function object
+ cb = sc[cb];
+ }
+ if(typeof fcb == "string"){
+ // get a function object
+ fcb = sc[fcb];
+ }
+ if((!fcb) || (fcb.call(sc, name, msg, d))) {
+ if((!pcb) || (pcb(name, msg, pcid, scid))) {
+ cb.call(sc, name, msg, d, sid);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ h._unsubscribe = function(tree, path, index, sid) {
+ if(typeof tree != "undefined") {
+ if(index < path.length) {
+ var childNode = tree.c[path[index]];
+ this._unsubscribe(childNode, path, index + 1, sid);
+ if(childNode.s.length == 0) {
+ for(var x in childNode.c)
+ return;
+ delete tree.c[path[index]];
+ }
+ return;
+ }
+ else {
+ var callbacks = tree.s;
+ var max = callbacks.length;
+ for(var i = 0; i < max; i++)
+ if(sid == callbacks[i].sid) {
+ if(this._pubDepth > 0) {
+ callbacks[i].cb = null;
+ this._cleanup.push(callbacks[i]);
+ }
+ else
+ callbacks.splice(i, 1);
+ return;
+ }
+ }
+ }
+ }
+ // The following function is provided for automatic testing purposes.
+ // It is not expected to be deployed in run-time OpenAjax Hub implementations.
+ h.reinit = function()
+ {
+ for (var lib in OpenAjax.hub.libraries) {
+ delete OpenAjax.hub.libraries[lib];
+ }
+ OpenAjax.hub.registerLibrary("OpenAjax", "http://openajax.org/hub", "1.0", {});
+
+ delete OpenAjax._subscriptions;
+ OpenAjax._subscriptions = {c:{},s:[]};
+ delete OpenAjax._cleanup;
+ OpenAjax._cleanup = [];
+ OpenAjax._subIndex = 0;
+ OpenAjax._pubDepth = 0;
+ }
+ };
+ // Register the OpenAjax Hub itself as a library.
+ OpenAjax.hub.registerLibrary("OpenAjax", "http://openajax.org/hub", "1.0", {});
+
+}
+OpenAjax.hub.registerLibrary("JavaScriptMVC", "http://JavaScriptMVC.com", "3.0", {});
+});
\ No newline at end of file
Added: openejb/trunk/openejb3/examples/webapps/rest-example/src/main/webapp/jquery/lang/rsplit/rsplit.js
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/examples/webapps/rest-example/src/main/webapp/jquery/lang/rsplit/rsplit.js?rev=1149378&view=auto
==============================================================================
--- openejb/trunk/openejb3/examples/webapps/rest-example/src/main/webapp/jquery/lang/rsplit/rsplit.js (added)
+++ openejb/trunk/openejb3/examples/webapps/rest-example/src/main/webapp/jquery/lang/rsplit/rsplit.js Thu Jul 21 21:42:07 2011
@@ -0,0 +1,35 @@
+steal.plugins('jquery/lang').then(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;
+ };
+});
\ No newline at end of file
Added: openejb/trunk/openejb3/examples/webapps/rest-example/src/main/webapp/jquery/model/model.js
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/examples/webapps/rest-example/src/main/webapp/jquery/model/model.js?rev=1149378&view=auto
==============================================================================
--- openejb/trunk/openejb3/examples/webapps/rest-example/src/main/webapp/jquery/model/model.js (added)
+++ openejb/trunk/openejb3/examples/webapps/rest-example/src/main/webapp/jquery/model/model.js Thu Jul 21 21:42:07 2011
@@ -0,0 +1,1638 @@
+/*global OpenAjax: true */
+
+steal.plugins('jquery/class', 'jquery/lang').then(function() {
+
+ //helper stuff for later. Eventually, might not need jQuery.
+ var underscore = $.String.underscore,
+ classize = $.String.classize,
+ isArray = $.isArray,
+ makeArray = $.makeArray,
+ extend = $.extend,
+ each = $.each,
+ reqType = /GET|POST|PUT|DELETE/i,
+ ajax = function(ajaxOb, attrs, success, error, fixture, type, dataType){
+ var dataType = dataType || "json",
+ src = "",
+ tmp;
+ if(typeof ajaxOb == "string"){
+ var sp = ajaxOb.indexOf(" ")
+ if( sp > 2 && sp <7){
+ tmp = ajaxOb.substr(0,sp);
+ if(reqType.test(tmp)){
+ type = tmp;
+ }else{
+ dataType = tmp;
+ }
+ src = ajaxOb.substr(sp+1)
+ }else{
+ src = ajaxOb;
+ }
+ }
+ attrs = extend({},attrs)
+
+ var url = $.String.sub(src, attrs, true)
+ return $.ajax({
+ url : url,
+ data : attrs,
+ success : success,
+ error: error,
+ type : type || "post",
+ dataType : dataType,
+ fixture: fixture
+ });
+ },
+ addId = function(attrs, id){
+ attrs = attrs || {};
+ var identity = this.id;
+ if(attrs[identity] && attrs[identity] !== id){
+ attrs["new"+$.String.capitalize(id)] = attrs[identity];
+ delete attrs[identity];
+ }
+ attrs[identity] = id;
+ return attrs;
+ },
+ getList = function(type){
+ var listType = type || $.Model.List || Array;
+ return new listType();
+ },
+ getId = function(inst){
+ return inst[inst.Class.id]
+ },
+ unique = function(items){
+ var collect = [];
+ for(var i=0; i < items.length; i++){
+ if(!items[i]["__u Nique"]){
+ collect.push(items[i]);
+ items[i]["__u Nique"] = true;
+ }
+ }
+ for(i=0; i< collect.length; i++){
+ delete collect[i]["__u Nique"];
+ }
+ return collect;
+ },
+ // makes a deferred request
+ makeRequest = function(self, type, success, error, method){
+ var deferred = $.Deferred(),
+ resolve = function(data){
+ self[method || type+"d"](data);
+ deferred.resolveWith(self,[self, data, type]);
+ },
+ reject = function(data){
+ deferred.rejectWith(self, [data])
+ },
+ args = [self.attrs(), resolve, reject];
+
+ if(type == 'destroy'){
+ args.shift();
+ }
+
+ if(type !== 'create' ){
+ args.unshift(getId(self))
+ }
+
+ deferred.then(success);
+ deferred.fail(error);
+
+ self.Class[type].apply(self.Class, args);
+
+ return deferred.promise();
+ },
+ // a quick way to tell if it's an object and not some string
+ isObject = function(obj){
+ return typeof obj === 'object' && obj !== null && obj;
+ },
+ $method = function(name){
+ return function( eventType, handler ) {
+ $.fn[name].apply($([this]), arguments);
+ return this;
+ }
+ },
+ bind = $method('bind'),
+ unbind = $method('unbind');
+ /**
+ * @class jQuery.Model
+ * @tag core
+ * @download http://jmvcsite.heroku.com/pluginify?plugins[]=jquery/model/model.js
+ * @test jquery/model/qunit.html
+ * @plugin jquery/model
+ *
+ * Models wrap an application's data layer. In large applications, a model is critical for:
+ *
+ * - [jquery.model.encapsulate Encapsulating] services so controllers + views don't care where data comes from.
+ *
+ * - Providing helper functions that make manipulating and abstracting raw service data easier.
+ *
+ * This is done in two ways:
+ *
+ * - Requesting data from and interacting with services
+ *
+ * - Converting or wrapping raw service data into a more useful form.
+ *
+ *
+ * ## Basic Use
+ *
+ * The [jQuery.Model] class provides a basic skeleton to organize pieces of your application's data layer.
+ * First, consider doing Ajax <b>without</b> a model. In our imaginary app, you:
+ *
+ * - retrieve a list of tasks</li>
+ * - display the number of days remaining for each task
+ * - mark tasks as complete after users click them
+ *
+ * Let's see how that might look without a model:
+ *
+ * @codestart
+ * $.Controller("Tasks",
+ * {
+ * // get tasks when the page is ready
+ * init: function() {
+ * $.get('/tasks.json', this.callback('gotTasks'), 'json')
+ * },
+ * |*
+ * * assume json is an array like [{name: "trash", due_date: 1247111409283}, ...]
+ * *|
+ * gotTasks: function( json ) {
+ * for(var i =0; i < json.length; i++){
+ * var taskJson = json[i];
+ *
+ * //calculate time remaining
+ * var remaininTime = new Date() - new Date(taskJson.due_date);
+ *
+ * //append some html
+ * $("#tasks").append("<div class='task' taskid='"+taskJson.id+"'>"+
+ * "<label>"+taskJson.name+"</label>"+
+ * "Due Date = "+remaininTime+"</div>")
+ * }
+ * },
+ * // when a task is complete, get the id, make a request, remove it
+ * ".task click" : function( el ) {
+ * $.post('/tasks/'+el.attr('data-taskid')+'.json',
+ * {complete: true},
+ * function(){
+ * el.remove();
+ * })
+ * }
+ * })
+ * @codeend
+ *
+ * This code might seem fine for right now, but what if:
+ *
+ * - The service changes?
+ * - Other parts of the app want to calculate <code>remaininTime</code>?
+ * - Other parts of the app want to get tasks?</li>
+ * - The same task is represented multiple palces on the page?
+ *
+ * The solution is of course a strong model layer. Lets look at what a
+ * a good model does for a controller before we learn how to make one:
+ *
+ * @codestart
+ * $.Controller("Tasks",
+ * {
+ * init: function() {
+ * Task.findAll({}, this.callback('tasks'));
+ * },
+ * list : function(todos){
+ * this.element.html("tasks.ejs", todos );
+ * },
+ * ".task click" : function( el ) {
+ * el.model().update({complete: true},function(){
+ * el.remove();
+ * });
+ * }
+ * });
+ * @codeend
+ *
+ * In tasks.ejs
+ *
+ * @codestart html
+ * <% for(var i =0; i < tasks.length; i++){ %>
+ * <div <%= tasks[i] %>>
+ * <label><%= tasks[i].name %></label>
+ * <%= tasks[i].<b>timeRemaining</b>() %>
+ * </div>
+ * <% } %>
+ * @codeend
+ *
+ * Isn't that better! Granted, some of the improvement comes because we used a view, but we've
+ * also made our controller completely understandable. Now lets take a look at the model:
+ *
+ * @codestart
+ * $.Model("Task",
+ * {
+ * findAll: "/tasks.json",
+ * update: "/tasks/{id}.json"
+ * },
+ * {
+ * timeRemaining: function() {
+ * return new Date() - new Date(this.due_date)
+ * }
+ * })
+ * @codeend
+ *
+ * Much better! Now you have a single place where you
+ * can organize Ajax functionality and
+ * wrap the data that it returned. Lets go through
+ * each bolded item in the controller and view.
+ *
+ * ### Task.findAll
+ *
+ * The findAll function requests data from "/tasks.json". When the data is returned,
+ * it converted by the [jQuery.Model.static.models models] function before being
+ * passed to the success callback.
+ *
+ * ### el.model
+ *
+ * [jQuery.fn.model] is a jQuery helper that returns a model instance from an element. The
+ * list.ejs template assings tasks to elements with the following line:
+ *
+ * @codestart html
+ * <div <%= tasks[i] %>> ... </div>
+ * @codeend
+ *
+ * ### timeRemaining
+ *
+ * timeRemaining is an example of wrapping your model's raw data with more useful functionality.
+ *
+ * ## Other Good Stuff
+ *
+ * This is just a tiny taste of what models can do. Check out these other features:
+ *
+ * ### [jquery.model.encapsulate Encapsulation]
+ *
+ * Learn how to connect to services.
+ *
+ * $.Model("Task",{
+ * findAll : "/tasks.json",
+ * findOne : "/tasks/{id}.json",
+ * create : "/tasks.json",
+ * update : "/tasks/{id}.json"
+ * },{})
+ *
+ * ### [jquery.model.typeconversion Type Conversion]
+ *
+ * Convert data like "10-20-1982" into new Date(1982,9,20) auto-magically:
+ *
+ * $.Model("Task",{
+ * attributes : {birthday : "date"}
+ * convert : {
+ * date : function(raw){ ... }
+ * }
+ * },{})
+ *
+ * ### [jQuery.Model.List]
+ *
+ * Learn how to handle multiple instances with ease.
+ *
+ * $.Model.List("Task.List",{
+ * destroyAll : function(){
+ * var ids = this.map(function(c){ return c.id });
+ * $.post("/destroy",
+ * ids,
+ * this.callback('destroyed'),
+ * 'json')
+ * },
+ * destroyed : function(){
+ * this.each(function(){ this.destroyed() });
+ * }
+ * });
+ *
+ * ".destroyAll click" : function(){
+ * this.find('.destroy:checked')
+ * .closest('.task')
+ * .models()
+ * .destroyAll();
+ * }
+ *
+ * ### [jquery.model.validations Validations]
+ *
+ * Validate your model's attributes.
+ *
+ * $.Model("Contact",{
+ * init : function(){
+ * this.validate("birthday",function(){
+ * if(this.birthday > new Date){
+ * return "your birthday needs to be in the past"
+ * }
+ * })
+ * }
+ * ,{});
+ *
+ *
+ */
+ // methods that we'll weave into model if provided
+ ajaxMethods =
+ /**
+ * @Static
+ */
+ {
+ create: function(str ) {
+ /**
+ * @function create
+ * Create is used to create a model instance on the server. By implementing
+ * create along with the rest of the [jquery.model.services service api], your models provide an abstract
+ * API for services.
+ *
+ * Create is called by save to create a new instance. If you want to be able to call save on an instance
+ * you have to implement create.
+ *
+ * The easiest way to implement create is to just give it the url to post data to:
+ *
+ * $.Model("Recipe",{
+ * create: "/recipes"
+ * },{})
+ *
+ * This lets you create a recipe like:
+ *
+ * new Recipe({name: "hot dog"}).save(function(){
+ * this.name //this is the new recipe
+ * }).save(callback)
+ *
+ * You can also implement create by yourself. You just need to call success back with
+ * an object that contains the id of the new instance and any other properties that should be
+ * set on the instance.
+ *
+ * For example, the following code makes a request
+ * to '/recipes.json?name=hot+dog' and gets back
+ * something that looks like:
+ *
+ * {
+ * id: 5,
+ * createdAt: 2234234329
+ * }
+ *
+ * The code looks like:
+ *
+ * $.Model("Recipe", {
+ * create : function(attrs, success, error){
+ * $.post("/recipes.json",attrs, success,"json");
+ * }
+ * },{})
+ *
+ * ## API
+ *
+ * @param {Object} attrs Attributes on the model instance
+ * @param {Function} success(attrs) the callback function, it must be called with an object
+ * that has the id of the new instance and any other attributes the service needs to add.
+ * @param {Function} error a function to callback if something goes wrong.
+ */
+ return function(attrs, success, error){
+ return ajax(str, attrs, success, error, "-restCreate")
+ };
+ },
+ update: function( str ) {
+ /**
+ * @function update
+ * Update is used to update a model instance on the server. By implementing
+ * update along with the rest of the [jquery.model.services service api], your models provide an abstract
+ * API for services.
+ *
+ * Update is called by [jQuery.Model.prototype.save] or [jQuery.Model.prototype.update]
+ * on an existing model instance. If you want to be able to call save on an instance
+ * you have to implement update.
+ *
+ * The easist way to implement update is to just give it the url to put data to:
+ *
+ * $.Model("Recipe",{
+ * create: "/recipes/{id}"
+ * },{})
+ *
+ * This lets you update a recipe like:
+ *
+ * // PUT /recipes/5 {name: "Hot Dog"}
+ * recipe.update({name: "Hot Dog"},
+ * function(){
+ * this.name //this is the updated recipe
+ * })
+ *
+ * If your server doesn't use PUT, you can change it to post like:
+ *
+ * $.Model("Recipe",{
+ * create: "POST /recipes/{id}"
+ * },{})
+ *
+ * Your server should send back an object with any new attributes the model
+ * should have. For example if your server udpates the "updatedAt" property, it
+ * should send back something like:
+ *
+ * // PUT /recipes/4 {name: "Food"} ->
+ * {
+ * updatedAt : "10-20-2011"
+ * }
+ *
+ * You can also implement create by yourself. You just need to call success back with
+ * an object that contains any properties that should be
+ * set on the instance.
+ *
+ * For example, the following code makes a request
+ * to '/recipes/5.json?name=hot+dog' and gets back
+ * something that looks like:
+ *
+ * {
+ * updatedAt: "10-20-2011"
+ * }
+ *
+ * The code looks like:
+ *
+ * $.Model("Recipe", {
+ * update : function(id, attrs, success, error){
+ * $.post("/recipes/"+id+".json",attrs, success,"json");
+ * }
+ * },{})
+ *
+ * ## API
+ *
+ * @param {String} id the id of the model instance
+ * @param {Object} attrs Attributes on the model instance
+ * @param {Function} success(attrs) the callback function, it must be called with an object
+ * that has the id of the new instance and any other attributes the service needs to add.
+ * @param {Function} error a function to callback if something goes wrong.
+ */
+ return function(id, attrs, success, error){
+ return ajax(str, addId.call(this,attrs, id), success, error, "-restUpdate","put")
+ }
+ },
+ destroy: function( str ) {
+ /**
+ * @function destroy
+ * Destroy is used to remove a model instance from the server. By implementing
+ * destroy along with the rest of the [jquery.model.services service api], your models provide an abstract
+ * service API.
+ *
+ * You can implement destroy with a string like:
+ *
+ * $.Model("Thing",{
+ * destroy : "POST /thing/destroy/{id}"
+ * })
+ *
+ * Or you can implement destroy manually like:
+ *
+ * $.Model("Thing",{
+ * destroy : function(id, success, error){
+ * $.post("/thing/destroy/"+id,{}, success);
+ * }
+ * })
+ *
+ * You just have to call success if the destroy was successful.
+ *
+ * @param {String|Number} id the id of the instance you want destroyed
+ * @param {Function} success the callback function, it must be called with an object
+ * that has the id of the new instance and any other attributes the service needs to add.
+ * @param {Function} error a function to callback if something goes wrong.
+ */
+ return function( id, success, error ) {
+ var attrs = {};
+ attrs[this.id] = id;
+ return ajax(str, attrs, success, error, "-restDestroy","delete")
+ }
+ },
+
+ findAll: function( str ) {
+ /**
+ * @function findAll
+ * FindAll is used to retrive a model instances from the server. By implementing
+ * findAll along with the rest of the [jquery.model.services service api], your models provide an abstract
+ * service API.
+ * findAll returns a deferred ($.Deferred)
+ *
+ * You can implement findAll with a string:
+ *
+ * $.Model("Thing",{
+ * findAll : "/things.json"
+ * },{})
+ *
+ * Or you can implement it yourself. The 'dataType' attribute is used to convert a JSON array of attributes
+ * to an array of instances. For example:
+ *
+ * $.Model("Thing",{
+ * findAll : function(params, success, error){
+ * return $.ajax({
+ * url: '/things.json',
+ * type: 'get',
+ * dataType: 'json thing.models',
+ * data: params,
+ * success: success,
+ * error: error})
+ * }
+ * },{})
+ *
+ * ## API
+ *
+ * @param {Object} params data to refine the results. An example might be passing {limit : 20} to
+ * limit the number of items retrieved.
+ * @param {Function} success(items) called with an array (or Model.List) of model instances.
+ * @param {Function} error
+ */
+ return function(params, success, error){
+ return ajax(str || this.shortName+"s.json",
+ params,
+ success,
+ error,
+ fixture.call(this,"s"),
+ "get",
+ "json "+this._shortName+".models");
+ };
+ },
+ findOne: function( str ) {
+ /**
+ * @function findOne
+ * FindOne is used to retrive a model instances from the server. By implementing
+ * findOne along with the rest of the [jquery.model.services service api], your models provide an abstract
+ * service API.
+ *
+ * You can implement findOne with a string:
+ *
+ * $.Model("Thing",{
+ * findOne : "/things/{id}.json"
+ * },{})
+ *
+ * Or you can implement it yourself.
+ *
+ * $.Model("Thing",{
+ * findOne : function(params, success, error){
+ * var self = this,
+ * id = params.id;
+ * delete params.id;
+ * return $.get("/things/"+id+".json",
+ * params,
+ * success,
+ * "json thing.model")
+ * }
+ * },{})
+ *
+ * ## API
+ *
+ * @param {Object} params data to refine the results. This is often something like {id: 5}.
+ * @param {Function} success(item) called with a model instance
+ * @param {Function} error
+ */
+ return function(params, success, error){
+ return ajax(str,
+ params,
+ success,
+ error,
+ fixture.call(this),
+ "get",
+ "json "+this._shortName+".model");
+ };
+ }
+ };
+
+
+
+
+
+ jQuery.Class("jQuery.Model", {
+ setup: function( superClass , stat, proto) {
+ //we do not inherit attributes (or associations)
+ var self=this;
+ each(["attributes","associations","validations"],function(i,name){
+ if (!self[name] || superClass[name] === self[name] ) {
+ self[name] = {};
+ }
+ })
+
+ //add missing converters
+ if ( superClass.convert != this.convert ) {
+ this.convert = extend(superClass.convert, this.convert);
+ }
+
+
+ this._fullName = underscore(this.fullName.replace(/\./g, "_"));
+ this._shortName = underscore(this.shortName);
+
+ if ( this.fullName.substr(0, 7) == "jQuery." ) {
+ return;
+ }
+
+ //add this to the collection of models
+ //jQuery.Model.models[this._fullName] = this;
+
+ if ( this.listType ) {
+ this.list = new this.listType([]);
+ }
+ //@steal-remove-start
+ if (! proto ) {
+ steal.dev.warn("model.js "+this.fullName+" has no static properties. You probably need ,{} ")
+ }
+ //@steal-remove-end
+ for(var name in ajaxMethods){
+ if(typeof this[name] !== 'function'){
+ this[name] = ajaxMethods[name](this[name]);
+ }
+ }
+
+ //add ajax converters
+ var converters = {},
+ convertName = "* "+this._shortName+".model";
+ converters[convertName+"s"] = this.callback('models');
+ converters[convertName] = this.callback('model');
+ $.ajaxSetup({
+ converters : converters
+ });
+ },
+ /**
+ * @attribute attributes
+ * Attributes contains a list of properties and their types
+ * for this model. You can use this in conjunction with
+ * [jQuery.Model.static.convert] to provide automatic
+ * [jquery.model.typeconversion type conversion].
+ *
+ * The following converts dueDates to JavaScript dates:
+ *
+ * @codestart
+ * $.Model("Contact",{
+ * attributes : {
+ * birthday : 'date'
+ * },
+ * convert : {
+ * date : function(raw){
+ * if(typeof raw == 'string'){
+ * var matches = raw.match(/(\d+)-(\d+)-(\d+)/)
+ * return new Date( matches[1],
+ * (+matches[2])-1,
+ * matches[3] )
+ * }else if(raw instanceof Date){
+ * return raw;
+ * }
+ * }
+ * }
+ * },{})
+ * @codeend
+ */
+ attributes: {},
+ /**
+ * @function wrap
+ * @hide
+ * @tag deprecated
+ * __warning__ : wrap is deprecated in favor of [jQuery.Model.static.model]. They
+ * provide the same functionality; however, model works better with Deferreds.
+ *
+ * Wrap is used to create a new instance from data returned from the server.
+ * It is very similar to doing <code> new Model(attributes) </code>
+ * except that wrap will check if the data passed has an
+ *
+ * - attributes,
+ * - data, or
+ * - <i>singularName</i>
+ *
+ * property. If it does, it will use that objects attributes.
+ *
+ * Wrap is really a convience method for servers that don't return just attributes.
+ *
+ * @param {Object} attributes
+ * @return {Model} an instance of the model
+ */
+ // wrap place holder
+ /**
+ * $.Model.model is used as a [http://api.jquery.com/extending-ajax/#Converters Ajax converter]
+ * to convert the response of a [jQuery.Model.static.findOne] request
+ * into a model instance.
+ *
+ * You will never call this method directly. Instead, you tell $.ajax about it in findOne:
+ *
+ * $.Model('Recipe',{
+ * findOne : function(params, success, error ){
+ * return $.ajax({
+ * url: '/services/recipes/'+params.id+'.json',
+ * type: 'get',
+ *
+ * dataType : 'json recipe.model' //LOOK HERE!
+ * });
+ * }
+ * },{})
+ *
+ * This makes the result of findOne a [http://api.jquery.com/category/deferred-object/ $.Deferred]
+ * that resolves to a model instance:
+ *
+ * var deferredRecipe = Recipe.findOne({id: 6});
+ *
+ * deferredRecipe.then(function(recipe){
+ * console.log('I am '+recipes.description+'.');
+ * })
+ *
+ * ## Non-standard Services
+ *
+ * $.jQuery.model expects data to be name-value pairs like:
+ *
+ * {id: 1, name : "justin"}
+ *
+ * It can also take an object with attributes in a data, attributes, or
+ * 'shortName' property. For a App.Models.Person model the following will all work:
+ *
+ * { data : {id: 1, name : "justin"} }
+ *
+ * { attributes : {id: 1, name : "justin"} }
+ *
+ * { person : {id: 1, name : "justin"} }
+ *
+ *
+ * ### Overwriting Model
+ *
+ * If your service returns data like:
+ *
+ * {id : 1, name: "justin", data: {foo : "bar"} }
+ *
+ * This will confuse $.Model.model. You will want to overwrite it to create
+ * an instance manually:
+ *
+ * $.Model('Person',{
+ * model : function(data){
+ * return new this(data);
+ * }
+ * },{})
+ *
+ * ## API
+ *
+ * @param {Object} attributes An object of name-value pairs or an object that has a
+ * data, attributes, or 'shortName' property that maps to an object of name-value pairs.
+ * @return {Model} an instance of the model
+ */
+ model: function( attributes ) {
+ if (!attributes ) {
+ return null;
+ }
+ return new this(
+ // checks for properties in an object (like rails 2.0 gives);
+ isObject(attributes[this._shortName]) ||
+ isObject(attributes.data) ||
+ isObject(attributes.attributes) ||
+ attributes);
+ },
+ /**
+ * @function wrapMany
+ * @hide
+ * @tag deprecated
+ *
+ * __warning__ : wrapMany is deprecated in favor of [jQuery.Model.static.models]. They
+ * provide the same functionality; however, models works better with Deferreds.
+ *
+ * $.Model.wrapMany converts a raw array of JavaScript Objects into an array (or [jQuery.Model.List $.Model.List]) of model instances.
+ *
+ * // a Recipe Model wi
+ * $.Model("Recipe",{
+ * squareId : function(){
+ * return this.id*this.id;
+ * }
+ * })
+ *
+ * var recipes = Recipe.wrapMany([{id: 1},{id: 2}])
+ * recipes[0].squareId() //-> 1
+ *
+ * If an array is not passed to wrapMany, it will look in the object's .data
+ * property.
+ *
+ * For example:
+ *
+ * var recipes = Recipe.wrapMany({data: [{id: 1},{id: 2}]})
+ * recipes[0].squareId() //-> 1
+ *
+ *
+ * Often wrapMany is used with this.callback inside a model's [jQuery.Model.static.findAll findAll]
+ * method like:
+ *
+ * findAll : function(params, success, error){
+ * $.get('/url',
+ * params,
+ * this.callback(['wrapMany',success]), 'json' )
+ * }
+ *
+ * If you are having problems getting your model to callback success correctly,
+ * make sure a request is being made (with firebug's net tab). Also, you
+ * might not use this.callback and instead do:
+ *
+ * findAll : function(params, success, error){
+ * self = this;
+ * $.get('/url',
+ * params,
+ * function(data){
+ * var wrapped = self.wrapMany(data);
+ * success(wrapped)
+ * },
+ * 'json')
+ * }
+ *
+ * ## API
+ *
+ * @param {Array} instancesRawData an array of raw name - value pairs like
+ *
+ * [{name: "foo", id: 4},{name: "bar", id: 5}]
+ *
+ * @return {Array} a JavaScript array of instances or a [jQuery.Model.List list] of instances
+ * if the model list plugin has been included.
+ */
+ // wrapMany placeholder
+ /**
+ * $.Model.models is used as a [http://api.jquery.com/extending-ajax/#Converters Ajax converter]
+ * to convert the response of a [jQuery.Model.static.findAll] request
+ * into an array (or [jQuery.Model.List $.Model.List]) of model instances.
+ *
+ * You will never call this method directly. Instead, you tell $.ajax about it in findAll:
+ *
+ * $.Model('Recipe',{
+ * findAll : function(params, success, error ){
+ * return $.ajax({
+ * url: '/services/recipes.json',
+ * type: 'get',
+ * data: params
+ *
+ * dataType : 'json recipe.models' //LOOK HERE!
+ * });
+ * }
+ * },{})
+ *
+ * This makes the result of findAll a [http://api.jquery.com/category/deferred-object/ $.Deferred]
+ * that resolves to a list of model instances:
+ *
+ * var deferredRecipes = Recipe.findAll({});
+ *
+ * deferredRecipes.then(function(recipes){
+ * console.log('I have '+recipes.length+'recipes.');
+ * })
+ *
+ * ## Non-standard Services
+ *
+ * $.jQuery.models expects data to be an array of name-value pairs like:
+ *
+ * [{id: 1, name : "justin"},{id:2, name: "brian"}, ...]
+ *
+ * It can also take an object with additional data about the array like:
+ *
+ * {
+ * count: 15000 //how many total items there might be
+ * data: [{id: 1, name : "justin"},{id:2, name: "brian"}, ...]
+ * }
+ *
+ * In this case, models will return an array of instances found in
+ * data, but with additional properties as expandos on the array:
+ *
+ * var people = Person.models({
+ * count : 1500,
+ * data : [{id: 1, name: 'justin'}, ...]
+ * })
+ * people[0].name // -> justin
+ * people.count // -> 1500
+ *
+ * ### Overwriting Models
+ *
+ * If your service returns data like:
+ *
+ * {ballers: [{name: "justin", id: 5}]}
+ *
+ * You will want to overwrite models to pass the base models what it expects like:
+ *
+ * $.Model('Person',{
+ * models : function(data){
+ * this._super(data.ballers);
+ * }
+ * },{})
+ *
+ * @param {Array} instancesRawData an array of raw name - value pairs.
+ * @return {Array} a JavaScript array of instances or a [jQuery.Model.List list] of instances
+ * if the model list plugin has been included.
+ */
+ models: function( instancesRawData ) {
+ if (!instancesRawData ) {
+ return null;
+ }
+ var res = getList(this.List),
+ arr = isArray(instancesRawData),
+ raw = arr ? instancesRawData : instancesRawData.data,
+ length = raw.length,
+ i = 0;
+ //@steal-remove-start
+ if (! length ) {
+ steal.dev.warn("model.js models has no data. If you have one item, use model")
+ }
+ //@steal-remove-end
+ res._use_call = true; //so we don't call next function with all of these
+ for (; i < length; i++ ) {
+ res.push(this.model(raw[i]));
+ }
+ if (!arr ) { //push other stuff onto array
+ for ( var prop in instancesRawData ) {
+ if ( prop !== 'data' ) {
+ res[prop] = instancesRawData[prop];
+ }
+
+ }
+ }
+ return res;
+ },
+ /**
+ * The name of the id field. Defaults to 'id'. Change this if it is something different.
+ *
+ * For example, it's common in .NET to use Id. Your model might look like:
+ *
+ * @codestart
+ * $.Model("Friends",{
+ * id: "Id"
+ * },{});
+ * @codeend
+ */
+ id: 'id',
+ //if null, maybe treat as an array?
+ /**
+ * Adds an attribute to the list of attributes for this class.
+ * @hide
+ * @param {String} property
+ * @param {String} type
+ */
+ addAttr: function( property, type ) {
+ var stub;
+
+ if ( this.associations[property] ) {
+ return;
+ }
+
+ stub = this.attributes[property] || (this.attributes[property] = type);
+ return type;
+ },
+ // a collection of all models
+ _models: {},
+ /**
+ * If OpenAjax is available,
+ * publishes to OpenAjax.hub. Always adds the shortName.event.
+ *
+ * @codestart
+ * // publishes contact.completed
+ * Namespace.Contact.publish("completed",contact);
+ * @codeend
+ *
+ * @param {String} event The event name to publish
+ * @param {Object} data The data to publish
+ */
+ publish: function( event, data ) {
+ //@steal-remove-start
+ steal.dev.log("Model.js - publishing " + this._shortName + "." + event);
+ //@steal-remove-end
+ if ( window.OpenAjax ) {
+ OpenAjax.hub.publish(this._shortName + "." + event, data);
+ }
+
+ },
+ guessType : function(){
+ return "string"
+ },
+ /**
+ * @attribute convert
+ * @type Object
+ * An object of name-function pairs that are used to convert attributes.
+ * Check out [jQuery.Model.static.attributes] or
+ * [jquery.model.typeconversion type conversion]
+ * for examples.
+ */
+ convert: {
+ "date": function( str ) {
+ return typeof str === "string" ? (isNaN(Date.parse(str)) ? null : Date.parse(str)) : str;
+ },
+ "number": function( val ) {
+ return parseFloat(val);
+ },
+ "boolean": function( val ) {
+ return Boolean(val);
+ }
+ },
+ bind: bind,
+ unbind: unbind
+ },
+ /**
+ * @Prototype
+ */
+ {
+ /**
+ * Setup is called when a new model instance is created.
+ * It adds default attributes, then whatever attributes
+ * are passed to the class.
+ * Setup should never be called directly.
+ *
+ * @codestart
+ * $.Model("Recipe")
+ * var recipe = new Recipe({foo: "bar"});
+ * recipe.foo //-> "bar"
+ * recipe.attr("foo") //-> "bar"
+ * @codeend
+ *
+ * @param {Object} attributes a hash of attributes
+ */
+ setup: function( attributes ) {
+ // so we know not to fire events
+ this._init = true;
+ this.attrs(extend({},this.Class.defaults,attributes));
+ delete this._init;
+ },
+ /**
+ * Sets the attributes on this instance and calls save.
+ * The instance needs to have an id. It will use
+ * the instance class's [jQuery.Model.static.update update]
+ * method.
+ *
+ * @codestart
+ * recipe.update({name: "chicken"}, success, error);
+ * @codeend
+ *
+ * If OpenAjax.hub is available, the model will also
+ * publish a "<i>modelName</i>.updated" message with
+ * the updated instance.
+ *
+ * @param {Object} attrs the model's attributes
+ * @param {Function} success called if a successful update
+ * @param {Function} error called if there's an error
+ */
+ update: function( attrs, success, error ) {
+ this.attrs(attrs);
+ return this.save(success, error); //on success, we should
+ },
+ /**
+ * Runs the validations on this model. You can
+ * also pass it an array of attributes to run only those attributes.
+ * It returns nothing if there are no errors, or an object
+ * of errors by attribute.
+ *
+ * To use validations, it's suggested you use the
+ * model/validations plugin.
+ *
+ * @codestart
+ * $.Model("Task",{
+ * init : function(){
+ * this.validatePresenceOf("dueDate")
+ * }
+ * },{});
+ *
+ * var task = new Task(),
+ * errors = task.errors()
+ *
+ * errors.dueDate[0] //-> "can't be empty"
+ * @codeend
+ */
+ errors: function( attrs ) {
+ if ( attrs ) {
+ attrs = isArray(attrs) ? attrs : makeArray(arguments);
+ }
+ var errors = {},
+ self = this,
+ addErrors = function( attr, funcs ) {
+ each(funcs, function( i, func ) {
+ var res = func.call(self);
+ if ( res ) {
+ if (!errors.hasOwnProperty(attr) ) {
+ errors[attr] = [];
+ }
+
+ errors[attr].push(res);
+ }
+
+ });
+ };
+
+ each(attrs || this.Class.validations || {}, function( attr, funcs ) {
+ if ( typeof attr == 'number' ) {
+ attr = funcs;
+ funcs = self.Class.validations[attr];
+ }
+ addErrors(attr, funcs || []);
+ });
+
+ for ( var attr in errors ) {
+ if ( errors.hasOwnProperty(attr) ) {
+ return errors;
+ }
+ }
+ return null;
+ },
+ /**
+ * Gets or sets an attribute on the model using setters and
+ * getters if available.
+ *
+ * @codestart
+ * $.Model("Recipe")
+ * var recipe = new Recipe();
+ * recipe.attr("foo","bar")
+ * recipe.foo //-> "bar"
+ * recipe.attr("foo") //-> "bar"
+ * @codeend
+ *
+ * ## Setters
+ *
+ * If you add a set<i>AttributeName</i> method on your model,
+ * it will be used to set the value. The set method is called
+ * with the value and is expected to return the converted value.
+ *
+ * @codestart
+ * $.Model("Recipe",{
+ * setCreatedAt : function(raw){
+ * return Date.parse(raw)
+ * }
+ * })
+ * var recipe = new Recipe();
+ * recipe.attr("createdAt","Dec 25, 1995")
+ * recipe.createAt //-> Date
+ * @codeend
+ *
+ * ## Asynchronous Setters
+ *
+ * Sometimes, you want to perform an ajax request when
+ * you set a property. You can do this with setters too.
+ *
+ * To do this, your setter should return undefined and
+ * call success with the converted value. For example:
+ *
+ * @codestart
+ * $.Model("Recipe",{
+ * setTitle : function(title, success, error){
+ * $.post(
+ * "recipe/update/"+this.id+"/title",
+ * title,
+ * function(){
+ * success(title);
+ * },
+ * "json")
+ * }
+ * })
+ *
+ * recipe.attr("title","fish")
+ * @codeend
+ *
+ * ## Events
+ *
+ * When you use attr, it can also trigger events. This is
+ * covered in [jQuery.Model.prototype.bind].
+ *
+ * @param {String} attribute the attribute you want to set or get
+ * @param {String|Number|Boolean} [value] value the value you want to set.
+ * @param {Function} [success] an optional success callback.
+ * This gets called if the attribute was successful.
+ * @param {Function} [error] an optional success callback.
+ * The error function is called with validation errors.
+ */
+ attr: function( attribute, value, success, error ) {
+ var cap = classize(attribute),
+ get = "get" + cap;
+ if ( value !== undefined ) {
+ this._setProperty(attribute, value, success, error, cap);
+ return this;
+ }
+ return this[get] ? this[get]() : this[attribute];
+ },
+ /**
+ * Binds to events on this model instance. Typically
+ * you'll bind to an attribute name. Handler will be called
+ * every time the attribute value changes. For example:
+ *
+ * @codestart
+ * $.Model("School")
+ * var school = new School();
+ * school.bind("address", function(ev, address){
+ * alert('address changed to '+address);
+ * })
+ * school.attr("address","1124 Park St");
+ * @codeend
+ *
+ * You can also bind to attribute errors.
+ *
+ * @codestart
+ * $.Model("School",{
+ * setName : function(name, success, error){
+ * if(!name){
+ * error("no name");
+ * }
+ * return error;
+ * }
+ * })
+ * var school = new School();
+ * school.bind("error.name", function(ev, mess){
+ * mess // -> "no name";
+ * })
+ * school.attr("name","");
+ * @codeend
+ *
+ * You can also bind to created, updated, and destroyed events.
+ *
+ * @param {String} eventType the name of the event.
+ * @param {Function} handler a function to call back when an event happens on this model.
+ * @return {model} the model instance for chaining
+ */
+ bind: bind,
+ /**
+ * Unbinds an event handler from this instance.
+ * Read [jQuery.Model.prototype.bind] for
+ * more information.
+ * @param {String} eventType
+ * @param {Function} handler
+ */
+ unbind: unbind,
+ /**
+ * Checks if there is a set_<i>property</i> value. If it returns true, lets it handle; otherwise
+ * saves it.
+ * @hide
+ * @param {Object} property
+ * @param {Object} value
+ */
+ _setProperty: function( property, value, success, error, capitalized ) {
+ // the potential setter name
+ var setName = "set" + capitalized,
+ //the old value
+ old = this[property],
+ self = this,
+ errorCallback = function( errors ) {
+ var stub;
+ stub = error && error.call(self, errors);
+ $(self).triggerHandler("error." + property, errors);
+ };
+
+ // provides getter / setters
+ //
+ if ( this[setName] &&
+ (value = this[setName](value, this.callback('_updateProperty', property, value, old, success, errorCallback), errorCallback)) === undefined ) {
+ return;
+ }
+ this._updateProperty(property, value, old, success, errorCallback);
+ },
+ /**
+ * Triggers events when a property has been updated
+ * @hide
+ * @param {Object} property
+ * @param {Object} value
+ * @param {Object} old
+ * @param {Object} success
+ */
+ _updateProperty: function( property, value, old, success, errorCallback ) {
+ var Class = this.Class,
+ val, type = Class.attributes[property] || Class.addAttr(property, Class.guessType(value)),
+ //the converter
+ converter = Class.convert[type],
+ errors = null,
+ stub;
+
+ val = this[property] = (value === null ? //if the value is null or undefined
+ null : // it should be null
+ (converter ? converter.call(Class, value) : //convert it to something useful
+ value)); //just return it
+ //validate (only if not initializing, this is for performance)
+ if (!this._init ) {
+ errors = this.errors(property);
+ }
+
+ if ( errors ) {
+ //get an array of errors
+ errorCallback(errors);
+ } else {
+ if ( old !== val && !this._init ) {
+ $(this).triggerHandler(property, [val]);
+ $(this).triggerHandler("updated.attr", [property,val, old]); // this is for 3.1
+ }
+ stub = success && success(this);
+
+ }
+
+ //if this class has a global list, add / remove from the list.
+ if ( property === Class.id && val !== null && Class.list ) {
+ // if we didn't have an old id, add ourselves
+ if (!old ) {
+ Class.list.push(this);
+ } else if ( old != val ) {
+ // if our id has changed ... well this should be ok
+ Class.list.remove(old);
+ Class.list.push(this);
+ }
+ }
+
+ },
+ /**
+ * Gets or sets a list of attributes.
+ * Each attribute is set with [jQuery.Model.prototype.attr attr].
+ *
+ * @codestart
+ * recipe.attrs({
+ * name: "ice water",
+ * instructions : "put water in a glass"
+ * })
+ * @codeend
+ *
+ * This can be used nicely with [jquery.model.events].
+ *
+ * @param {Object} [attributes] if present, the list of attributes to send
+ * @return {Object} the current attributes of the model
+ */
+ attrs: function( attributes ) {
+ var key;
+ if (!attributes ) {
+ attributes = {};
+ for ( key in this.Class.attributes ) {
+ if ( this.Class.attributes.hasOwnProperty(key) ) {
+ attributes[key] = this.attr(key);
+ }
+ }
+ } else {
+ var idName = this.Class.id;
+ //always set the id last
+ for ( key in attributes ) {
+ if ( key != idName ) {
+ this.attr(key, attributes[key]);
+ }
+ }
+ if ( idName in attributes ) {
+ this.attr(idName, attributes[idName]);
+ }
+
+ }
+ return attributes;
+ },
+ /**
+ * Returns if the instance is a new object. This is essentially if the
+ * id is null or undefined.
+ *
+ * new Recipe({id: 1}).isNew() //-> false
+ * @return {Boolean} false if an id is set, true if otherwise.
+ */
+ isNew: function() {
+ var id = getId(this);
+ return (id === undefined || id === null); //if null or undefined
+ },
+ /**
+ * Saves the instance if there are no errors.
+ * If the instance is new, [jQuery.Model.static.create] is
+ * called; otherwise, [jQuery.Model.static.update] is
+ * called.
+ *
+ * @codestart
+ * recipe.save(success, error);
+ * @codeend
+ *
+ * If OpenAjax.hub is available, after a successful create or update,
+ * "<i>modelName</i>.created" or "<i>modelName</i>.updated" is published.
+ *
+ * @param {Function} [success] called if a successful save.
+ * @param {Function} [error] called if the save was not successful.
+ */
+ save: function( success, error ) {
+ return makeRequest(this, this.isNew() ? 'create' : 'update' , success, error);
+ },
+
+ /**
+ * Destroys the instance by calling
+ * [jQuery.Model.static.destroy] with the id of the instance.
+ *
+ * @codestart
+ * recipe.destroy(success, error);
+ * @codeend
+ *
+ * If OpenAjax.hub is available, after a successful
+ * destroy "<i>modelName</i>.destroyed" is published
+ * with the model instance.
+ *
+ * @param {Function} [success] called if a successful destroy
+ * @param {Function} [error] called if an unsuccessful destroy
+ */
+ destroy: function( success, error ) {
+ return makeRequest(this, 'destroy' , success, error , 'destroyed');
+ },
+
+
+ /**
+ * Returns a unique identifier for the model instance. For example:
+ * @codestart
+ * new Todo({id: 5}).identity() //-> 'todo_5'
+ * @codeend
+ * Typically this is used in an element's shortName property so you can find all elements
+ * for a model with [jQuery.Model.prototype.elements elements].
+ * @return {String}
+ */
+ identity: function() {
+ var id = getId(this);
+ return this.Class._fullName + '_' + (this.Class.escapeIdentity ? encodeURIComponent(id) : id);
+ },
+ /**
+ * Returns elements that represent this model instance. For this to work, your element's should
+ * us the [jQuery.Model.prototype.identity identity] function in their class name. Example:
+ *
+ * <div class='todo <%= todo.identity() %>'> ... </div>
+ *
+ * This also works if you hooked up the model:
+ *
+ * <div <%= todo %>> ... </div>
+ *
+ * Typically, you'll use this as a response of an OpenAjax message:
+ *
+ * "todo.destroyed subscribe": function(called, todo){
+ * todo.elements(this.element).remove();
+ * }
+ *
+ * ## API
+ *
+ * @param {String|jQuery|element} context If provided, only elements inside this element
+ * that represent this model will be returned.
+ *
+ * @return {jQuery} Returns a jQuery wrapped nodelist of elements that have this model instances
+ * identity in their class name.
+ */
+ elements: function( context ) {
+ return $("." + this.identity(), context);
+ },
+ /**
+ * Publishes to OpenAjax.hub
+ *
+ * $.Model('Task', {
+ * complete : function(cb){
+ * var self = this;
+ * $.post('/task/'+this.id,
+ * {complete : true},
+ * function(){
+ * self.attr('completed', true);
+ * self.publish('completed');
+ * })
+ * }
+ * })
+ *
+ *
+ * @param {String} event The event type. The model's short name will be automatically prefixed.
+ * @param {Object} [data] if missing, uses the instance in {data: this}
+ */
+ publish: function( event, data ) {
+ this.Class.publish(event, data || this);
+ },
+ hookup: function( el ) {
+ var shortName = this.Class._shortName,
+ models = $.data(el, "models") || $.data(el, "models", {});
+ $(el).addClass(shortName + " " + this.identity());
+ models[shortName] = this;
+ }
+ });
+ // map wrapMany
+ $.Model.wrapMany = $.Model.models;
+ $.Model.wrap = $.Model.model;
+
+
+ each([
+ /**
+ * @function created
+ * @hide
+ * Called by save after a new instance is created. Publishes 'created'.
+ * @param {Object} attrs
+ */
+ "created",
+ /**
+ * @function updated
+ * @hide
+ * Called by save after an instance is updated. Publishes 'updated'.
+ * @param {Object} attrs
+ */
+ "updated",
+ /**
+ * @function destroyed
+ * @hide
+ * Called after an instance is destroyed.
+ * - Publishes "shortName.destroyed".
+ * - Triggers a "destroyed" event on this model.
+ * - Removes the model from the global list if its used.
+ *
+ */
+ "destroyed"], function( i, funcName ) {
+ $.Model.prototype[funcName] = function( attrs ) {
+ var stub;
+
+ if ( funcName === 'destroyed' && this.Class.list ) {
+ this.Class.list.remove(getId(this));
+ }
+ stub = attrs && typeof attrs == 'object' && this.attrs(attrs.attrs ? attrs.attrs() : attrs);
+ $(this).triggerHandler(funcName);
+ this.publish(funcName, this);
+ $([this.Class]).triggerHandler(funcName, this);
+ return [this].concat(makeArray(arguments)); // return like this for this.callback chains
+ };
+ });
+
+ /**
+ * @add jQuery.fn
+ */
+ // break
+ /**
+ * @function models
+ * Returns a list of models. If the models are of the same
+ * type, and have a [jQuery.Model.List], it will return
+ * the models wrapped with the list.
+ *
+ * @codestart
+ * $(".recipes").models() //-> [recipe, ...]
+ * @codeend
+ *
+ * @param {jQuery.Class} [type] if present only returns models of the provided type.
+ * @return {Array|jQuery.Model.List} returns an array of model instances that are represented by the contained elements.
+ */
+ $.fn.models = function( type ) {
+ //get it from the data
+ var collection = [],
+ kind, ret, retType;
+ this.each(function() {
+ each($.data(this, "models") || {}, function( name, instance ) {
+ //either null or the list type shared by all classes
+ kind = kind === undefined ?
+ instance.Class.List || null :
+ (instance.Class.List === kind ? kind : null);
+ collection.push(instance);
+ });
+ });
+
+ ret = getList(kind);
+
+ ret.push.apply(ret, unique(collection));
+ return ret;
+ };
+ /**
+ * @function model
+ *
+ * Returns the first model instance found from [jQuery.fn.models] or
+ * sets the model instance on an element.
+ *
+ * //gets an instance
+ * ".edit click" : function(el) {
+ * el.closest('.todo').model().destroy()
+ * },
+ * // sets an instance
+ * list : function(items){
+ * var el = this.element;
+ * $.each(item, function(item){
+ * $('<div/>').model(item)
+ * .appendTo(el)
+ * })
+ * }
+ *
+ * @param {Object} [type] The type of model to return. If a model instance is provided
+ * it will add the model to the element.
+ */
+ $.fn.model = function( type ) {
+ if ( type && type instanceof $.Model ) {
+ type.hookup(this[0]);
+ return this;
+ } else {
+ return this.models.apply(this, arguments)[0];
+ }
+
+ };
+ /**
+ * @page jquery.model.services Service APIs
+ * @parent jQuery.Model
+ *
+ * Models provide an abstract API for connecting to your Services.
+ * By implementing static:
+ *
+ * - [jQuery.Model.static.findAll]
+ * - [jQuery.Model.static.findOne]
+ * - [jQuery.Model.static.create]
+ * - [jQuery.Model.static.update]
+ * - [jQuery.Model.static.destroy]
+ *
+ * You can find more details on how to implement each method.
+ * Typically, you can just use templated service urls. But if you need to
+ * implement these methods yourself, the following
+ * is a useful quick reference:
+ *
+ * ### create(attrs, success([attrs]), error()) -> deferred
+ *
+ * - <code>attrs</code> - an Object of attribute / value pairs
+ * - <code>success([attrs])</code> - Create calls success when the request has completed
+ * successfully. Success can be called back with an object that represents
+ * additional properties that will be set on the instance. For example, the server might
+ * send back an updatedAt date.
+ * - <code>error</code> - Create should callback error if an error happens during the request
+ * - <code>deferred</code> - A deferred that gets resolved to any additional attrs
+ * that might need to be set on the model instance.
+ *
+ *
+ * ### findAll( params, success(items), error) -> deferred
+ *
+ *
+ * - <code>params</code> - an Object that filters the items returned
+ * - <code>success(items)</code> - success should be called with an Array of Model instances.
+ * - <code>error</code> - called if an error happens during the request
+ * - <code>deferred</code> - A deferred that gets resolved to the list of items
+ *
+ * ### findOne(params, success(items), error) -> deferred
+ *
+ * - <code>params</code> - an Object that filters the item returned
+ * - <code>success(item)</code> - success should be called with a model instance.
+ * - <code>error</code> - called if an error happens during the request
+ * - <code>deferred</code> - A deferred that gets resolved to a model instance
+ *
+ * ### update(id, attrs, success([attrs]), error()) -> deferred
+ *
+ * - <code>id</code> - the id of the instance you are updating
+ * - <code>attrs</code> - an Object of attribute / value pairs
+ * - <code>success([attrs])</code> - Call success when the request has completed
+ * successfully. Success can be called back with an object that represents
+ * additional properties that will be set on the instance. For example, the server might
+ * send back an updatedAt date.
+ * - <code>error</code> - Callback error if an error happens during the request
+ * - <code>deferred</code> - A deferred that gets resolved to any additional attrs
+ * that might need to be set on the model instance.
+ *
+ * ### destroy(id, success([attrs]), error()) -> deferred
+ *
+ * - <code>id</code> - the id of the instance you are destroying
+ * - <code>success([attrs])</code> - Calls success when the request has completed
+ * successfully. Success can be called back with an object that represents
+ * additional properties that will be set on the instance.
+ * - <code>error</code> - Create should callback error if an error happens during the request
+ * - <code>deferred</code> - A deferred that gets resolved to any additional attrs
+ * that might need to be set on the model instance.
+ */
+});
Added: openejb/trunk/openejb3/examples/webapps/rest-example/src/main/webapp/jquery/view/ejs/ejs.js
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/examples/webapps/rest-example/src/main/webapp/jquery/view/ejs/ejs.js?rev=1149378&view=auto
==============================================================================
--- openejb/trunk/openejb3/examples/webapps/rest-example/src/main/webapp/jquery/view/ejs/ejs.js (added)
+++ openejb/trunk/openejb3/examples/webapps/rest-example/src/main/webapp/jquery/view/ejs/ejs.js Thu Jul 21 21:42:07 2011
@@ -0,0 +1,523 @@
+/*jslint evil: true */
+
+
+
+steal.plugins('jquery/view', 'jquery/lang/rsplit').then(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, '"')
+ .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 <a href="http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/">ERB</a>
+ * 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 <i>'views\tasks\list.ejs'</i>.
+ *
+ *
+ * ## 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><% CODE %></code> - Runs JS Code.
+ * For example:
+ *
+ * <% alert('hello world') %>
+ *
+ * - <code><%= CODE %></code> - Runs JS Code and writes the result into the result of the template.
+ * For example:
+ *
+ * <h1><%= 'hello world' %></h1>
+ *
+ * - <code><%~ CODE %></code> - Runs JS Code and writes the _escaped_ result into the result of the template.
+ * For example:
+ *
+ * <%~ 'hello world' %>
+ *
+ * - <code><%%= CODE %></code> - Writes <%= CODE %> to the result of the template. This is very useful for generators.
+ *
+ * <%%= 'hello world' %>
+ *
+ * - <code><%# CODE %></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
+ *
+ * <h2>View Helpers</h2>
+ * 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
+ * <table class="options">
+ * <tbody><tr><th>Option</th><th>Default</th><th>Description</th></tr>
+ * <tr>
+ * <td>url</td>
+ * <td> </td>
+ * <td>loads the template from a file. This path should be relative to <i>[jQuery.root]</i>.
+ * </td>
+ * </tr>
+ * <tr>
+ * <td>text</td>
+ * <td> </td>
+ * <td>uses the provided text as the template. Example:<br/><code>new View({text: '<%=user%>'})</code>
+ * </td>
+ * </tr>
+ * <tr>
+ * <td>element</td>
+ * <td> </td>
+ * <td>loads a template from the innerHTML or value of the element.
+ * </td>
+ * </tr>
+ * <tr>
+ * <td>type</td>
+ * <td>'<'</td>
+ * <td>type of magic tags. Options are '<' or '['
+ * </td>
+ * </tr>
+ * <tr>
+ * <td>name</td>
+ * <td>the element ID or url </td>
+ * <td>an optional name that is used for caching.
+ * </td>
+ * </tr>
+ * <tr>
+ * <td>cache</td>
+ * <td>true in production mode, false in other modes</td>
+ * <td>true to cache template.
+ * </td>
+ * </tr>
+ *
+ * </tbody></table>
+ */
+ $.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
+ * <table class="options">
+ * <tbody><tr><th>Option</th><th>Default</th><th>Description</th></tr>
+ * <tr>
+ * <td>type</td>
+ * <td>'<'</td>
+ * <td>type of magic tags. Options are '<' or '['
+ * </td>
+ * </tr>
+ * <tr>
+ * <td>cache</td>
+ * <td>true in production mode, false in other modes</td>
+ * <td>true to cache template.
+ * </td>
+ * </tr>
+ * </tbody></table>
+ *
+ */
+ 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 <code>$.View()</code>.
+ */
+ 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);
+ };
+ }
+ });
+});
\ No newline at end of file