source: Dev/trunk/src/client/dojox/dtl/dom.js @ 529

Last change on this file since 529 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

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