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