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