source: trunk/prototype/app/plugins/view/jquerymx-1.0.custom.js @ 5136

Revision 5136, 37.6 KB checked in by wmerlotto, 12 years ago (diff)

Ticket #2305 - Enviando alteracoes, desenvolvidas internamente na Prognus, do modulo prototype.

Line 
1
2//jquery.view.js
3
4(function( $ ) {
5
6        // converts to an ok dom id
7        var toId = function( src ) {
8                return src.replace(/^\/\//, "").replace(/[\/\.]/g, "_");
9        },
10                // used for hookup ids
11                id = 1;
12
13        /**
14         * @class jQuery.View
15         * @tag core
16         * @plugin jquery/view
17         * @test jquery/view/qunit.html
18         * @download dist/jquery.view.js
19         *
20         * View provides a uniform interface for using templates with
21         * jQuery. When template engines [jQuery.View.register register]
22         * themselves, you are able to:
23         *
24         *  - Use views with jQuery extensions [jQuery.fn.after after], [jQuery.fn.append append],
25         *   [jQuery.fn.before before], [jQuery.fn.html html], [jQuery.fn.prepend prepend],
26         *   [jQuery.fn.replaceWith replaceWith], [jQuery.fn.text text].
27         *  - Template loading from html elements and external files.
28         *  - Synchronous and asynchronous template loading.
29         *  - Deferred Rendering.
30         *  - Template caching.
31         *  - Bundling of processed templates in production builds.
32         *  - Hookup jquery plugins directly in the template.
33         * 
34         * ## Use
35         *
36         *
37         * When using views, you're almost always wanting to insert the results
38         * of a rendered template into the page. jQuery.View overwrites the
39         * jQuery modifiers so using a view is as easy as:
40         *
41         *     $("#foo").html('mytemplate.ejs',{message: 'hello world'})
42         *
43         * This code:
44         *
45         *  - Loads the template a 'mytemplate.ejs'. It might look like:
46         *    <pre><code>&lt;h2>&lt;%= message %>&lt;/h2></pre></code>
47         * 
48         *  - Renders it with {message: 'hello world'}, resulting in:
49         *    <pre><code>&lt;div id='foo'>"&lt;h2>hello world&lt;/h2>&lt;/div></pre></code>
50         * 
51         *  - Inserts the result into the foo element. Foo might look like:
52         *    <pre><code>&lt;div id='foo'>&lt;h2>hello world&lt;/h2>&lt;/div></pre></code>
53         *
54         * ## jQuery Modifiers
55         *
56         * You can use a template with the following jQuery modifiers:
57         *
58         * <table>
59         * <tr><td>[jQuery.fn.after after]</td><td> <code>$('#bar').after('temp.jaml',{});</code></td></tr>
60         * <tr><td>[jQuery.fn.after append] </td><td>  <code>$('#bar').append('temp.jaml',{});</code></td></tr>
61         * <tr><td>[jQuery.fn.after before] </td><td> <code>$('#bar').before('temp.jaml',{});</code></td></tr>
62         * <tr><td>[jQuery.fn.after html] </td><td> <code>$('#bar').html('temp.jaml',{});</code></td></tr>
63         * <tr><td>[jQuery.fn.after prepend] </td><td> <code>$('#bar').prepend('temp.jaml',{});</code></td></tr>
64         * <tr><td>[jQuery.fn.after replaceWith] </td><td> <code>$('#bar').replaceWidth('temp.jaml',{});</code></td></tr>
65         * <tr><td>[jQuery.fn.after text] </td><td> <code>$('#bar').text('temp.jaml',{});</code></td></tr>
66         * </table>
67         *
68         * You always have to pass a string and an object (or function) for the jQuery modifier
69         * to user a template.
70         *
71         * ## Template Locations
72         *
73         * View can load from script tags or from files.
74         *
75         * ## From Script Tags
76         *
77         * To load from a script tag, create a script tag with your template and an id like:
78         *
79         * <pre><code>&lt;script type='text/ejs' id='recipes'>
80         * &lt;% for(var i=0; i &lt; recipes.length; i++){ %>
81         *   &lt;li>&lt;%=recipes[i].name %>&lt;/li>
82         * &lt;%} %>
83         * &lt;/script></code></pre>
84         *
85         * Render with this template like:
86         *
87         * @codestart
88         * $("#foo").html('recipes',recipeData)
89         * @codeend
90         *
91         * Notice we passed the id of the element we want to render.
92         *
93         * ## From File
94         *
95         * You can pass the path of a template file location like:
96         *
97         *     $("#foo").html('templates/recipes.ejs',recipeData)
98         *
99         * However, you typically want to make the template work from whatever page they
100         * are called from.  To do this, use // to look up templates from JMVC root:
101         *
102         *     $("#foo").html('//app/views/recipes.ejs',recipeData)
103         *     
104         * Finally, the [jQuery.Controller.prototype.view controller/view] plugin can make looking
105         * up a thread (and adding helpers) even easier:
106         *
107         *     $("#foo").html( this.view('recipes', recipeData) )
108         *
109         * ## Packaging Templates
110         *
111         * If you're making heavy use of templates, you want to organize
112         * them in files so they can be reused between pages and applications.
113         *
114         * But, this organization would come at a high price
115         * if the browser has to
116         * retrieve each template individually. The additional
117         * HTTP requests would slow down your app.
118         *
119         * Fortunately, [steal.static.views steal.views] can build templates
120         * into your production files. You just have to point to the view file like:
121         *
122         *     steal.views('path/to/the/view.ejs');
123     *
124         * ## Asynchronous
125         *
126         * By default, retrieving requests is done synchronously. This is
127         * fine because StealJS packages view templates with your JS download.
128         *
129         * However, some people might not be using StealJS or want to delay loading
130         * templates until necessary. If you have the need, you can
131         * provide a callback paramter like:
132         *
133         *     $("#foo").html('recipes',recipeData, function(result){
134         *       this.fadeIn()
135         *     });
136         *
137         * The callback function will be called with the result of the
138         * rendered template and 'this' will be set to the original jQuery object.
139         *
140         * ## Deferreds (3.0.6)
141         *
142         * If you pass deferreds to $.View or any of the jQuery
143         * modifiers, the view will wait until all deferreds resolve before
144         * rendering the view.  This makes it a one-liner to make a request and
145         * use the result to render a template.
146         *
147         * The following makes a request for todos in parallel with the
148         * todos.ejs template.  Once todos and template have been loaded, it with
149         * render the view with the todos.
150         *
151         *     $('#todos').html("todos.ejs",Todo.findAll());
152         *
153         * ## Just Render Templates
154         *
155         * Sometimes, you just want to get the result of a rendered
156         * template without inserting it, you can do this with $.View:
157         *
158         *     var out = $.View('path/to/template.jaml',{});
159         *     
160     * ## Preloading Templates
161         *
162         * You can preload templates asynchronously like:
163         *
164         *     $.get('path/to/template.jaml',{},function(){},'view');
165         *
166         * ## Supported Template Engines
167         *
168         * JavaScriptMVC comes with the following template languages:
169         *
170         *   - EmbeddedJS
171         *     <pre><code>&lt;h2>&lt;%= message %>&lt;/h2></code></pre>
172         *     
173         *   - JAML
174         *     <pre><code>h2(data.message);</code></pre>
175         *     
176         *   - Micro
177         *     <pre><code>&lt;h2>{%= message %}&lt;/h2></code></pre>
178         *     
179         *   - jQuery.Tmpl
180         *     <pre><code>&lt;h2>${message}&lt;/h2></code></pre>
181
182         *
183         * The popular <a href='http://awardwinningfjords.com/2010/08/09/mustache-for-javascriptmvc-3.html'>Mustache</a>
184         * template engine is supported in a 2nd party plugin.
185         *
186         * ## Using other Template Engines
187         *
188         * It's easy to integrate your favorite template into $.View and Steal.  Read
189         * how in [jQuery.View.register].
190         *
191         * @constructor
192         *
193         * Looks up a template, processes it, caches it, then renders the template
194         * with data and optional helpers.
195         *
196         * With [stealjs StealJS], views are typically bundled in the production build.
197         * This makes it ok to use views synchronously like:
198         *
199         * @codestart
200         * $.View("//myplugin/views/init.ejs",{message: "Hello World"})
201         * @codeend
202         *
203         * If you aren't using StealJS, it's best to use views asynchronously like:
204         *
205         * @codestart
206         * $.View("//myplugin/views/init.ejs",
207         *        {message: "Hello World"}, function(result){
208         *   // do something with result
209         * })
210         * @codeend
211         *
212         * @param {String} view The url or id of an element to use as the template's source.
213         * @param {Object} data The data to be passed to the view.
214         * @param {Object} [helpers] Optional helper functions the view might use. Not all
215         * templates support helpers.
216         * @param {Object} [callback] Optional callback function.  If present, the template is
217         * retrieved asynchronously.  This is a good idea if you aren't compressing the templates
218         * into your view.
219         * @return {String} The rendered result of the view or if deferreds are passed, a deferred that will contain
220         * the rendered result of the view.
221         */
222
223        var $view, render, checkText, get, getRenderer
224                isDeferred = function(obj){
225                        return obj && $.isFunction(obj.always) // check if obj is a $.Deferred
226                },
227                // gets an array of deferreds from an object
228                // this only goes one level deep
229                getDeferreds =  function(data){
230                        var deferreds = [];
231               
232                        // pull out deferreds
233                        if(isDeferred(data)){
234                                return [data]
235                        }else{
236                                for(var prop in data) {
237                                        if(isDeferred(data[prop])) {
238                                                deferreds.push(data[prop]);
239                                        }
240                                }
241                        }
242                        return deferreds;
243                },
244                // gets the useful part of deferred
245                // this is for Models and $.ajax that give arrays
246                usefulPart = function(resolved){
247                        return $.isArray(resolved) &&
248                                        resolved.length ===3 &&
249                                        resolved[1] === 'success' ?
250                                                resolved[0] : resolved
251                };
252
253        $view = $.View = function( view, data, helpers, callback ) {
254                if ( typeof helpers === 'function' ) {
255                        callback = helpers;
256                        helpers = undefined;
257                }
258               
259                // see if we got passed any deferreds
260                var deferreds = getDeferreds(data);
261               
262               
263                if(deferreds.length) { // does data contain any deferreds?
264                       
265                        // the deferred that resolves into the rendered content ...
266                        var deferred = $.Deferred();
267                       
268                        // add the view request to the list of deferreds
269                        deferreds.push(get(view, true))
270                       
271                        // wait for the view and all deferreds to finish
272                        $.when.apply($, deferreds).then(function(resolved) {
273                                var objs = $.makeArray(arguments),
274                                        renderer = objs.pop()[0],
275                                        result; //get the view render function
276                               
277                                // make data look like the resolved deferreds
278                                if (isDeferred(data)) {
279                                        data = usefulPart(resolved);
280                                }
281                                else {
282                                        for (var prop in data) {
283                                                if (isDeferred(data[prop])) {
284                                                        data[prop] = usefulPart(objs.shift());
285                                                }
286                                        }
287                                }
288                                result = renderer(data, helpers);
289                               
290                                //resolve with the rendered view
291                                deferred.resolve( result ); // this does not work as is...
292                                callback && callback(result);
293                        });
294                        // return the deferred ....
295                        return deferred.promise();
296                }
297                else {
298
299                        var response,
300                                async = typeof callback === "function",
301                                deferred = get(view, async);
302                       
303                        if(async){
304                                response = deferred;
305                                deferred.done(function(renderer){
306                                        callback(renderer(data, helpers))
307                                })
308                        } else {
309                                deferred.done(function(renderer){
310                                        response = renderer(data, helpers);
311                                });
312                        }
313                       
314                        return response;
315                }
316        };
317        // makes sure there's a template
318        checkText = function( text, url ) {
319                if (!text.match(/[^\s]/) ) {
320                       
321                        throw "$.View ERROR: There is no template or an empty template at " + url;
322                }
323        };
324        get = function(url , async){
325                return $.ajax({
326                                url: url,
327                                dataType : "view",
328                                async : async
329                });
330        };
331       
332        // you can request a view renderer (a function you pass data to and get html)
333        $.ajaxTransport("view", function(options, orig){
334                var view = orig.url,
335                        suffix = view.match(/\.[\w\d]+$/),
336                        type, el, id, renderer, url = view,
337                        jqXHR,
338                        response = function(text){
339                                var func = type.renderer(id, text);
340                                if ( $view.cache ) {
341                                        $view.cached[id] = func;
342                                }
343                                return {
344                                        view: func
345                                };
346                        };
347                       
348        // if we have an inline template, derive the suffix from the 'text/???' part
349        // this only supports '<script></script>' tags
350        if ( el = document.getElementById(view)) {
351          suffix = el.type.match(/\/[\d\w]+$/)[0].replace(/^\//, '.');
352        }
353               
354                //if there is no suffix, add one
355                if (!suffix ) {
356                        suffix = $view.ext;
357                        url = url + $view.ext;
358                }
359
360                //convert to a unique and valid id
361                id = toId(url);
362
363                //if a absolute path, use steal to get it
364                if ( url.match(/^\/\//) ) {
365                        if (typeof steal === "undefined") {
366                                url = "/"+url.substr(2);
367                        }
368                        else {
369                                url = steal.root.join(url.substr(2));
370                        }
371                }
372
373                //get the template engine
374                type = $view.types[suffix];
375
376                return {
377                        send : function(headers, callback){
378                                if($view.cached[id]){
379                                        return callback( 200, "success", {view: $view.cached[id]} );
380                                } else if( el  ) {
381                                        callback( 200, "success", response(el.innerHTML) );
382                                } else {
383                                        jqXHR = $.ajax({
384                                                async : orig.async,
385                                                url: url,
386                                                dataType: "text",
387                                                error: function() {
388                                                        checkText("", url);
389                                                        callback(404);
390                                                },
391                                                success: function( text ) {
392                                                        checkText(text, url);
393                                                        callback(200, "success", response(text) )
394                                                }
395                                        });
396                                }
397                        },
398                        abort : function(){
399                                jqXHR && jqXHR.abort();
400                        }
401                }
402        })
403        $.extend($view, {
404                /**
405                 * @attribute hookups
406                 * @hide
407                 * A list of pending 'hookups'
408                 */
409                hookups: {},
410                /**
411                 * @function hookup
412                 * Registers a hookup function that can be called back after the html is
413                 * put on the page.  Typically this is handled by the template engine.  Currently
414                 * only EJS supports this functionality.
415                 *
416                 *     var id = $.View.hookup(function(el){
417                 *            //do something with el
418                 *         }),
419                 *         html = "<div data-view-id='"+id+"'>"
420                 *     $('.foo').html(html);
421                 *
422                 *
423                 * @param {Function} cb a callback function to be called with the element
424                 * @param {Number} the hookup number
425                 */
426                hookup: function( cb ) {
427                        var myid = ++id;
428                        $view.hookups[myid] = cb;
429                        return myid;
430                },
431                /**
432                 * @attribute cached
433                 * @hide
434                 * Cached are put in this object
435                 */
436                cached: {},
437                /**
438                 * @attribute cache
439                 * Should the views be cached or reloaded from the server. Defaults to true.
440                 */
441                cache: true,
442                /**
443                 * @function register
444                 * Registers a template engine to be used with
445                 * view helpers and compression. 
446                 *
447                 * ## Example
448                 *
449                 * @codestart
450                 * $.View.register({
451                 *      suffix : "tmpl",
452                 *      renderer: function( id, text ) {
453                 *              return function(data){
454                 *                      return jQuery.render( text, data );
455                 *              }
456                 *      },
457                 *      script: function( id, text ) {
458                 *              var tmpl = $.tmpl(text).toString();
459                 *              return "function(data){return ("+
460                 *                      tmpl+
461                 *                      ").call(jQuery, jQuery, data); }";
462                 *      }
463                 * })
464                 * @codeend
465                 * Here's what each property does:
466                 *
467                 *    * suffix - files that use this suffix will be processed by this template engine
468                 *    * renderer - returns a function that will render the template provided by text
469                 *    * script - returns a string form of the processed template function.
470                 *
471                 * @param {Object} info a object of method and properties
472                 *
473                 * that enable template integration:
474                 * <ul>
475                 *   <li>suffix - the view extension.  EX: 'ejs'</li>
476                 *   <li>script(id, src) - a function that returns a string that when evaluated returns a function that can be
477                 *    used as the render (i.e. have func.call(data, data, helpers) called on it).</li>
478                 *   <li>renderer(id, text) - a function that takes the id of the template and the text of the template and
479                 *    returns a render function.</li>
480                 * </ul>
481                 */
482                register: function( info ) {
483                        this.types["." + info.suffix] = info;
484                },
485                types: {},
486                /**
487                 * @attribute ext
488                 * The default suffix to use if none is provided in the view's url. 
489                 * This is set to .ejs by default.
490                 */
491                ext: ".ejs",
492                /**
493                 * Returns the text that
494                 * @hide
495                 * @param {Object} type
496                 * @param {Object} id
497                 * @param {Object} src
498                 */
499                registerScript: function( type, id, src ) {
500                        return "$.View.preload('" + id + "'," + $view.types["." + type].script(id, src) + ");";
501                },
502                /**
503                 * @hide
504                 * Called by a production script to pre-load a renderer function
505                 * into the view cache.
506                 * @param {String} id
507                 * @param {Function} renderer
508                 */
509                preload: function( id, renderer ) {
510                        $view.cached[id] = function( data, helpers ) {
511                                return renderer.call(data, data, helpers);
512                        };
513                }
514
515        });
516
517
518        //---- ADD jQUERY HELPERS -----
519        //converts jquery functions to use views       
520        var convert, modify, isTemplate, getCallback, hookupView, funcs;
521
522        convert = function( func_name ) {
523                var old = $.fn[func_name];
524
525                $.fn[func_name] = function() {
526                        var args = $.makeArray(arguments),
527                                callbackNum,
528                                callback,
529                                self = this,
530                                result;
531
532                        //check if a template
533                        if ( isTemplate(args) ) {
534
535                                // if we should operate async
536                                if ((callbackNum = getCallback(args))) {
537                                        callback = args[callbackNum];
538                                        args[callbackNum] = function( result ) {
539                                                modify.call(self, [result], old);
540                                                callback.call(self, result);
541                                        };
542                                        $view.apply($view, args);
543                                        return this;
544                                }
545                                result = $view.apply($view, args);
546                                if(!isDeferred( result ) ){
547                                        args = [result];
548                                }else{
549                                        result.done(function(res){
550                                                modify.call(self, [res], old);
551                                        })
552                                        return this;
553                                }
554                                //otherwise do the template now
555                               
556                        }
557
558                        return modify.call(this, args, old);
559                };
560        };
561        // modifies the html of the element
562        modify = function( args, old ) {
563                var res, stub, hooks;
564
565                //check if there are new hookups
566                for ( var hasHookups in $view.hookups ) {
567                        break;
568                }
569
570                //if there are hookups, get jQuery object
571                if ( hasHookups ) {
572                        hooks = $view.hookups;
573                        $view.hookups = {};
574                        args[0] = $(args[0]);
575                }
576                res = old.apply(this, args);
577
578                //now hookup hookups
579                if ( hasHookups ) {
580                        hookupView(args[0], hooks);
581                }
582                return res;
583        };
584
585        // returns true or false if the args indicate a template is being used
586        isTemplate = function( args ) {
587                var secArgType = typeof args[1];
588
589                return typeof args[0] == "string" && (secArgType == 'object' || secArgType == 'function') && !args[1].nodeType && !args[1].jquery;
590        };
591
592        //returns the callback if there is one (for async view use)
593        getCallback = function( args ) {
594                return typeof args[3] === 'function' ? 3 : typeof args[2] === 'function' && 2;
595        };
596
597        hookupView = function( els , hooks) {
598                //remove all hookups
599                var hookupEls,
600                        len, i = 0,
601                        id, func;
602                els = els.filter(function(){
603                        return this.nodeType != 3; //filter out text nodes
604                })
605                hookupEls = els.add("[data-view-id]", els);
606                len = hookupEls.length;
607                for (; i < len; i++ ) {
608                        if ( hookupEls[i].getAttribute && (id = hookupEls[i].getAttribute('data-view-id')) && (func = hooks[id]) ) {
609                                func(hookupEls[i], id);
610                                delete hooks[id];
611                                hookupEls[i].removeAttribute('data-view-id');
612                        }
613                }
614                //copy remaining hooks back
615                $.extend($view.hookups, hooks);
616        };
617
618        /**
619         *  @add jQuery.fn
620         */
621        funcs = [
622        /**
623         *  @function prepend
624         *  @parent jQuery.View
625         *  abc
626         */
627        "prepend",
628        /**
629         *  @function append
630         *  @parent jQuery.View
631         *  abc
632         */
633        "append",
634        /**
635         *  @function after
636         *  @parent jQuery.View
637         *  abc
638         */
639        "after",
640        /**
641         *  @function before
642         *  @parent jQuery.View
643         *  abc
644         */
645        "before",
646        /**
647         *  @function text
648         *  @parent jQuery.View
649         *  abc
650         */
651        "text",
652        /**
653         *  @function html
654         *  @parent jQuery.View
655         *  abc
656         */
657        "html",
658        /**
659         *  @function replaceWith
660         *  @parent jQuery.View
661         *  abc
662         */
663        "replaceWith",
664        "val"];
665
666        //go through helper funcs and convert
667        for ( var i = 0; i < funcs.length; i++ ) {
668                convert(funcs[i]);
669        }
670
671})(jQuery);
672
673//jquery.lang.js
674
675(function( $ ) {
676        // Several of the methods in this plugin use code adapated from Prototype
677        //  Prototype JavaScript framework, version 1.6.0.1
678        //  (c) 2005-2007 Sam Stephenson
679        var regs = {
680                undHash: /_|-/,
681                colons: /::/,
682                words: /([A-Z]+)([A-Z][a-z])/g,
683                lowUp: /([a-z\d])([A-Z])/g,
684                dash: /([a-z\d])([A-Z])/g,
685                replacer: /\{([^\}]+)\}/g,
686                dot: /\./
687        },
688                getNext = function(current, nextPart, add){
689                        return current[nextPart] || ( add && (current[nextPart] = {}) );
690                },
691                isContainer = function(current){
692                        var type = typeof current;
693                        return type && (  type == 'function' || type == 'object' );
694                },
695                getObject = function( objectName, roots, add ) {
696                       
697                        var parts = objectName ? objectName.split(regs.dot) : [],
698                                length =  parts.length,
699                                currents = $.isArray(roots) ? roots : [roots || window],
700                                current,
701                                ret,
702                                i,
703                                c = 0,
704                                type;
705                       
706                        if(length == 0){
707                                return currents[0];
708                        }
709                        while(current = currents[c++]){
710                                for (i =0; i < length - 1 && isContainer(current); i++ ) {
711                                        current = getNext(current, parts[i], add);
712                                }
713                                if( isContainer(current) ) {
714                                       
715                                        ret = getNext(current, parts[i], add);
716                                       
717                                        if( ret !== undefined ) {
718                                               
719                                                if ( add === false ) {
720                                                        delete current[parts[i]];
721                                                }
722                                                return ret;
723                                               
724                                        }
725                                       
726                                }
727                        }
728                },
729
730                /**
731                 * @class jQuery.String
732                 *
733                 * A collection of useful string helpers.
734                 *
735                 */
736                str = $.String = $.extend( $.String || {} , {
737                        /**
738                         * @function
739                         * Gets an object from a string.
740                         * @param {String} name the name of the object to look for
741                         * @param {Array} [roots] an array of root objects to look for the name
742                         * @param {Boolean} [add] true to add missing objects to
743                         *  the path. false to remove found properties. undefined to
744                         *  not modify the root object
745                         */
746                        getObject : getObject,
747                        /**
748                         * Capitalizes a string
749                         * @param {String} s the string.
750                         * @return {String} a string with the first character capitalized.
751                         */
752                        capitalize: function( s, cache ) {
753                                return s.charAt(0).toUpperCase() + s.substr(1);
754                        },
755                        /**
756                         * Capitalizes a string from something undercored. Examples:
757                         * @codestart
758                         * jQuery.String.camelize("one_two") //-> "oneTwo"
759                         * "three-four".camelize() //-> threeFour
760                         * @codeend
761                         * @param {String} s
762                         * @return {String} a the camelized string
763                         */
764                        camelize: function( s ) {
765                                s = str.classize(s);
766                                return s.charAt(0).toLowerCase() + s.substr(1);
767                        },
768                        /**
769                         * Like camelize, but the first part is also capitalized
770                         * @param {String} s
771                         * @return {String} the classized string
772                         */
773                        classize: function( s , join) {
774                                var parts = s.split(regs.undHash),
775                                        i = 0;
776                                for (; i < parts.length; i++ ) {
777                                        parts[i] = str.capitalize(parts[i]);
778                                }
779
780                                return parts.join(join || '');
781                        },
782                        /**
783                         * Like [jQuery.String.classize|classize], but a space separates each 'word'
784                         * @codestart
785                         * jQuery.String.niceName("one_two") //-> "One Two"
786                         * @codeend
787                         * @param {String} s
788                         * @return {String} the niceName
789                         */
790                        niceName: function( s ) {
791                                str.classize(parts[i],' ');
792                        },
793
794                        /**
795                         * Underscores a string.
796                         * @codestart
797                         * jQuery.String.underscore("OneTwo") //-> "one_two"
798                         * @codeend
799                         * @param {String} s
800                         * @return {String} the underscored string
801                         */
802                        underscore: function( s ) {
803                                return s.replace(regs.colons, '/').replace(regs.words, '$1_$2').replace(regs.lowUp, '$1_$2').replace(regs.dash, '_').toLowerCase();
804                        },
805                        /**
806                         * Returns a string with {param} replaced values from data.
807                         *
808                         *     $.String.sub("foo {bar}",{bar: "far"})
809                         *     //-> "foo far"
810                         *     
811                         * @param {String} s The string to replace
812                         * @param {Object} data The data to be used to look for properties.  If it's an array, multiple
813                         * objects can be used.
814                         * @param {Boolean} [remove] if a match is found, remove the property from the object
815                         */
816                        sub: function( s, data, remove ) {
817                                var obs = [];
818                                obs.push(s.replace(regs.replacer, function( whole, inside ) {
819                                        //convert inside to type
820                                        var ob = getObject(inside, data, typeof remove == 'boolean' ? !remove : remove),
821                                                type = typeof ob;
822                                        if((type === 'object' || type === 'function') && type !== null){
823                                                obs.push(ob);
824                                                return "";
825                                        }else{
826                                                return ""+ob;
827                                        }
828                                }));
829                                return obs.length <= 1 ? obs[0] : obs;
830                        }
831                });
832
833})(jQuery);
834
835//jquery.lang.rsplit.js
836
837(function( $ ) {
838        /**
839         * @add jQuery.String
840         */
841        $.String.
842        /**
843         * Splits a string with a regex correctly cross browser
844         *
845         *     $.String.rsplit("a.b.c.d", /\./) //-> ['a','b','c','d']
846         *
847         * @param {String} string The string to split
848         * @param {RegExp} regex A regular expression
849         * @return {Array} An array of strings
850         */
851        rsplit = function( string, regex ) {
852                var result = regex.exec(string),
853                        retArr = [],
854                        first_idx, last_idx;
855                while ( result !== null ) {
856                        first_idx = result.index;
857                        last_idx = regex.lastIndex;
858                        if ( first_idx !== 0 ) {
859                                retArr.push(string.substring(0, first_idx));
860                                string = string.slice(first_idx);
861                        }
862                        retArr.push(result[0]);
863                        string = string.slice(result[0].length);
864                        result = regex.exec(string);
865                }
866                if ( string !== '' ) {
867                        retArr.push(string);
868                }
869                return retArr;
870        };
871})(jQuery);
872
873//jquery.view.ejs.js
874
875(function( $ ) {
876        var myEval = function(script){
877                        eval(script);
878                },
879                chop = function( string ) {
880                        return string.substr(0, string.length - 1);
881                },
882                rSplit = $.String.rsplit,
883                extend = $.extend,
884                isArray = $.isArray,
885                clean = function( content ) {
886                                return content.replace(/\\/g, '\\\\').replace(/\n/g, '\\n').replace(/"/g, '\\"');
887                }
888                // from prototype  http://www.prototypejs.org/
889                escapeHTML = function(content){
890                        return content.replace(/&/g,'&amp;')
891                                        .replace(/</g,'&lt;')
892                                        .replace(/>/g,'&gt;')
893                                        .replace(/"/g, '&#34;')
894                                        .replace(/'/g, "&#39;");
895                },
896                EJS = function( options ) {
897                        //returns a renderer function
898                        if ( this.constructor != EJS ) {
899                                var ejs = new EJS(options);
900                                return function( data, helpers ) {
901                                        return ejs.render(data, helpers);
902                                };
903                        }
904                        //so we can set the processor
905                        if ( typeof options == "function" ) {
906                                this.template = {};
907                                this.template.process = options;
908                                return;
909                        }
910                        //set options on self
911                        extend(this, EJS.options, options);
912                        this.template = compile(this.text, this.type, this.name);
913                };
914        /**
915         * @class jQuery.EJS
916         *
917         * @plugin jquery/view/ejs
918         * @parent jQuery.View
919         * @download  http://jmvcsite.heroku.com/pluginify?plugins[]=jquery/view/ejs/ejs.js
920         * @test jquery/view/ejs/qunit.html
921         *
922         *
923         * Ejs provides <a href="http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/">ERB</a>
924         * style client side templates.  Use them with controllers to easily build html and inject
925         * it into the DOM.
926         *
927         * ###  Example
928         *
929         * The following generates a list of tasks:
930         *
931         * @codestart html
932         * &lt;ul>
933         * &lt;% for(var i = 0; i < tasks.length; i++){ %>
934         *     &lt;li class="task &lt;%= tasks[i].identity %>">&lt;%= tasks[i].name %>&lt;/li>
935         * &lt;% } %>
936         * &lt;/ul>
937         * @codeend
938         *
939         * For the following examples, we assume this view is in <i>'views\tasks\list.ejs'</i>.
940         *
941         *
942         * ## Use
943         *
944         * ### Loading and Rendering EJS:
945         *
946         * You should use EJS through the helper functions [jQuery.View] provides such as:
947         *
948         *   - [jQuery.fn.after after]
949         *   - [jQuery.fn.append append]
950         *   - [jQuery.fn.before before]
951         *   - [jQuery.fn.html html],
952         *   - [jQuery.fn.prepend prepend],
953         *   - [jQuery.fn.replaceWith replaceWith], and
954         *   - [jQuery.fn.text text].
955         *
956         * or [jQuery.Controller.prototype.view].
957         *
958         * ### Syntax
959         *
960         * EJS uses 5 types of tags:
961         *
962         *   - <code>&lt;% CODE %&gt;</code> - Runs JS Code.
963         *     For example:
964         *     
965         *         <% alert('hello world') %>
966         *     
967         *   - <code>&lt;%= CODE %&gt;</code> - Runs JS Code and writes the result into the result of the template.
968         *     For example:
969         *     
970         *         <h1><%= 'hello world' %></h1>
971         *       
972         *   - <code>&lt;%~ CODE %&gt;</code> - Runs JS Code and writes the _escaped_ result into the result of the template.
973         *     For example:
974         *     
975         *         <%~ 'hello world' %>
976         *         
977         *   - <code>&lt;%%= CODE %&gt;</code> - Writes <%= CODE %> to the result of the template.  This is very useful for generators.
978         *     
979         *         <%%= 'hello world' %>
980         *         
981         *   - <code>&lt;%# CODE %&gt;</code> - Used for comments.  This does nothing.
982         *     
983         *         <%# 'hello world' %>
984         *       
985         * ## Hooking up controllers
986         *
987         * After drawing some html, you often want to add other widgets and plugins inside that html.
988         * View makes this easy.  You just have to return the Contoller class you want to be hooked up.
989         *
990         * @codestart
991         * &lt;ul &lt;%= Mxui.Tabs%>>...&lt;ul>
992         * @codeend
993         *
994         * You can even hook up multiple controllers:
995         *
996         * @codestart
997         * &lt;ul &lt;%= [Mxui.Tabs, Mxui.Filler]%>>...&lt;ul>
998         * @codeend
999         *
1000         * <h2>View Helpers</h2>
1001         * View Helpers return html code.  View by default only comes with
1002         * [jQuery.EJS.Helpers.prototype.view view] and [jQuery.EJS.Helpers.prototype.text text].
1003         * You can include more with the view/helpers plugin.  But, you can easily make your own!
1004         * Learn how in the [jQuery.EJS.Helpers Helpers] page.
1005         *
1006         * @constructor Creates a new view
1007         * @param {Object} options A hash with the following options
1008         * <table class="options">
1009         *     <tbody><tr><th>Option</th><th>Default</th><th>Description</th></tr>
1010         *     <tr>
1011         *      <td>url</td>
1012         *      <td>&nbsp;</td>
1013         *      <td>loads the template from a file.  This path should be relative to <i>[jQuery.root]</i>.
1014         *      </td>
1015         *     </tr>
1016         *     <tr>
1017         *      <td>text</td>
1018         *      <td>&nbsp;</td>
1019         *      <td>uses the provided text as the template. Example:<br/><code>new View({text: '&lt;%=user%>'})</code>
1020         *      </td>
1021         *     </tr>
1022         *     <tr>
1023         *      <td>element</td>
1024         *      <td>&nbsp;</td>
1025         *      <td>loads a template from the innerHTML or value of the element.
1026         *      </td>
1027         *     </tr>
1028         *     <tr>
1029         *      <td>type</td>
1030         *      <td>'<'</td>
1031         *      <td>type of magic tags.  Options are '&lt;' or '['
1032         *      </td>
1033         *     </tr>
1034         *     <tr>
1035         *      <td>name</td>
1036         *      <td>the element ID or url </td>
1037         *      <td>an optional name that is used for caching.
1038         *      </td>
1039         *     </tr>
1040         *     <tr>
1041         *      <td>cache</td>
1042         *      <td>true in production mode, false in other modes</td>
1043         *      <td>true to cache template.
1044         *      </td>
1045         *     </tr>
1046         *     
1047         *    </tbody></table>
1048         */
1049        $.EJS = EJS;
1050        /**
1051         * @Prototype
1052         */
1053        EJS.prototype = {
1054                constructor: EJS,
1055                /**
1056                 * Renders an object with extra view helpers attached to the view.
1057                 * @param {Object} object data to be rendered
1058                 * @param {Object} extra_helpers an object with additonal view helpers
1059                 * @return {String} returns the result of the string
1060                 */
1061                render: function( object, extraHelpers ) {
1062                        object = object || {};
1063                        this._extra_helpers = extraHelpers;
1064                        var v = new EJS.Helpers(object, extraHelpers || {});
1065                        return this.template.process.call(object, object, v);
1066                }
1067        };
1068        /* @Static */
1069
1070
1071        EJS.
1072        /**
1073         * Used to convert what's in &lt;%= %> magic tags to a string
1074         * to be inserted in the rendered output.
1075         *
1076         * Typically, it's a string, and the string is just inserted.  However,
1077         * if it's a function or an object with a hookup method, it can potentially be
1078         * be ran on the element after it's inserted into the page.
1079         *
1080         * This is a very nice way of adding functionality through the view.
1081         * Usually this is done with [jQuery.EJS.Helpers.prototype.plugin]
1082         * but the following fades in the div element after it has been inserted:
1083         *
1084         * @codestart
1085         * &lt;%= function(el){$(el).fadeIn()} %>
1086         * @codeend
1087         *
1088         * @param {String|Object|Function} input the value in between the
1089         * write majic tags: &lt;%= %>
1090         * @return {String} returns the content to be added to the rendered
1091         * output.  The content is different depending on the type:
1092         *
1093         *   * string - a bac
1094         *   * foo - bar
1095         */
1096        text = function( input ) {
1097                if ( typeof input == 'string' ) {
1098                        return input;
1099                }
1100                if ( input === null || input === undefined ) {
1101                        return '';
1102                }
1103                var hook =
1104                        (input.hookup && function( el, id ) {
1105                                input.hookup.call(input, el, id);
1106                        })
1107                        ||
1108                        (typeof input == 'function' && input)
1109                        ||
1110                        (isArray(input) && function( el, id ) {
1111                                for ( var i = 0; i < input.length; i++ ) {
1112                                        var stub;
1113                                        stub = input[i].hookup ? input[i].hookup(el, id) : input[i](el, id);
1114                                }
1115                        });
1116                if(hook){
1117                        return "data-view-id='" + $.View.hookup(hook) + "'";
1118                }
1119                return input.toString ? input.toString() : "";
1120        };
1121        EJS.clean = function(text){
1122                //return sanatized text
1123                if(typeof text == 'string'){
1124                        return escapeHTML(text)
1125                }else{
1126                        return "";
1127                }
1128        }
1129        //returns something you can call scan on
1130        var scan = function(scanner, source, block ) {
1131                var source_split = rSplit(source, /\n/),
1132                        i=0;
1133                for (; i < source_split.length; i++ ) {
1134                        scanline(scanner,  source_split[i], block);
1135                }
1136               
1137        },
1138        scanline= function(scanner,  line, block ) {
1139                scanner.lines++;
1140                var line_split = rSplit(line, scanner.splitter),
1141                        token;
1142                for ( var i = 0; i < line_split.length; i++ ) {
1143                        token = line_split[i];
1144                        if ( token !== null ) {
1145                                block(token, scanner);
1146                        }
1147                }
1148        },
1149        makeScanner = function(left, right){
1150                var scanner = {};
1151                extend(scanner, {
1152                        left: left + '%',
1153                        right: '%' + right,
1154                        dLeft: left + '%%',
1155                        dRight: '%%' + right,
1156                        eeLeft : left + '%==',
1157                        eLeft: left + '%=',
1158                        cmnt: left + '%#',
1159                        cleanLeft: left+"%~",
1160                        scan : scan,
1161                        lines : 0
1162                });
1163                scanner.splitter = new RegExp("(" + [scanner.dLeft, scanner.dRight, scanner.eeLeft, scanner.eLeft, scanner.cleanLeft,
1164                scanner.cmnt, scanner.left, scanner.right + '\n', scanner.right, '\n'].join(")|(").
1165                        replace(/\[/g,"\\[").replace(/\]/g,"\\]") + ")");
1166                return scanner;
1167        },
1168        // compiles a template
1169        compile = function( source, left, name ) {
1170                source = source.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
1171                //normalize line endings
1172                left = left || '<';
1173                var put_cmd = "___v1ew.push(",
1174                        insert_cmd = put_cmd,
1175                        buff = new EJS.Buffer(['var ___v1ew = [];'], []),
1176                        content = '',
1177                        put = function( content ) {
1178                                buff.push(put_cmd, '"', clean(content), '");');
1179                        },
1180                        startTag = null,
1181                        empty = function(){
1182                                content = ''
1183                        };
1184               
1185                scan( makeScanner(left, left === '[' ? ']' : '>') ,
1186                        source||"",
1187                        function( token, scanner ) {
1188                                // if we don't have a start pair
1189                                if ( startTag === null ) {
1190                                        switch ( token ) {
1191                                        case '\n':
1192                                                content = content + "\n";
1193                                                put(content);
1194                                                buff.cr();
1195                                                empty();
1196                                                break;
1197                                        case scanner.left:
1198                                        case scanner.eLeft:
1199                                        case scanner.eeLeft:
1200                                        case scanner.cleanLeft:
1201                                        case scanner.cmnt:
1202                                                startTag = token;
1203                                                if ( content.length > 0 ) {
1204                                                        put(content);
1205                                                }
1206                                                empty();
1207                                                break;
1208
1209                                                // replace <%% with <%
1210                                        case scanner.dLeft:
1211                                                content += scanner.left;
1212                                                break;
1213                                        default:
1214                                                content +=  token;
1215                                                break;
1216                                        }
1217                                }
1218                                else {
1219                                        switch ( token ) {
1220                                        case scanner.right:
1221                                                switch ( startTag ) {
1222                                                case scanner.left:
1223                                                        if ( content[content.length - 1] == '\n' ) {
1224                                                                content = chop(content);
1225                                                                buff.push(content, ";");
1226                                                                buff.cr();
1227                                                        }
1228                                                        else {
1229                                                                buff.push(content, ";");
1230                                                        }
1231                                                        break;
1232                                                case scanner.cleanLeft :
1233                                                        buff.push(insert_cmd, "(jQuery.EJS.clean(", content, ")));");
1234                                                        break;
1235                                                case scanner.eLeft:
1236                                                        buff.push(insert_cmd, "(jQuery.EJS.text(", content, ")));");
1237                                                        break;
1238                                                case scanner.eeLeft:
1239                                                        buff.push(insert_cmd, "(jQuery.EJS.text(", content, ")));");
1240                                                        break;
1241                                                }
1242                                                startTag = null;
1243                                                empty();
1244                                                break;
1245                                        case scanner.dRight:
1246                                                content += scanner.right;
1247                                                break;
1248                                        default:
1249                                                content += token;
1250                                                break;
1251                                        }
1252                                }
1253                        })
1254                if ( content.length > 0 ) {
1255                        // Should be content.dump in Ruby
1256                        buff.push(put_cmd, '"', clean(content) + '");');
1257                }
1258                var template = buff.close(),
1259                        out = {
1260                                out : 'try { with(_VIEW) { with (_CONTEXT) {' + template + " return ___v1ew.join('');}}}catch(e){e.lineNumber=null;throw e;}"
1261                        };
1262                //use eval instead of creating a function, b/c it is easier to debug
1263                myEval.call(out,'this.process = (function(_CONTEXT,_VIEW){' + out.out + '});\r\n//@ sourceURL='+name+".js");
1264                return out;
1265        };
1266
1267
1268        // a line and script buffer
1269        // we use this so we know line numbers when there
1270        // is an error. 
1271        // pre and post are setup and teardown for the buffer
1272        EJS.Buffer = function( pre_cmd, post ) {
1273                this.line = [];
1274                this.script = [];
1275                this.post = post;
1276
1277                // add the pre commands to the first line
1278                this.push.apply(this, pre_cmd);
1279        };
1280        EJS.Buffer.prototype = {
1281                //need to maintain your own semi-colons (for performance)
1282                push: function() {
1283                        this.line.push.apply(this.line, arguments);
1284                },
1285
1286                cr: function() {
1287                        this.script.push(this.line.join(''), "\n");
1288                        this.line = [];
1289                },
1290                //returns the script too
1291                close: function() {
1292                        var stub;
1293
1294                        if ( this.line.length > 0 ) {
1295                                this.script.push(this.line.join(''));
1296                                this.line = [];
1297                        }
1298
1299                        stub = this.post.length && this.push.apply(this, this.post);
1300
1301                        this.script.push(";"); //makes sure we always have an ending /
1302                        return this.script.join("");
1303                }
1304
1305        };
1306       
1307
1308        //type, cache, folder
1309        /**
1310         * @attribute options
1311         * Sets default options for all views
1312         * <table class="options">
1313         * <tbody><tr><th>Option</th><th>Default</th><th>Description</th></tr>
1314         * <tr>
1315         * <td>type</td>
1316         * <td>'<'</td>
1317         * <td>type of magic tags.  Options are '&lt;' or '['
1318         * </td>
1319         * </tr>
1320         * <tr>
1321         * <td>cache</td>
1322         * <td>true in production mode, false in other modes</td>
1323         * <td>true to cache template.
1324         * </td>
1325         * </tr>
1326         * </tbody></table>
1327         *
1328         */
1329        EJS.options = {
1330                type: '<',
1331                ext: '.ejs'
1332        };
1333
1334
1335
1336
1337        /**
1338         * @class jQuery.EJS.Helpers
1339         * @parent jQuery.EJS
1340         * By adding functions to jQuery.EJS.Helpers.prototype, those functions will be available in the
1341         * views.
1342         * @constructor Creates a view helper.  This function is called internally.  You should never call it.
1343         * @param {Object} data The data passed to the view.  Helpers have access to it through this._data
1344         */
1345        EJS.Helpers = function( data, extras ) {
1346                this._data = data;
1347                this._extras = extras;
1348                extend(this, extras);
1349        };
1350        /* @prototype*/
1351        EJS.Helpers.prototype = {
1352                /**
1353                 * Hooks up a jQuery plugin on.
1354                 * @param {String} name the plugin name
1355                 */
1356                plugin: function( name ) {
1357                        var args = $.makeArray(arguments),
1358                                widget = args.shift();
1359                        return function( el ) {
1360                                var jq = $(el);
1361                                jq[widget].apply(jq, args);
1362                        };
1363                },
1364                /**
1365                 * Renders a partial view.  This is deprecated in favor of <code>$.View()</code>.
1366                 */
1367                view: function( url, data, helpers ) {
1368                        helpers = helpers || this._extras;
1369                        data = data || this._data;
1370                        return $.View(url, data, helpers); //new EJS(options).render(data, helpers);
1371                }
1372        };
1373
1374
1375        $.View.register({
1376                suffix: "ejs",
1377                //returns a function that renders the view
1378                script: function( id, src ) {
1379                        return "jQuery.EJS(function(_CONTEXT,_VIEW) { " + new EJS({
1380                                text: src
1381                        }).template.out + " })";
1382                },
1383                renderer: function( id, text ) {
1384                        var ejs = new EJS({
1385                                text: text,
1386                                name: id
1387                        });
1388                        return function( data, helpers ) {
1389                                return ejs.render.call(ejs, data, helpers);
1390                        };
1391                }
1392        });
1393})(jQuery);
Note: See TracBrowser for help on using the repository browser.