1 /* 2 * Geddy JavaScript Web development framework 3 * Copyright 2112 Matthew Eernisse (mde@fleegix.org) 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 var errors = require('../../../errors') 20 , TemplaterBase = require('../../templater_base').TemplaterBase 21 , EventEmitter = require('events').EventEmitter 22 , TemplateNode = require('./template_node').TemplateNode; 23 24 /** 25 * EJS templater constructor 26 * @contstructor 27 */ 28 var Templater = function () { 29 this.currentPartialId = 0; 30 this.baseTemplateNode = undefined; 31 this.templateRoot = undefined; 32 this.isLayout = false; 33 }; 34 35 // Inherit from TemplaterBase 36 Templater.prototype = new TemplaterBase(); 37 38 // Override the TempaterBase render method 39 Templater.prototype.render = function (data, config) { 40 41 if (config.layout) { 42 43 this.isLayout = true; 44 this.templateRoot = getDirname(config.layout); 45 46 47 var _this = this; 48 var templaterContent = new Templater(); 49 var contentPartial = ''; 50 51 templaterContent.addListener('data', function (d) { 52 // Buffer for now, but could stream 53 contentPartial += d; 54 }); 55 56 templaterContent.addListener('end', function () { 57 data.yield = function () { return contentPartial; }; 58 _this.partial(getFilename(config.layout), data); 59 }); 60 61 templaterContent.render(data, {template: config.template}); 62 } 63 64 else { 65 // Set the base path to look for template partials 66 this.templateRoot = getDirname(config.template); 67 filename = getFilename(config.template); 68 this.partial(filename, data); 69 } 70 }; 71 72 var getFilename = function (path) { 73 return path.split('/').pop(); 74 }; 75 76 var getDirname = function (path) { 77 var arr = path.split('/'); 78 arr.pop(); 79 return arr.join('/'); 80 }; 81 82 var getTemplateUrl = function (templateRoot, partialUrl, parentNode, isLayout) { 83 var key 84 , templateUrl 85 , dirs = [] 86 , dir 87 , err; 88 89 // If this is a sub-template, try in the same directory as the the parent 90 if (parentNode) { 91 dirs.push(parentNode.dirname); 92 } 93 94 // Or look in the specified templateRoot 95 dirs.push(templateRoot); 96 97 // Look through the directory list until you find a registered 98 // template path -- these are registered during app init so we're 99 // not touching the filesystem every time to look for partials 100 for (var i = 0, ii = dirs.length; i < ii; i++) { 101 dir = dirs[i]; 102 key = dir + '/' + partialUrl + '.html.ejs'; 103 if (geddy.templateRegistry[key]) { 104 templateUrl = key; 105 break; 106 } 107 } 108 109 // No template 110 if (!templateUrl) { 111 // If it's a layout, use the default one for the app 112 if (isLayout) { 113 templateUrl = 'app/views/layouts/application.html.ejs'; 114 } 115 // Bail out if a normal content template 116 else { 117 err = new errors.InternalServerError('Partial template "' + 118 partialUrl + '" not found in ' + dirs.join(", ")); 119 throw err; 120 } 121 } 122 123 return templateUrl; 124 }; 125 126 Templater.prototype.partial = function (partialUrl, renderContext, parentNode) { 127 128 var _this = this, 129 node, 130 partialId = this.currentPartialId, 131 isBaseNode = !this.baseTemplateNode, 132 templateUrl; 133 134 templateUrl = getTemplateUrl(this.templateRoot, partialUrl, parentNode, this.isLayout); 135 136 // Create the current node, with a reference to its parent, if any 137 node = new TemplateNode(partialId, templateUrl, renderContext, parentNode); 138 139 // Curry the partial method to use the current node as the 140 // parent in subsequent recursive calls 141 renderContext.partial = function (partUrl, ctxt) { 142 return _this.partial.call(_this, partUrl, ctxt, node); 143 }; 144 145 // If there is a parent, add this node as its child 146 if (parentNode) { 147 parentNode.childNodes[partialId] = node; 148 } 149 150 // If this is the base node (i.e., there's no baseTemplateNode yet), 151 // give this node the finishRoot method that actually renders the final, 152 // completed content for the entire template 153 if (isBaseNode) { 154 node.finishRoot = function () { 155 156 _this.emit('data', _this.baseTemplateNode.content); 157 _this.emit('end'); 158 159 } 160 this.baseTemplateNode = node; 161 // Kick off the hierarchical async loading process 162 node.loadTemplate(); 163 } 164 165 // Increment the current partial id for the next call 166 this.currentPartialId++; 167 168 // Return the placeholder text to represent this template -- it gets 169 // replaced in the callback from the async load of the actual content 170 return '###partial###' + partialId; 171 }; 172 173 174 exports.Templater = Templater; 175