source: Dev/branches/rest-dojo-ui/client/dojox/dtl/dom.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

  • Property svn:executable set to *
File size: 28.8 KB
Line 
1define([
2        "dojo/_base/lang",
3        "./_base",
4        "dojox/string/tokenize",
5        "./Context",
6        "dojo/dom",
7        "dojo/dom-construct",
8        "dojo/_base/html",
9        "dojo/_base/array",
10        "dojo/_base/connect",
11        "dojo/_base/sniff"
12], function(lang,dd,Tokenize,context,dom,domconstruct,html,array,connect,has){
13        /*=====
14                Tokenize = dojox.string.tokenize;
15                dd = dojox.dtl;
16        =====*/
17        dd.BOOLS = {checked: 1, disabled: 1, readonly: 1};
18        dd.TOKEN_CHANGE = -11;
19        dd.TOKEN_ATTR = -12;
20        dd.TOKEN_CUSTOM = -13;
21        dd.TOKEN_NODE = 1;
22
23        var ddt = dd.text;
24        var ddh = dd.dom = {
25                _attributes: {},
26                _uppers: {},
27                _re4: /^function anonymous\(\)\s*{\s*(.*)\s*}$/,
28                _reTrim: /(?:^[\n\s]*(\{%)?\s*|\s*(%\})?[\n\s]*$)/g,
29                _reSplit: /\s*%\}[\n\s]*\{%\s*/g,
30                getTemplate: function(text){
31                        if(typeof this._commentable == "undefined"){
32                                // Check to see if the browser can handle comments
33                                this._commentable = false;
34                                var div = document.createElement("div"), comment = "Test comment handling, and long comments, using comments whenever possible.";
35                                div.innerHTML = "<!--" + comment + "-->";
36                                if(div.childNodes.length && div.firstChild.nodeType == 8 && div.firstChild.data == comment){
37                                        this._commentable = true;
38                                }
39                        }
40
41                        if(!this._commentable){
42                                // Strip comments
43                                text = text.replace(/<!--({({|%).*?(%|})})-->/g, "$1");
44                        }
45
46                        if(has("ie")){
47                                text = text.replace(/\b(checked|disabled|readonly|style)="/g, 't$1="');
48                        }
49                        text = text.replace(/\bstyle="/g, 'tstyle="');
50
51                        var match;
52                        var table = has("webkit");
53                        var pairs = [ // Format: [enable, parent, allowed children (first for nesting), nestings]
54                                [true, "select", "option"],
55                                [table, "tr", "td|th"],
56                                [table, "thead", "tr", "th"],
57                                [table, "tbody", "tr", "td"],
58                                [table, "table", "tbody|thead|tr", "tr", "td"]
59                        ];
60                        var replacements = [];
61                        // Some tags can't contain text. So we wrap the text in tags that they can have.
62                        for(var i = 0, pair; pair = pairs[i]; i++){
63                                if(!pair[0]){
64                                        continue;
65                                }
66                                if(text.indexOf("<" + pair[1]) != -1){
67                                        var selectRe = new RegExp("<" + pair[1] + "(?:.|\n)*?>((?:.|\n)+?)</" + pair[1] + ">", "ig");
68                                        tagLoop: while(match = selectRe.exec(text)){
69                                                // Do it like this to make sure we don't double-wrap
70                                                var inners = pair[2].split("|");
71                                                var innerRe = [];
72                                                for(var j = 0, inner; inner = inners[j]; j++){
73                                                        innerRe.push("<" + inner + "(?:.|\n)*?>(?:.|\n)*?</" + inner + ">");
74                                                }
75                                                var tags = [];
76                                                var tokens = Tokenize(match[1], new RegExp("(" + innerRe.join("|") + ")", "ig"), function(data){
77                                                        var tag = /<(\w+)/.exec(data)[1];
78                                                        if(!tags[tag]){
79                                                                tags[tag] = true;
80                                                                tags.push(tag);
81                                                        }
82                                                        return {data: data};
83                                                });
84                                                if(tags.length){
85                                                        var tag = (tags.length == 1) ? tags[0] : pair[2].split("|")[0];
86
87                                                        var replace = [];
88                                                        for(var j = 0, jl = tokens.length; j < jl; j++) {
89                                                                var token = tokens[j];
90                                                                if(lang.isObject(token)){
91                                                                        replace.push(token.data);
92                                                                }else{
93                                                                        var stripped = token.replace(this._reTrim, "");
94                                                                        if(!stripped){ continue; }
95                                                                        token = stripped.split(this._reSplit);
96                                                                        for(var k = 0, kl = token.length; k < kl; k++){
97                                                                                var replacement = "";
98                                                                                for(var p = 2, pl = pair.length; p < pl; p++){
99                                                                                        if(p == 2){
100                                                                                                replacement += "<" + tag + ' dtlinstruction="{% ' + token[k].replace('"', '\\"') + ' %}">';
101                                                                                        }else if(tag == pair[p]) {
102                                                                                                continue;
103                                                                                        }else{
104                                                                                                replacement += "<" + pair[p] + ">";
105                                                                                        }
106                                                                                }
107                                                                                replacement += "DTL";
108                                                                                for(var p = pair.length - 1; p > 1; p--){
109                                                                                        if(p == 2){
110                                                                                                replacement += "</" + tag + ">";
111                                                                                        }else if(tag == pair[p]) {
112                                                                                                continue;
113                                                                                        }else{
114                                                                                                replacement += "</" + pair[p] + ">";
115                                                                                        }
116                                                                                }
117                                                                                replace.push("\xFF" + replacements.length);
118                                                                                replacements.push(replacement);
119                                                                        }
120                                                                }
121                                                        }
122                                                        text = text.replace(match[1], replace.join(""));
123                                                }
124                                        }
125                                }
126                        }
127
128                        for(var i = replacements.length; i--;){
129                                text = text.replace("\xFF" + i, replacements[i]);
130                        }
131
132                        var re = /\b([a-zA-Z_:][a-zA-Z0-9_\-\.:]*)=['"]/g;
133                        while(match = re.exec(text)){
134                                var lower = match[1].toLowerCase();
135                                if(lower == "dtlinstruction"){ continue; }
136                                if(lower != match[1]){
137                                        this._uppers[lower] = match[1];
138                                }
139                                this._attributes[lower] = true;
140                        }
141                        var div = document.createElement("div");
142                        div.innerHTML = text;
143                        var output = {nodes: []};
144                        while(div.childNodes.length){
145                                output.nodes.push(div.removeChild(div.childNodes[0]))
146                        }
147
148                        return output;
149                },
150                tokenize: function(/*Node*/ nodes){
151                        var tokens = [];
152
153                        for(var i = 0, node; node = nodes[i++];){
154                                if(node.nodeType != 1){
155                                        this.__tokenize(node, tokens);
156                                }else{
157                                        this._tokenize(node, tokens);
158                                }
159                        }
160
161                        return tokens;
162                },
163                _swallowed: [],
164                _tokenize: function(/*Node*/ node, /*Array*/ tokens){
165                        var first = false;
166                        var swallowed = this._swallowed;
167                        var i, j, tag, child;
168
169                        if(!tokens.first){
170                                // Try to efficiently associate tags that use an attribute to
171                                // remove the node from DOM (eg dojoType) so that we can efficiently
172                                // locate them later in the tokenizing.
173                                first = tokens.first = true;
174                                var tags = dd.register.getAttributeTags();
175                                for(i = 0; tag = tags[i]; i++){
176                                        try{
177                                                (tag[2])({ swallowNode: function(){ throw 1; }}, new dd.Token(dd.TOKEN_ATTR, ""));
178                                        }catch(e){
179                                                swallowed.push(tag);
180                                        }
181                                }
182                        }
183
184                        for(i = 0; tag = swallowed[i]; i++){
185                                var text = node.getAttribute(tag[0]);
186                                if(text){
187                                        var swallowed = false;
188                                        var custom = (tag[2])({ swallowNode: function(){ swallowed = true; return node; }}, new dd.Token(dd.TOKEN_ATTR, tag[0] + " " + text));
189                                        if(swallowed){
190                                                if(node.parentNode && node.parentNode.removeChild){
191                                                        node.parentNode.removeChild(node);
192                                                }
193                                                tokens.push([dd.TOKEN_CUSTOM, custom]);
194                                                return;
195                                        }
196                                }
197                        }
198
199                        var children = [];
200                        if(has("ie") && node.tagName == "SCRIPT"){
201                                children.push({
202                                        nodeType: 3,
203                                        data: node.text
204                                });
205                                node.text = "";
206                        }else{
207                                for(i = 0; child = node.childNodes[i]; i++){
208                                        children.push(child);
209                                }
210                        }
211
212                        tokens.push([dd.TOKEN_NODE, node]);
213
214                        var change = false;
215                        if(children.length){
216                                // Only do a change request if we need to
217                                tokens.push([dd.TOKEN_CHANGE, node]);
218                                change = true;
219                        }
220
221                        for(var key in this._attributes){
222                                var clear = false;
223
224                                var value = "";
225                                if(key == "class"){
226                                        value = node.className || value;
227                                }else if(key == "for"){
228                                        value = node.htmlFor || value;
229                                }else if(key == "value" && node.value == node.innerHTML){
230                                        // Sometimes .value is set the same as the contents of the item (button)
231                                        continue;
232                                }else if(node.getAttribute){
233                                        value = node.getAttribute(key, 2) || value;
234                                        if(key == "href" || key == "src"){
235                                                if(has("ie")){
236                                                        var hash = location.href.lastIndexOf(location.hash);
237                                                        var href = location.href.substring(0, hash).split("/");
238                                                        href.pop();
239                                                        href = href.join("/") + "/";
240                                                        if(value.indexOf(href) == 0){
241                                                                value = value.replace(href, "");
242                                                        }
243                                                        value = decodeURIComponent(value);
244                                                }
245                                        }else if(key == "tstyle"){
246                                                clear = key; // Placeholder because we can't use style
247                                                key = "style";
248                                        }else if(dd.BOOLS[key.slice(1)] && lang.trim(value)){
249                                                key = key.slice(1);
250                                        }else if(this._uppers[key] && lang.trim(value)){
251                                                clear = this._uppers[key]; // Replaced by lowercase
252                                        }
253                                }
254
255                                if(clear){
256                                        // Clear out values that are different than will
257                                        // be used in plugins
258                                        node.setAttribute(clear, "");
259                                        node.removeAttribute(clear);
260                                }
261
262                                if(typeof value == "function"){
263                                        value = value.toString().replace(this._re4, "$1");
264                                }
265
266                                if(!change){
267                                        // Only do a change request if we need to
268                                        tokens.push([dd.TOKEN_CHANGE, node]);
269                                        change = true;
270                                }
271
272                                // We'll have to resolve attributes during parsing (some ref plugins)
273
274                                tokens.push([dd.TOKEN_ATTR, node, key, value]);
275                        }
276
277                        for(i = 0, child; child = children[i]; i++){
278                                if(child.nodeType == 1){
279                                        var instruction = child.getAttribute("dtlinstruction");
280                                        if(instruction){
281                                                child.parentNode.removeChild(child);
282                                                child = {
283                                                        nodeType: 8,
284                                                        data: instruction
285                                                };
286                                        }
287                                }
288                                this.__tokenize(child, tokens);
289                        }
290
291                        if(!first && node.parentNode && node.parentNode.tagName){
292                                if(change){
293                                        tokens.push([dd.TOKEN_CHANGE, node, true]);
294                                }
295                                tokens.push([dd.TOKEN_CHANGE, node.parentNode]);
296                                node.parentNode.removeChild(node);
297                        }else{
298                                // If this node is parentless, it's a base node, so we have to "up" change to itself
299                                // and note that it's a top-level to watch for errors
300                                tokens.push([dd.TOKEN_CHANGE, node, true, true]);
301                        }
302                },
303                __tokenize: function(child, tokens){
304                        var data = child.data;
305                        switch(child.nodeType){
306                                case 1:
307                                        this._tokenize(child, tokens);
308                                        return;
309                                case 3:
310                                        if(data.match(/[^\s\n]/) && (data.indexOf("{{") != -1 || data.indexOf("{%") != -1)){
311                                                var texts = ddt.tokenize(data);
312                                                for(var j = 0, text; text = texts[j]; j++){
313                                                        if(typeof text == "string"){
314                                                                tokens.push([dd.TOKEN_TEXT, text]);
315                                                        }else{
316                                                                tokens.push(text);
317                                                        }
318                                                }
319                                        }else{
320                                                tokens.push([child.nodeType, child]);
321                                        }
322                                        if(child.parentNode) child.parentNode.removeChild(child);
323                                        return;
324                                case 8:
325                                        if(data.indexOf("{%") == 0){
326                                                var text = lang.trim(data.slice(2, -2));
327                                                if(text.substr(0, 5) == "load "){
328                                                        var parts = lang.trim(text).split(/\s+/g);
329                                                        for(var i = 1, part; part = parts[i]; i++){
330                                                                if (/\./.test(part)){
331                                                                        part = part.replace(/\./g,"/");
332                                                                }
333                                                                require([part]);
334                                                        }
335                                                }
336                                                tokens.push([dd.TOKEN_BLOCK, text]);
337                                        }
338                                        if(data.indexOf("{{") == 0){
339                                                tokens.push([dd.TOKEN_VAR, lang.trim(data.slice(2, -2))]);
340                                        }
341                                        if(child.parentNode) child.parentNode.removeChild(child);
342                                        return;
343                        }
344                }
345        };
346
347        dd.DomTemplate = lang.extend(function(/*String|DOMNode|dojo._Url*/ obj){
348                // summary: The template class for DOM templating.
349                if(!obj.nodes){
350                        var node = dom.byId(obj);
351                        if(node && node.nodeType == 1){
352                                array.forEach(["class", "src", "href", "name", "value"], function(item){
353                                        ddh._attributes[item] = true;
354                                });
355                                obj = {
356                                        nodes: [node]
357                                };
358                        }else{
359                                if(typeof obj == "object"){
360                                        obj = ddt.getTemplateString(obj);
361                                }
362                                obj = ddh.getTemplate(obj);
363                        }
364                }
365
366                var tokens = ddh.tokenize(obj.nodes);
367                if(dd.tests){
368                        this.tokens = tokens.slice(0);
369                }
370
371                var parser = new dd._DomParser(tokens);
372                this.nodelist = parser.parse();
373        },
374        {
375                _count: 0,
376                _re: /\bdojo:([a-zA-Z0-9_]+)\b/g,
377                setClass: function(/*String*/str){
378                        // summary: Sets the specified class name on the root node.
379                        this.getRootNode().className = str;
380                },
381                getRootNode: function(){
382                        // summary: Returns the template root node.
383                        return this.buffer.rootNode;
384                },
385                getBuffer: function(){
386                        // summary: Returns a new buffer.
387                        return new dd.DomBuffer();
388                },
389                render: function(/*dojox.dtl.Context?*/context, /*concatenable?*/buffer){
390                        // summary: Renders this template.
391                        buffer = this.buffer = buffer || this.getBuffer();
392                        this.rootNode = null;
393                        var output = this.nodelist.render(context || new dd.Context({}), buffer);
394                        for(var i = 0, node; node = buffer._cache[i]; i++){
395                                if(node._cache){
396                                        node._cache.length = 0;
397                                }
398                        }
399                        return output;
400                },
401                unrender: function(context, buffer){
402                        return this.nodelist.unrender(context, buffer);
403                }
404        });
405
406        dd.DomBuffer = lang.extend(function(/*Node*/ parent){
407                // summary: Allows the manipulation of DOM
408                // description:
409                //              Use this to append a child, change the parent, or
410                //              change the attribute of the current node.
411                this._parent = parent;
412                this._cache = [];
413        },
414        {
415                concat: function(/*DOMNode*/ node){
416                        var parent = this._parent;
417                        if(parent && node.parentNode && node.parentNode === parent && !parent._dirty){
418                                return this;
419                        }
420
421                        if(node.nodeType == 1 && !this.rootNode){
422                                this.rootNode = node || true;
423                                return this;
424                        }
425
426                        if(!parent){
427                                if(node.nodeType == 3 && lang.trim(node.data)){
428                                        throw new Error("Text should not exist outside of the root node in template");
429                                }
430                                return this;
431                        }
432                        if(this._closed){
433                                if(node.nodeType == 3 && !lang.trim(node.data)){
434                                        return this;
435                                }else{
436                                        throw new Error("Content should not exist outside of the root node in template");
437                                }
438                        }
439                        if(parent._dirty){
440                                if(node._drawn && node.parentNode == parent){
441                                        var caches = parent._cache;
442                                        if(caches){
443                                                for(var i = 0, cache; cache = caches[i]; i++){
444                                                        this.onAddNode && this.onAddNode(cache);
445                                                        parent.insertBefore(cache, node);
446                                                        this.onAddNodeComplete && this.onAddNodeComplete(cache);
447                                                }
448                                                caches.length = 0;
449                                        }
450                                }
451                                parent._dirty = false;
452                        }
453                        if(!parent._cache){
454                                parent._cache = [];
455                                this._cache.push(parent);
456                        }
457                        parent._dirty = true;
458                        parent._cache.push(node);
459                        return this;
460                },
461                remove: function(/*String|DomNode*/obj){
462                        if(typeof obj == "string"){
463                                if(this._parent){
464                                        this._parent.removeAttribute(obj);
465                                }
466                        }else{
467                                if(obj.nodeType == 1 && !this.getRootNode() && !this._removed){
468                                        this._removed = true;
469                                        return this;
470                                }
471                                if(obj.parentNode){
472                                        this.onRemoveNode && this.onRemoveNode(obj);
473                                        if(obj.parentNode){
474                                                obj.parentNode.removeChild(obj);
475                                        }
476                                }
477                        }
478                        return this;
479                },
480                setAttribute: function(key, value){
481                        var old = html.attr(this._parent, key);
482                        if(this.onChangeAttribute && old != value){
483                                this.onChangeAttribute(this._parent, key, old, value);
484                        }
485                        if(key == "style"){
486                                //console.log(value);
487                                this._parent.style.cssText = value;
488                        }else{
489                                html.attr(this._parent, key, value);
490                                //console.log(this._parent, key, value);
491                                if(key == "value"){
492                                        this._parent.setAttribute(key, value);
493                                }
494                        }
495                        return this;
496                },
497                addEvent: function(context, type, fn, /*Array|Function*/ args){
498                        if(!context.getThis()){ throw new Error("You must use Context.setObject(instance)"); }
499                        this.onAddEvent && this.onAddEvent(this.getParent(), type, fn);
500                        var resolved = fn;
501                        if(lang.isArray(args)){
502                                resolved = function(e){
503                                        this[fn].apply(this, [e].concat(args));
504                                }
505                        }
506                        return connect.connect(this.getParent(), type, context.getThis(), resolved);
507                },
508                setParent: function(node, /*Boolean?*/ up, /*Boolean?*/ root){
509                        if(!this._parent) this._parent = this._first = node;
510
511                        if(up && root && node === this._first){
512                                this._closed = true;
513                        }
514
515                        if(up){
516                                var parent = this._parent;
517                                var script = "";
518                                var ie = has("ie") && parent.tagName == "SCRIPT";
519                                if(ie){
520                                        parent.text = "";
521                                }
522                                if(parent._dirty){
523                                        var caches = parent._cache;
524                                        var select = (parent.tagName == "SELECT" && !parent.options.length);
525                                        for(var i = 0, cache; cache = caches[i]; i++){
526                                                if(cache !== parent){
527                                                        this.onAddNode && this.onAddNode(cache);
528                                                        if(ie){
529                                                                script += cache.data;
530                                                        }else{
531                                                                parent.appendChild(cache);
532                                                                if(select && cache.defaultSelected && i){
533                                                                        select = i;
534                                                                }
535                                                        }
536                                                        this.onAddNodeComplete && this.onAddNodeComplete(cache);
537                                                }
538                                        }
539                                        if(select){
540                                                parent.options.selectedIndex = (typeof select == "number") ? select : 0;
541                                        }
542                                        caches.length = 0;
543                                        parent._dirty = false;
544                                }
545                                if(ie){
546                                        parent.text = script;
547                                }
548                        }
549
550                        this._parent = node;
551                        this.onSetParent && this.onSetParent(node, up, root);
552                        return this;
553                },
554                getParent: function(){
555                        return this._parent;
556                },
557                getRootNode: function(){
558                        return this.rootNode;
559                }
560                /*=====
561                ,
562                onSetParent: function(node, up){
563                        // summary: Stub called when setParent is used.
564                },
565                onAddNode: function(node){
566                        // summary: Stub called before new nodes are added
567                },
568                onAddNodeComplete: function(node){
569                        // summary: Stub called after new nodes are added
570                },
571                onRemoveNode: function(node){
572                        // summary: Stub called when nodes are removed
573                },
574                onChangeAttribute: function(node, attribute, old, updated){
575                        // summary: Stub called when an attribute is changed
576                },
577                onChangeData: function(node, old, updated){
578                        // summary: Stub called when a data in a node is changed
579                },
580                onClone: function(from, to){
581                        // summary: Stub called when a node is duplicated
582                        // from: DOMNode
583                        // to: DOMNode
584                },
585                onAddEvent: function(node, type, description){
586                        // summary: Stub to call when you're adding an event
587                        // node: DOMNode
588                        // type: String
589                        // description: String
590                }
591                =====*/
592        });
593
594        dd._DomNode = lang.extend(function(node){
595                // summary: Places a node into DOM
596                this.contents = node;
597        },
598        {
599                render: function(context, buffer){
600                        this._rendered = true;
601                        return buffer.concat(this.contents);
602                },
603                unrender: function(context, buffer){
604                        if(!this._rendered){
605                                return buffer;
606                        }
607                        this._rendered = false;
608                        return buffer.remove(this.contents);
609                },
610                clone: function(buffer){
611                        return new this.constructor(this.contents);
612                }
613        });
614
615        dd._DomNodeList = lang.extend(function(/*Node[]*/ nodes){
616                // summary: A list of any DOM-specific node objects
617                // description:
618                //              Any object that's used in the constructor or added
619                //              through the push function much implement the
620                //              render, unrender, and clone functions.
621                this.contents = nodes || [];
622        },
623        {
624                push: function(node){
625                        this.contents.push(node);
626                },
627                unshift: function(node){
628                        this.contents.unshift(node);
629                },
630                render: function(context, buffer, /*Node*/ instance){
631                        buffer = buffer || dd.DomTemplate.prototype.getBuffer();
632
633                        if(instance){
634                                var parent = buffer.getParent();
635                        }
636                        for(var i = 0; i < this.contents.length; i++){
637                                buffer = this.contents[i].render(context, buffer);
638                                if(!buffer) throw new Error("Template node render functions must return their buffer");
639                        }
640                        if(parent){
641                                buffer.setParent(parent);
642                        }
643                        return buffer;
644                },
645                dummyRender: function(context, buffer, asNode){
646                        // summary: A really expensive way of checking to see how a rendering will look.
647                        //              Used in the ifchanged tag
648                        var div = document.createElement("div");
649
650                        var parent = buffer.getParent();
651                        var old = parent._clone;
652                        // Tell the clone system to attach itself to our new div
653                        parent._clone = div;
654                        var nodelist = this.clone(buffer, div);
655                        if(old){
656                                // Restore state if there was a previous clone
657                                parent._clone = old;
658                        }else{
659                                // Remove if there was no clone
660                                parent._clone = null;
661                        }
662
663                        buffer = dd.DomTemplate.prototype.getBuffer();
664                        nodelist.unshift(new dd.ChangeNode(div));
665                        nodelist.unshift(new dd._DomNode(div));
666                        nodelist.push(new dd.ChangeNode(div, true));
667                        nodelist.render(context, buffer);
668
669                        if(asNode){
670                                return buffer.getRootNode();
671                        }
672
673                        var html = div.innerHTML;
674                        return (has("ie")) ? domconstruct.replace(/\s*_(dirty|clone)="[^"]*"/g, "") : html;
675                },
676                unrender: function(context, buffer, instance){
677                        if(instance){
678                                var parent = buffer.getParent();
679                        }
680                        for(var i = 0; i < this.contents.length; i++){
681                                buffer = this.contents[i].unrender(context, buffer);
682                                if(!buffer) throw new Error("Template node render functions must return their buffer");
683                        }
684                        if(parent){
685                                buffer.setParent(parent);
686                        }
687                        return buffer;
688                },
689                clone: function(buffer){
690                        // summary:
691                        //              Used to create an identical copy of a NodeList, useful for things like the for tag.
692                        var parent = buffer.getParent();
693                        var contents = this.contents;
694                        var nodelist = new dd._DomNodeList();
695                        var cloned = [];
696                        for(var i = 0; i < contents.length; i++){
697                                var clone = contents[i].clone(buffer);
698                                if(clone instanceof dd.ChangeNode || clone instanceof dd._DomNode){
699                                        var item = clone.contents._clone;
700                                        if(item){
701                                                clone.contents = item;
702                                        }else if(parent != clone.contents && clone instanceof dd._DomNode){
703                                                var node = clone.contents;
704                                                clone.contents = clone.contents.cloneNode(false);
705                                                buffer.onClone && buffer.onClone(node, clone.contents);
706                                                cloned.push(node);
707                                                node._clone = clone.contents;
708                                        }
709                                }
710                                nodelist.push(clone);
711                        }
712
713                        for(var i = 0, clone; clone = cloned[i]; i++){
714                                clone._clone = null;
715                        }
716
717                        return nodelist;
718                },
719                rtrim: function(){
720                        while(1){
721                                var i = this.contents.length - 1;
722                                if(this.contents[i] instanceof dd._DomTextNode && this.contents[i].isEmpty()){
723                                        this.contents.pop();
724                                }else{
725                                        break;
726                                }
727                        }
728
729                        return this;
730                }
731        });
732
733        dd._DomVarNode = lang.extend(function(str){
734                // summary: A node to be processed as a variable
735                // description:
736                //              Will render an object that supports the render function
737                //              and the getRootNode function
738                this.contents = new dd._Filter(str);
739        },
740        {
741                render: function(context, buffer){
742                        var str = this.contents.resolve(context);
743
744                        // What type of rendering?
745                        var type = "text";
746                        if(str){
747                                if(str.render && str.getRootNode){
748                                        type = "injection";
749                                }else if(str.safe){
750                                        if(str.nodeType){
751                                                type = "node";
752                                        }else if(str.toString){
753                                                str = str.toString();
754                                                type = "html";
755                                        }
756                                }
757                        }
758
759                        // Has the typed changed?
760                        if(this._type && type != this._type){
761                                this.unrender(context, buffer);
762                        }
763                        this._type = type;
764
765                        // Now render
766                        switch(type){
767                        case "text":
768                                this._rendered = true;
769                                this._txt = this._txt || document.createTextNode(str);
770                                if(this._txt.data != str){
771                                        var old = this._txt.data;
772                                        this._txt.data = str;
773                                        buffer.onChangeData && buffer.onChangeData(this._txt, old, this._txt.data);
774                                }
775                                return buffer.concat(this._txt);
776                        case "injection":
777                                var root = str.getRootNode();
778
779                                if(this._rendered && root != this._root){
780                                        buffer = this.unrender(context, buffer);
781                                }
782                                this._root = root;
783
784                                var injected = this._injected = new dd._DomNodeList();
785                                injected.push(new dd.ChangeNode(buffer.getParent()));
786                                injected.push(new dd._DomNode(root));
787                                injected.push(str);
788                                injected.push(new dd.ChangeNode(buffer.getParent()));
789                                this._rendered = true;
790
791                                return injected.render(context, buffer);
792                        case "node":
793                                this._rendered = true;
794                                if(this._node && this._node != str && this._node.parentNode && this._node.parentNode === buffer.getParent()){
795                                        this._node.parentNode.removeChild(this._node);
796                                }
797                                this._node = str;
798                                return buffer.concat(str);
799                        case "html":
800                                if(this._rendered && this._src != str){
801                                        buffer = this.unrender(context, buffer);
802                                }
803                                this._src = str;
804
805                                // This can get reset in the above tag
806                                if(!this._rendered){
807                                        this._rendered = true;
808                                        this._html = this._html || [];
809                                        var div = (this._div = this._div || document.createElement("div"));
810                                        div.innerHTML = str;
811                                        var children = div.childNodes;
812                                        while(children.length){
813                                                var removed = div.removeChild(children[0]);
814                                                this._html.push(removed);
815                                                buffer = buffer.concat(removed);
816                                        }
817                                }
818
819                                return buffer;
820                        default:
821                                return buffer;
822                        }
823                },
824                unrender: function(context, buffer){
825                        if(!this._rendered){
826                                return buffer;
827                        }
828                        this._rendered = false;
829
830                        // Unrender injected nodes
831                        switch(this._type){
832                        case "text":
833                                return buffer.remove(this._txt);
834                        case "injection":
835                                return this._injection.unrender(context, buffer);
836                        case "node":
837                                if(this._node.parentNode === buffer.getParent()){
838                                        return buffer.remove(this._node);
839                                }
840                                return buffer;
841                        case "html":
842                                for(var i = 0, l = this._html.length; i < l; i++){
843                                        buffer = buffer.remove(this._html[i]);
844                                }
845                                return buffer;
846                        default:
847                                return buffer;
848                        }
849                },
850                clone: function(){
851                        return new this.constructor(this.contents.getExpression());
852                }
853        });
854
855        dd.ChangeNode = lang.extend(function(node, /*Boolean?*/ up, /*Bookean*/ root){
856                // summary: Changes the parent during render/unrender
857                this.contents = node;
858                this.up = up;
859                this.root = root;
860        },
861        {
862                render: function(context, buffer){
863                        return buffer.setParent(this.contents, this.up, this.root);
864                },
865                unrender: function(context, buffer){
866                        if(!buffer.getParent()){
867                                return buffer;
868                        }
869                        return buffer.setParent(this.contents);
870                },
871                clone: function(){
872                        return new this.constructor(this.contents, this.up, this.root);
873                }
874        });
875
876        dd.AttributeNode = lang.extend(function(key, value){
877                // summary: Works on attributes
878                this.key = key;
879                this.value = value;
880                this.contents = value;
881                if(this._pool[value]){
882                        this.nodelist = this._pool[value];
883                }else{
884                        if(!(this.nodelist = dd.quickFilter(value))){
885                                this.nodelist = (new dd.Template(value, true)).nodelist;
886                        }
887                        this._pool[value] = this.nodelist;
888                }
889
890                this.contents = "";
891        },
892        {
893                _pool: {},
894                render: function(context, buffer){
895                        var key = this.key;
896                        var value = this.nodelist.dummyRender(context);
897                        if(dd.BOOLS[key]){
898                                value = !(value == "false" || value == "undefined" || !value);
899                        }
900                        if(value !== this.contents){
901                                this.contents = value;
902                                return buffer.setAttribute(key, value);
903                        }
904                        return buffer;
905                },
906                unrender: function(context, buffer){
907                        this.contents = "";
908                        return buffer.remove(this.key);
909                },
910                clone: function(buffer){
911                        return new this.constructor(this.key, this.value);
912                }
913        });
914
915        dd._DomTextNode = lang.extend(function(str){
916                // summary: Adds a straight text node without any processing
917                this.contents = document.createTextNode(str);
918                this.upcoming = str;
919        },
920        {
921                set: function(data){
922                        this.upcoming = data;
923                        return this;
924                },
925                render: function(context, buffer){
926                        if(this.contents.data != this.upcoming){
927                                var old = this.contents.data;
928                                this.contents.data = this.upcoming;
929                                buffer.onChangeData && buffer.onChangeData(this.contents, old, this.upcoming);
930                        }
931                        return buffer.concat(this.contents);
932                },
933                unrender: function(context, buffer){
934                        return buffer.remove(this.contents);
935                },
936                isEmpty: function(){
937                        return !lang.trim(this.contents.data);
938                },
939                clone: function(){
940                        return new this.constructor(this.contents.data);
941                }
942        });
943
944        dd._DomParser = lang.extend(function(tokens){
945                // summary: Turn a simple array into a set of objects
946                // description:
947                //      This is also used by all tags to move through
948                //      the list of nodes.
949                this.contents = tokens;
950        },
951        {
952                i: 0,
953                parse: function(/*Array?*/ stop_at){
954                        var terminators = {};
955                        var tokens = this.contents;
956                        if(!stop_at){
957                                stop_at = [];
958                        }
959                        for(var i = 0; i < stop_at.length; i++){
960                                terminators[stop_at[i]] = true;
961                        }
962                        var nodelist = new dd._DomNodeList();
963                        while(this.i < tokens.length){
964                                var token = tokens[this.i++];
965                                var type = token[0];
966                                var value = token[1];
967                                if(type == dd.TOKEN_CUSTOM){
968                                        nodelist.push(value);
969                                }else if(type == dd.TOKEN_CHANGE){
970                                        var changeNode = new dd.ChangeNode(value, token[2], token[3]);
971                                        value[changeNode.attr] = changeNode;
972                                        nodelist.push(changeNode);
973                                }else if(type == dd.TOKEN_ATTR){
974                                        var fn = ddt.getTag("attr:" + token[2], true);
975                                        if(fn && token[3]){
976                                                if (token[3].indexOf("{%") != -1 || token[3].indexOf("{{") != -1) {
977                                                        value.setAttribute(token[2], "");
978                                                }
979                                                nodelist.push(fn(null, new dd.Token(type, token[2] + " " + token[3])));
980                                        }else if(lang.isString(token[3])){
981                                                if(token[2] == "style" || token[3].indexOf("{%") != -1 || token[3].indexOf("{{") != -1){
982                                                        nodelist.push(new dd.AttributeNode(token[2], token[3]));
983                                                }else if(lang.trim(token[3])){
984                                                        try{
985                                                                html.attr(value, token[2], token[3]);
986                                                        }catch(e){}
987                                                }
988                                        }
989                                }else if(type == dd.TOKEN_NODE){
990                                        var fn = ddt.getTag("node:" + value.tagName.toLowerCase(), true);
991                                        if(fn){
992                                                // TODO: We need to move this to tokenization so that it's before the
993                                                //                              node and the parser can be passed here instead of null
994                                                nodelist.push(fn(null, new dd.Token(type, value), value.tagName.toLowerCase()));
995                                        }
996                                        nodelist.push(new dd._DomNode(value));
997                                }else if(type == dd.TOKEN_VAR){
998                                        nodelist.push(new dd._DomVarNode(value));
999                                }else if(type == dd.TOKEN_TEXT){
1000                                        nodelist.push(new dd._DomTextNode(value.data || value));
1001                                }else if(type == dd.TOKEN_BLOCK){
1002                                        if(terminators[value]){
1003                                                --this.i;
1004                                                return nodelist;
1005                                        }
1006                                        var cmd = value.split(/\s+/g);
1007                                        if(cmd.length){
1008                                                cmd = cmd[0];
1009                                                var fn = ddt.getTag(cmd);
1010                                                if(typeof fn != "function"){
1011                                                        throw new Error("Function not found for " + cmd);
1012                                                }
1013                                                var tpl = fn(this, new dd.Token(type, value));
1014                                                if(tpl){
1015                                                        nodelist.push(tpl);
1016                                                }
1017                                        }
1018                                }
1019                        }
1020
1021                        if(stop_at.length){
1022                                throw new Error("Could not find closing tag(s): " + stop_at.toString());
1023                        }
1024
1025                        return nodelist;
1026                },
1027                next_token: function(){
1028                        // summary: Returns the next token in the list.
1029                        var token = this.contents[this.i++];
1030                        return new dd.Token(token[0], token[1]);
1031                },
1032                delete_first_token: function(){
1033                        this.i++;
1034                },
1035                skip_past: function(endtag){
1036                        return dd._Parser.prototype.skip_past.call(this, endtag);
1037                },
1038                create_variable_node: function(expr){
1039                        return new dd._DomVarNode(expr);
1040                },
1041                create_text_node: function(expr){
1042                        return new dd._DomTextNode(expr || "");
1043                },
1044                getTemplate: function(/*String*/ loc){
1045                        return new dd.DomTemplate(ddh.getTemplate(loc));
1046                }
1047        });
1048        return dojox.dtl.dom;
1049});
Note: See TracBrowser for help on using the repository browser.