1 /** 2 @class 3 4 JsTemplates are used to parse rio templates (*.jst files). JsTemplates are created by rio pages as a starting point 5 for a pages html state. You should rarely have to instantiate this class directly. 6 */ 7 rio.JsTemplate = Class.create({ 8 initialize: function(options) { 9 this._name = options.name || "template"; 10 this._template = options.text.gsub(" ", "\u00a0"); 11 this._path = options.path; 12 }, 13 14 mixinMethods: function() { 15 var template = this; 16 return { 17 buildHtml: function() { 18 return template.render(this); 19 } 20 }; 21 }, 22 23 xml: function() { 24 return '<div style="height: 100%;" class="' + this._name + '" xmlns:rio="http://riojs.com">' + this._template + "</div>"; 25 }, 26 27 parse: function() { 28 return rio.JsTemplate.parse(this._path ? this._template : this.xml()); 29 }, 30 31 render: function(context) { 32 var xml = this.parse(); 33 try { 34 if (this._path) { 35 var nodes = xml.childNodes.item(0).getElementsByTagName(this._path)[0].childNodes; 36 var divNode; 37 var i = 0; 38 while (divNode == undefined) { 39 var node = nodes.item(i); 40 if (node.nodeType == 1) { 41 divNode = node; 42 } 43 i++; 44 } 45 return this.renderElement(node, context); 46 } else { 47 return this.renderElement(xml.childNodes.item(0), context); 48 } 49 } catch(e) { 50 rio.error(e, "Rendering error. Your xml might be malformed.\nRemember to escape these characters [<, \", &] with [<, ", &])"); 51 throw(e); 52 } 53 }, 54 55 renderElement: function(xmlElement, context) { 56 var nodes = xmlElement.childNodes; 57 58 var htmlChildren = []; 59 var i, item; 60 for (i = 0; i < nodes.length; i++) { 61 item = nodes.item(i); 62 63 switch(item.nodeType) { 64 case 1: // Element 65 if (item.nodeName.startsWith("rio:")) { 66 htmlChildren.push(this.renderComponent(item, context).html()); 67 } else { 68 htmlChildren.push(this.renderElement(item, context)); 69 } 70 break; 71 case 3: // Text 72 htmlChildren.push(item.nodeValue); 73 break; 74 } 75 } 76 77 var htmlAttributes = {}; 78 var attributes = xmlElement.attributes; 79 var rioId; 80 for (i = 0; i < attributes.length; i++) { 81 item = attributes.item(i); 82 if (item.nodeName == "rio:id") { 83 rioId = item.nodeValue; 84 } else { 85 htmlAttributes[item.nodeName] = item.nodeValue; 86 } 87 } 88 89 var htmlElement = rio.Tag.build(xmlElement.nodeName, htmlChildren, htmlAttributes); 90 91 if (rioId) { 92 context[("get-" + rioId + "Element").camelize()] = function() { 93 return htmlElement; 94 }; 95 } 96 97 return htmlElement; 98 }, 99 100 renderChildNodes: function(xmlElement, context) { 101 var nodes = xmlElement.childNodes; 102 103 var renderedChildren = []; 104 for (var i = 0; i < nodes.length; i++) { 105 var item = nodes.item(i); 106 107 switch(item.nodeType) { 108 case 1: // Element 109 if (item.nodeName.startsWith("rio:")) { 110 if (item.nodeName == "rio:Object") { 111 renderedChildren.push(this.renderRioObject(item, context)); 112 } else { 113 renderedChildren.push(this.renderComponent(item, context)); 114 } 115 } else { 116 renderedChildren.push(this.renderElement(item, context)); 117 } 118 break; 119 case 3: // Text 120 if (item.nodeValue.strip() != "") { 121 renderedChildren.push(item.nodeValue); 122 } 123 break; 124 } 125 } 126 return renderedChildren.reduce(); 127 }, 128 129 renderRioObject: function(xmlElement, context) { 130 var obj = {}; 131 var nodes = xmlElement.childNodes; 132 for (var i = 0; i < nodes.length; i++) { 133 var item = nodes.item(i); 134 if (item.nodeType == 1) { 135 obj[item.nodeName] = this.renderChildNodes(item, context); 136 } 137 } 138 139 return obj; 140 }, 141 142 renderComponent: function(xmlElement, context) { 143 var componentType = xmlElement.nodeName.match(/rio:(.*)/)[1]; 144 145 146 var componentAttributes = {}; 147 var attributes = xmlElement.attributes; 148 var rioId, i, item; 149 150 for (i = 0; i < attributes.length; i++) { 151 item = attributes.item(i); 152 if (item.nodeName == "rio:id") { 153 rioId = item.nodeValue; 154 } else { 155 var value = item.nodeValue; 156 var executable = value.match(/\{(.*)\}/); 157 if (executable) { 158 rio.pageBinding = context; 159 try { 160 value = eval("(function() { return " + executable[1] + "; }).bind(rio.pageBinding)();"); 161 } catch(e) { 162 rio.error(e, "Error evaluating " + this._name.classize() + " template attribute: " + executable[1]); 163 throw(e); 164 } 165 delete rio.pageBinding; 166 } 167 componentAttributes[item.nodeName] = value; 168 } 169 } 170 171 var nodes = xmlElement.childNodes; 172 for (i = 0; i < nodes.length; i++) { 173 item = nodes.item(i); 174 175 if (item.nodeType == 1) { 176 if (item.nodeName == "rio:id") { 177 rioId = item.nodeValue; 178 } else { 179 componentAttributes[item.nodeName] = this.renderChildNodes(item, context); 180 } 181 } 182 } 183 try { 184 var component = new rio.components[componentType](componentAttributes); 185 } catch(e2) { 186 if (rio.components[componentType] == undefined) { 187 rio.error(e2, componentType + " not found. Add 'components/" + componentType.underscore() + "' to the " + this._name.classize() + " require list."); 188 } 189 throw(e2); 190 } 191 192 if (rioId) { 193 context[("get-" + rioId).camelize()] = function() { 194 return component; 195 }; 196 } 197 198 return component; 199 }, 200 201 name: function() { 202 return this._name; 203 } 204 }); 205 Object.extend(rio.JsTemplate, { 206 build: function(path) { 207 path = rio.boot.appRoot + path; 208 if (this._templatePreloaded(path)) { 209 return new rio.JsTemplate({ 210 name: this._templateNameFromPath(path), 211 text: this._allTemplatesFile, 212 path: path.gsub("/", "--") 213 }); 214 } else { 215 rio.log("missed: " + path); 216 return new rio.JsTemplate({ 217 name: this._templateNameFromPath(path), 218 text: this._templateFile(path) 219 }); 220 } 221 }, 222 223 preload: function(path) { 224 this._allTemplatesFile = this._templateFile(path); 225 this._allTemplatesList = []; 226 227 var doc = this.parse(this._allTemplatesFile); 228 var templateNodes = doc.childNodes.item(0).childNodes; 229 for (var i = 0; i < templateNodes.length; i++) { 230 var item = templateNodes.item(i); 231 if (item.nodeType == 1) { 232 this._allTemplatesList.push(item.nodeName.gsub("--", "/")); 233 } 234 } 235 }, 236 237 parse: function(xml) { 238 var xmlDoc; 239 if (Prototype.Browser.IE) { 240 xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); 241 xmlDoc.async="false"; 242 xmlDoc.loadXML(xml); 243 } else { 244 try { 245 var parser = new DOMParser(); 246 xmlDoc = parser.parseFromString(xml,"text/xml"); 247 } catch(e) { 248 rio.log(e); 249 } 250 } 251 return xmlDoc; 252 }, 253 254 _templatePreloaded: function(path) { 255 return (this._allTemplatesList || []).include(path); 256 }, 257 258 _templateNameFromPath: function(path) { 259 return path.split("/").last().camelize(); 260 }, 261 262 _templateFile: function(path) { 263 return rio.File.open(rio.boot.root + rio.boot.appRoot + path + ".jst", { 264 asynchronous: false, 265 onFailure: function() { 266 rio.Application.fail("Failed loading template - " + path); 267 } 268 }); 269 }, 270 271 toString: function() { 272 return "JsTemplate"; 273 } 274 }); 275 276