From e44a7e37b6c7b5961adaffc62b9042b8d442938e Mon Sep 17 00:00:00 2001 From: mensonge Date: Thu, 13 Nov 2008 09:49:11 +0000 Subject: New feature: basic Ajax suggestion for tags and implementation of Dojo toolkit git-svn-id: https://semanticscuttle.svn.sourceforge.net/svnroot/semanticscuttle/trunk@151 b3834d28-1941-0410-a4f8-b48e95affb8f --- includes/js/dojox/dtl/html.js | 818 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 818 insertions(+) create mode 100644 includes/js/dojox/dtl/html.js (limited to 'includes/js/dojox/dtl/html.js') diff --git a/includes/js/dojox/dtl/html.js b/includes/js/dojox/dtl/html.js new file mode 100644 index 0000000..c984157 --- /dev/null +++ b/includes/js/dojox/dtl/html.js @@ -0,0 +1,818 @@ +if(!dojo._hasResource["dojox.dtl.html"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojox.dtl.html"] = true; +dojo.provide("dojox.dtl.html"); + +dojo.require("dojox.dtl._base"); +dojo.require("dojox.dtl.Context"); + +(function(){ + var dd = dojox.dtl; + + var ddt = dd.text; + var ddh = dd.html = { + types: dojo.mixin({change: -11, attr: -12, custom: -13, elem: 1, text: 3}, ddt.types), + _attributes: {}, + _re4: /^function anonymous\(\)\s*{\s*(.*)\s*}$/, + getTemplate: function(text){ + if(typeof this._commentable == "undefined"){ + // Check to see if the browser can handle comments + this._commentable = false; + var div = document.createElement("div"); + div.innerHTML = ""; + if(div.childNodes.length && div.childNodes[0].nodeType == 8 && div.childNodes[0].data == "comment"){ + this._commentable = true; + } + } + + if(!this._commentable){ + // Strip comments + text = text.replace(//g, "$1"); + } + + var match; + var pairs = [ + [true, "select", "option"], + [dojo.isSafari, "tr", "th"], + [dojo.isSafari, "tr", "td"], + [dojo.isSafari, "thead", "tr", "th"], + [dojo.isSafari, "tbody", "tr", "td"] + ]; + // Some tags can't contain text. So we wrap the text in tags that they can have. + for(var i = 0, pair; pair = pairs[i]; i++){ + if(!pair[0]){ + continue; + } + if(text.indexOf("<" + pair[1]) != -1){ + var selectRe = new RegExp("<" + pair[1] + "[\\s\\S]*?>([\\s\\S]+?)", "ig"); + while(match = selectRe.exec(text)){ + // Do it like this to make sure we don't double-wrap + var found = false; + var tokens = dojox.string.tokenize(match[1], new RegExp("(<" + pair[2] + "[\\s\\S]*?>[\\s\\S]*?)", "ig"), function(child){ found = true; return {data: child}; }); + if(found){ + var replace = []; + for(var j = 0; j < tokens.length; j++) { + if(dojo.isObject(tokens[j])){ + replace.push(tokens[j].data); + }else{ + var close = pair[pair.length - 1]; + var k, replacement = ""; + for(k = 2; k < pair.length - 1; k++){ + replacement += "<" + pair[k] + ">"; + } + replacement += "<" + close + ' iscomment="true">' + dojo.trim(tokens[j]) + ""; + for(k = 2; k < pair.length - 1; k++){ + replacement += ""; + } + replace.push(replacement); + } + } + text = text.replace(match[1], replace.join("")); + } + } + } + } + + var re = /\b([a-zA-Z]+)=['"]/g; + while(match = re.exec(text)){ + this._attributes[match[1].toLowerCase()] = true; + } + var div = document.createElement("div"); + div.innerHTML = text; + var output = {nodes: []}; + while(div.childNodes.length){ + output.nodes.push(div.removeChild(div.childNodes[0])) + } + + return output; + }, + tokenize: function(/*Node*/ nodes){ + var tokens = []; + + for(var i = 0, node; node = nodes[i++];){ + if(node.nodeType != 1){ + this.__tokenize(node, tokens); + }else{ + this._tokenize(node, tokens); + } + } + + return tokens; + }, + _swallowed: [], + _tokenize: function(/*Node*/ node, /*Array*/ tokens){ + var types = this.types; + var first = false; + var swallowed = this._swallowed; + var i, j, tag, child; + + if(!tokens.first){ + // Try to efficiently associate tags that use an attribute to + // remove the node from DOM (eg dojoType) so that we can efficiently + // locate them later in the tokenizing. + first = tokens.first = true; + var tags = dd.register.getAttributeTags(); + for(i = 0; tag = tags[i]; i++){ + try{ + (tag[2])({ swallowNode: function(){ throw 1; }}, ""); + }catch(e){ + swallowed.push(tag); + } + } + } + + + for(i = 0; tag = swallowed[i]; i++){ + var text = node.getAttribute(tag[0]); + if(text){ + var swallowed = false; + var custom = (tag[2])({ swallowNode: function(){ swallowed = true; return node; }}, text); + if(swallowed){ + if(node.parentNode && node.parentNode.removeChild){ + node.parentNode.removeChild(node); + } + tokens.push([types.custom, custom]); + return; + } + } + } + + var children = []; + if(dojo.isIE && node.tagName == "SCRIPT"){ + children.push({ + nodeType: 3, + data: node.text + }); + node.text = ""; + }else{ + for(i = 0; child = node.childNodes[i]; i++){ + children.push(child); + } + } + + tokens.push([types.elem, node]); + + var change = false; + if(children.length){ + // Only do a change request if we need to + tokens.push([types.change, node]); + change = true; + } + + for(var key in this._attributes){ + var value = ""; + if(key == "class"){ + value = node.className || value; + }else if(key == "for"){ + value = node.htmlFor || value; + }else if(key == "value" && node.value == node.innerHTML){ + // Sometimes .value is set the same as the contents of the item (button) + continue; + }else if(node.getAttribute){ + value = node.getAttribute(key, 2) || value; + if(key == "href" || key == "src"){ + if(dojo.isIE){ + var hash = location.href.lastIndexOf(location.hash); + var href = location.href.substring(0, hash).split("/"); + href.pop(); + href = href.join("/") + "/"; + if(value.indexOf(href) == 0){ + value = value.replace(href, ""); + } + value = decodeURIComponent(value); + } + if(value.indexOf("{%") != -1 || value.indexOf("{{") != -1){ + node.setAttribute(key, ""); + } + } + } + if(typeof value == "function"){ + value = value.toString().replace(this._re4, "$1"); + } + + if(!change){ + // Only do a change request if we need to + tokens.push([types.change, node]); + change = true; + } + // We'll have to resolve attributes during parsing + tokens.push([types.attr, node, key, value]); + } + + for(i = 0, child; child = children[i]; i++){ + if(child.nodeType == 1 && child.getAttribute("iscomment")){ + child.parentNode.removeChild(child); + child = { + nodeType: 8, + data: child.innerHTML + }; + } + this.__tokenize(child, tokens); + } + + if(!first && node.parentNode && node.parentNode.tagName){ + if(change){ + tokens.push([types.change, node, true]); + } + tokens.push([types.change, node.parentNode]); + node.parentNode.removeChild(node); + }else{ + // If this node is parentless, it's a base node, so we have to "up" change to itself + // and note that it's a top-level to watch for errors + tokens.push([types.change, node, true, true]); + } + }, + __tokenize: function(child, tokens){ + var types = this.types; + var data = child.data; + switch(child.nodeType){ + case 1: + this._tokenize(child, tokens); + return; + case 3: + if(data.match(/[^\s\n]/) && (data.indexOf("{{") != -1 || data.indexOf("{%") != -1)){ + var texts = ddt.tokenize(data); + for(var j = 0, text; text = texts[j]; j++){ + if(typeof text == "string"){ + tokens.push([types.text, text]); + }else{ + tokens.push(text); + } + } + }else{ + tokens.push([child.nodeType, child]); + } + if(child.parentNode) child.parentNode.removeChild(child); + return; + case 8: + if(data.indexOf("{%") == 0){ + var text = dojo.trim(data.slice(2, -2)); + if(text.substr(0, 5) == "load "){ + var parts = dd.text.pySplit(dojo.trim(text)); + for(var i = 1, part; part = parts[i]; i++){ + dojo["require"](part); + } + } + tokens.push([types.tag, text]); + } + if(data.indexOf("{{") == 0){ + tokens.push([types.varr, dojo.trim(data.slice(2, -2))]); + } + if(child.parentNode) child.parentNode.removeChild(child); + return; + } + } + }; + + dd.HtmlTemplate = dojo.extend(function(/*String|DOMNode|dojo._Url*/ obj){ + // summary: Use this object for HTML templating + if(!obj.nodes){ + var node = dojo.byId(obj); + if(node){ + dojo.forEach(["class", "src", "href", "name", "value"], function(item){ + ddh._attributes[item] = true; + }); + obj = { + nodes: [node] + }; + }else{ + if(typeof obj == "object"){ + obj = ddt.getTemplateString(obj); + } + obj = ddh.getTemplate(obj); + } + } + + var tokens = ddh.tokenize(obj.nodes); + if(dd.tests){ + this.tokens = tokens.slice(0); + } + + var parser = new dd._HtmlParser(tokens); + this.nodelist = parser.parse(); + }, + { + _count: 0, + _re: /\bdojo:([a-zA-Z0-9_]+)\b/g, + setClass: function(str){ + this.getRootNode().className = str; + }, + getRootNode: function(){ + return this.rootNode; + }, + getBuffer: function(){ + return new dd.HtmlBuffer(); + }, + render: function(context, buffer){ + buffer = buffer || this.getBuffer(); + this.rootNode = null; + var output = this.nodelist.render(context || new dd.Context({}), buffer); + this.rootNode = buffer.getRootNode(); + for(var i = 0, node; node = buffer._cache[i]; i++){ + if(node._cache){ + node._cache.length = 0; + } + } + return output; + }, + unrender: function(context, buffer){ + return this.nodelist.unrender(context, buffer); + } + }); + + dd.HtmlBuffer = dojo.extend(function(/*Node*/ parent){ + // summary: Allows the manipulation of DOM + // description: + // Use this to append a child, change the parent, or + // change the attribute of the current node. + this._parent = parent; + this._cache = []; + }, + { + concat: function(/*DOMNode*/ node){ + var parent = this._parent; + if(node.parentNode && node.parentNode.tagName && parent && !parent._dirty){ + return this; + } + + if(node.nodeType == 1 && !this.rootNode){ + this.rootNode = node || true; + } + + if(!parent){ + if(node.nodeType == 3 && dojo.trim(node.data)){ + throw new Error("Text should not exist outside of the root node in template"); + } + return this; + } + if(this._closed && (node.nodeType != 3 || dojo.trim(node.data))){ + throw new Error("Content should not exist outside of the root node in template"); + } + if(parent._dirty){ + if(node._drawn && node.parentNode == parent){ + var caches = parent._cache; + if(caches){ + for(var i = 0, cache; cache = caches[i]; i++){ + this.onAddNode(cache); + parent.insertBefore(cache, node); + this.onAddNodeComplete(cache); + } + caches.length = 0; + } + } + parent._dirty = false; + } + if(!parent._cache){ + parent._cache = []; + this._cache.push(parent); + } + parent._dirty = true; + parent._cache.push(node); + return this; + }, + remove: function(obj){ + if(typeof obj == "string"){ + if(this._parent){ + this._parent.removeAttribute(obj); + } + }else{ + if(obj.nodeType == 1 && !this.getRootNode() && !this._removed){ + this._removed = true; + return this; + } + if(obj.parentNode){ + this.onRemoveNode(); + if(obj.parentNode){ + obj.parentNode.removeChild(obj); + } + } + } + return this; + }, + setAttribute: function(key, value){ + if(key == "class"){ + this._parent.className = value; + }else if(key == "for"){ + this._parent.htmlFor = value; + }else if(this._parent.setAttribute){ + this._parent.setAttribute(key, value); + } + return this; + }, + addEvent: function(context, type, fn, /*Array|Function*/ args){ + if(!context.getThis()){ throw new Error("You must use Context.setObject(instance)"); } + this.onAddEvent(this.getParent(), type, fn); + var resolved = fn; + if(dojo.isArray(args)){ + resolved = function(e){ + this[fn].apply(this, [e].concat(args)); + } + } + return dojo.connect(this.getParent(), type, context.getThis(), resolved); + }, + setParent: function(node, /*Boolean?*/ up, /*Boolean?*/ root){ + if(!this._parent) this._parent = this._first = node; + + if(up && root && node === this._first){ + this._closed = true; + } + + if(up){ + var parent = this._parent; + var script = ""; + var ie = dojo.isIE && parent.tagName == "SCRIPT"; + if(ie){ + parent.text = ""; + } + if(parent._dirty){ + var caches = parent._cache; + for(var i = 0, cache; cache = caches[i]; i++){ + if(cache !== parent){ + this.onAddNode(cache); + if(ie){ + script += cache.data; + }else{ + parent.appendChild(cache); + } + this.onAddNodeComplete(cache); + } + } + caches.length = 0; + parent._dirty = false; + } + if(ie){ + parent.text = script; + } + } + + this.onSetParent(node, up); + this._parent = node; + return this; + }, + getParent: function(){ + return this._parent; + }, + getRootNode: function(){ + return this.rootNode; + }, + onSetParent: function(node, up){ + // summary: Stub called when setParent is used. + }, + onAddNode: function(node){ + // summary: Stub called before new nodes are added + }, + onAddNodeComplete: function(node){ + // summary: Stub called after new nodes are added + }, + onRemoveNode: function(node){ + // summary: Stub called when nodes are removed + }, + onClone: function(/*DOMNode*/ from, /*DOMNode*/ to){ + // summary: Stub called when a node is duplicated + }, + onAddEvent: function(/*DOMNode*/ node, /*String*/ type, /*String*/ description){ + // summary: Stub to call when you're adding an event + } + }); + + dd._HtmlNode = dojo.extend(function(node){ + // summary: Places a node into DOM + this.contents = node; + }, + { + render: function(context, buffer){ + this._rendered = true; + return buffer.concat(this.contents); + }, + unrender: function(context, buffer){ + if(!this._rendered){ + return buffer; + } + this._rendered = false; + return buffer.remove(this.contents); + }, + clone: function(buffer){ + return new this.constructor(this.contents); + } + }); + + dd._HtmlNodeList = dojo.extend(function(/*Node[]*/ nodes){ + // summary: A list of any HTML-specific node object + // description: + // Any object that's used in the constructor or added + // through the push function much implement the + // render, unrender, and clone functions. + this.contents = nodes || []; + }, + { + push: function(node){ + this.contents.push(node); + }, + unshift: function(node){ + this.contents.unshift(node); + }, + render: function(context, buffer, /*Node*/ instance){ + buffer = buffer || dd.HtmlTemplate.prototype.getBuffer(); + + if(instance){ + var parent = buffer.getParent(); + } + for(var i = 0; i < this.contents.length; i++){ + buffer = this.contents[i].render(context, buffer); + if(!buffer) throw new Error("Template node render functions must return their buffer"); + } + if(parent){ + buffer.setParent(parent); + } + return buffer; + }, + dummyRender: function(context, buffer, asNode){ + // summary: A really expensive way of checking to see how a rendering will look. + // Used in the ifchanged tag + var div = document.createElement("div"); + + var parent = buffer.getParent(); + var old = parent._clone; + // Tell the clone system to attach itself to our new div + parent._clone = div; + var nodelist = this.clone(buffer, div); + if(old){ + // Restore state if there was a previous clone + parent._clone = old; + }else{ + // Remove if there was no clone + parent._clone = null; + } + + buffer = dd.HtmlTemplate.prototype.getBuffer(); + nodelist.unshift(new dd.ChangeNode(div)); + nodelist.push(new dd.ChangeNode(div, true)); + nodelist.render(context, buffer); + + if(asNode){ + return buffer.getRootNode(); + } + + var html = div.innerHTML; + return (dojo.isIE) ? html.replace(/\s*_(dirty|clone)="[^"]*"/g, "") : html; + }, + unrender: function(context, buffer){ + for(var i = 0; i < this.contents.length; i++){ + buffer = this.contents[i].unrender(context, buffer); + if(!buffer) throw new Error("Template node render functions must return their buffer"); + } + return buffer; + }, + clone: function(buffer){ + // summary: + // Used to create an identical copy of a NodeList, useful for things like the for tag. + var parent = buffer.getParent(); + var contents = this.contents; + var nodelist = new dd._HtmlNodeList(); + var cloned = []; + for(var i = 0; i < contents.length; i++){ + var clone = contents[i].clone(buffer); + if(clone instanceof dd.ChangeNode || clone instanceof dd._HtmlNode){ + var item = clone.contents._clone; + if(item){ + clone.contents = item; + }else if(parent != clone.contents && clone instanceof dd._HtmlNode){ + var node = clone.contents; + clone.contents = clone.contents.cloneNode(false); + buffer.onClone(node, clone.contents); + cloned.push(node); + node._clone = clone.contents; + } + } + nodelist.push(clone); + } + + for(var i = 0, clone; clone = cloned[i]; i++){ + clone._clone = null; + } + + return nodelist; + } + }); + + dd._HtmlVarNode = dojo.extend(function(str){ + // summary: A node to be processed as a variable + // description: + // Will render an object that supports the render function + // and the getRootNode function + this.contents = new dd._Filter(str); + this._lists = {}; + }, + { + render: function(context, buffer){ + this._rendered = true; + + var str = this.contents.resolve(context); + if(str && str.render && str.getRootNode){ + var root = this._curr = str.getRootNode(); + var lists = this._lists; + var list = lists[root]; + if(!list){ + list = lists[root] = new dd._HtmlNodeList(); + list.push(new dd.ChangeNode(buffer.getParent())); + list.push(new dd._HtmlNode(root)); + list.push(str); + list.push(new dd.ChangeNode(buffer.getParent())); + } + return list.render(context, buffer); + }else{ + if(!this._txt){ + this._txt = document.createTextNode(str); + } + this._txt.data = str; + return buffer.concat(this._txt); + } + }, + unrender: function(context, buffer){ + if(!this._rendered){ + return buffer; + } + this._rendered = false; + if(this._curr){ + return this._lists[this._curr].unrender(context, buffer); + }else if(this._txt){ + return buffer.remove(this._txt); + } + return buffer; + }, + clone: function(){ + return new this.constructor(this.contents.getExpression()); + } + }); + + dd.ChangeNode = dojo.extend(function(node, /*Boolean?*/ up, /*Bookean*/ root){ + // summary: Changes the parent during render/unrender + this.contents = node; + this.up = up; + this.root = root; + }, + { + render: function(context, buffer){ + return buffer.setParent(this.contents, this.up, this.root); + }, + unrender: function(context, buffer){ + if(!this.contents.parentNode){ + return buffer; + } + if(!buffer.getParent()){ + return buffer; + } + return buffer.setParent(this.contents); + }, + clone: function(){ + return new this.constructor(this.contents, this.up, this.root); + } + }); + + dd.AttributeNode = dojo.extend(function(key, value, nodelist){ + // summary: Works on attributes + this.key = key; + this.value = value; + this.nodelist = nodelist || (new dd.Template(value)).nodelist; + + this.contents = ""; + }, + { + render: function(context, buffer){ + var key = this.key; + var value = this.nodelist.dummyRender(context); + if(this._rendered){ + if(value != this.contents){ + this.contents = value; + return buffer.setAttribute(key, value); + } + }else{ + this._rendered = true; + this.contents = value; + return buffer.setAttribute(key, value); + } + return buffer; + }, + unrender: function(context, buffer){ + return buffer.remove(this.key); + }, + clone: function(buffer){ + return new this.constructor(this.key, this.value, this.nodelist.clone(buffer)); + } + }); + + dd._HtmlTextNode = dojo.extend(function(str){ + // summary: Adds a straight text node without any processing + this.contents = document.createTextNode(str); + }, + { + set: function(data){ + this.contents.data = data; + }, + render: function(context, buffer){ + return buffer.concat(this.contents); + }, + unrender: function(context, buffer){ + return buffer.remove(this.contents); + }, + clone: function(){ + return new this.constructor(this.contents.data); + } + }); + + dd._HtmlParser = dojo.extend(function(tokens){ + // summary: Turn a simple array into a set of objects + // description: + // This is also used by all tags to move through + // the list of nodes. + this.contents = tokens; + }, + { + i: 0, + parse: function(/*Array?*/ stop_at){ + var types = ddh.types; + var terminators = {}; + var tokens = this.contents; + if(!stop_at){ + stop_at = []; + } + for(var i = 0; i < stop_at.length; i++){ + terminators[stop_at[i]] = true; + } + var nodelist = new dd._HtmlNodeList(); + while(this.i < tokens.length){ + var token = tokens[this.i++]; + var type = token[0]; + var value = token[1]; + if(type == types.custom){ + nodelist.push(value); + }else if(type == types.change){ + var changeNode = new dd.ChangeNode(value, token[2], token[3]); + value[changeNode.attr] = changeNode; + nodelist.push(changeNode); + }else if(type == types.attr){ + var fn = ddt.getTag("attr:" + token[2], true); + if(fn && token[3]){ + nodelist.push(fn(null, token[2] + " " + token[3])); + }else if(dojo.isString(token[3]) && (token[3].indexOf("{%") != -1 || token[3].indexOf("{{") != -1)){ + nodelist.push(new dd.AttributeNode(token[2], token[3])); + } + }else if(type == types.elem){ + var fn = ddt.getTag("node:" + value.tagName.toLowerCase(), true); + if(fn){ + // TODO: We need to move this to tokenization so that it's before the + // node and the parser can be passed here instead of null + nodelist.push(fn(null, value, value.tagName.toLowerCase())); + } + nodelist.push(new dd._HtmlNode(value)); + }else if(type == types.varr){ + nodelist.push(new dd._HtmlVarNode(value)); + }else if(type == types.text){ + nodelist.push(new dd._HtmlTextNode(value.data || value)); + }else if(type == types.tag){ + if(terminators[value]){ + --this.i; + return nodelist; + } + var cmd = value.split(/\s+/g); + if(cmd.length){ + cmd = cmd[0]; + var fn = ddt.getTag(cmd); + if(typeof fn != "function"){ + throw new Error("Function not found for " + cmd); + } + var tpl = fn(this, value); + if(tpl){ + nodelist.push(tpl); + } + } + } + } + + if(stop_at.length){ + throw new Error("Could not find closing tag(s): " + stop_at.toString()); + } + + return nodelist; + }, + next: function(){ + // summary: Used by tags to discover what token was found + var token = this.contents[this.i++]; + return {type: token[0], text: token[1]}; + }, + skipPast: function(endtag){ + return dd.Parser.prototype.skipPast.call(this, endtag); + }, + getVarNodeConstructor: function(){ + return dd._HtmlVarNode; + }, + getTextNodeConstructor: function(){ + return dd._HtmlTextNode; + }, + getTemplate: function(/*String*/ loc){ + return new dd.HtmlTemplate(ddh.getTemplate(loc)); + } + }); + +})(); + +} -- cgit v1.2.3