UNPKG

raptor

Version:

RaptorJS provides an AMD module loader that works in Node, Rhino and the web browser. It also includes various sub-modules to support building optimized web applications.

351 lines (309 loc) 14.1 kB
/* * Copyright 2011 eBay Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * This module provides the runtime for rendering compiled templates. * * * <p>The code for the Raptor Templates compiler is kept separately * in the {@link raptor/templating/compiler} module. */ define('raptor/templating', ['raptor'], function(raptor, require, exports, module) { "use strict"; var getRegisteredTemplate = function(name) { return $rget('rhtml', name); }, loadedTemplates = {}, isArray = Array.isArray, createError = raptor.createError, StringBuilder = require('raptor/strings/StringBuilder'), escapeXml = require('raptor/xml/utils').escapeXml, escapeXmlAttr = require('raptor/xml/utils').escapeXmlAttr, renderContext = require('raptor/render-context'), Context = renderContext.Context, _getFunction = Context.classFunc, templating, /** * Helper function to return the singleton instance of a tag handler * * @param name {String} The class name of the tag handler * @returns {Object} The tag handler singleton instance. */ _getHandler = function(name) { var Handler = require(name), //Load the handler class instance; if (Handler.process || Handler.render) { instance = Handler; } else if (!(instance = Handler.instance)) { //See if an instance has already been created instance = Handler.instance = new Handler(); //If not, create and store a new instance } return instance; //Return the handler instance }, /** * Helper function to check if an object is "empty". Different types of objects are handled differently: * 1) null/undefined: Null and undefined objects are considered empty * 2) String: The string is trimmed (starting and trailing whitespace is removed) and if the resulting string is an empty string then it is considered empty * 3) Array: If the length of the array is 0 then the array is considred empty * */ notEmpty = function(o) { if (Array.isArray(o) === true) { return o.length !== 0; } return o; }, helpers = { /** * Helper function to return a static helper function * * @function * @param uri * @param name * @returns {Function} The corresponding helper function. An exception is thrown if the helper function is not found */ h: _getFunction, t: _getHandler, /** * forEach helper function * * @param array {Array} The array to iterate over * @param callback {Function} The callback function to invoke for each iteration * @returns {void} */ fv: function(array, callback) { if (!array) { return; } if (!array.forEach) { array = [array]; } var i=0, len=array.length, //Cache the array size loopStatus = { //The "loop status" object is provided as the second argument to the callback function used for each iteration /** * Returns the length of the array that is being iterated over * @returns {int} The length of the array */ getLength: function() { return len; }, /** * Returns true if the current iteration is the last iteration * @returns {Boolean} True if the current iteration is the last iteration. False, otherwse. */ isLast: function() { return i === len-1; }, isFirst: function() { return i === 0; }, getIndex: function() { return i; } }; for (; i<len; i++) { //Loop over the elements in the array var o = array[i]; callback(o || '', loopStatus); } }, f: raptor.forEach, fl: function(array, func) { if (array != null) { if (!isArray(array)) { array = [array]; } func(array, 0, array.length); } }, fp: function(o, func) { if (!o) { return; } for (var k in o) { if (o.hasOwnProperty(k)) { func(k, o[k]); } } }, e: function(o) { return !notEmpty(o); }, ne: notEmpty, /** * escapeXml helper function * * @param str * @returns */ x: escapeXml, xa: escapeXmlAttr, nx: function(str) { return { toString: function() { return str; } }; } }; templating = { /** * Returns a function that can be used to render the template with the specified name. * * The template function should always be invoked with two arguments: * <ol> * <li><b>data</b> {Object}: The template data object</li> * <li><b>context</b> {@link raptor/templating/Context}: The template context object</li> * </ul> * * @param {String} templateName The name of the template * @return {Function} The function that can be used to render the specified template. */ templateFunc: function(templateName) { /* * We first need to find the template rendering function. It's possible * that the factory function for the template rendering function has been * registered but that the template rendering function has not already * been created. * * The template rendering functions are lazily initialized. */ var templateFunc = loadedTemplates[templateName]; //Look for the template function in the loaded templates lookup if (!templateFunc) { //See if the template has already been loaded /* * If we didn't find the template function in the loaded template lookup * then it means that the template has not been fully loaded and initialized. * Therefore, check if the template has been registerd with the name provided */ templateFunc = getRegisteredTemplate(templateName); if (!templateFunc && this.findTemplate) { this.findTemplate(templateName); templateFunc = getRegisteredTemplate(templateName); } if (templateFunc) { //Check the registered templates lookup to see if a factory function has been register /* * We found that template has been registered so we need to fully initialize it. * To create the template rendering function we must invoke the template factory * function which expects a reference to the static helpers object. * * NOTE: The factory function allows static private variables to be created once * and are then made available to the rendering function as part of the * closure for the rendering function */ var templateInfo = this.getTemplateInfo(templateName); templateFunc = templateFunc(helpers, templateInfo); //Invoke the factory function to get back the rendering function } if (!templateFunc) { throw createError(new Error('Template not found with name "' + templateName + '"')); } loadedTemplates[templateName] = templateFunc; //Store the template rendering function in the lookup } return templateFunc; }, getTemplateInfo: function(templateName) { return { name: templateName }; }, /** * Renders a template to the provided context. * * <p> * The template specified by the templateName parameter must already have been loaded. The data object * is passed to the compiled rendering function of the template. All output is written to the provided * context using the "writer" associated with the context. * * @param templateName The name of the template to render. The template must have been previously rendered * @param data The data object to pass to the template rendering function * @param context The context to use for all rendered output (required) */ render: function(templateName, data, context) { if (!context) { throw createError(new Error("Context is required")); } var templateFunc = this.templateFunc(templateName); try { templateFunc(data || {}, context); //Invoke the template rendering function with the required arguments } catch(e) { throw createError(new Error('Unable to render template with name "' + templateName + '". Exception: ' + e), e); } }, /** * Renders a template and captures the output as a String * * @param templateName {String}The name of the template to render. NOTE: The template must have already been loaded. * @param data {Object} The data object to provide to the template rendering function * @param context {raptor/templating/Context} The context object to use (optional). If a context is provided then the writer will be * temporarily swapped with a StringBuilder to capture the output of rendering. If a context * is not provided then one will be created using the "createContext" method. * @returns {String} The string output of the template */ renderToString: function(templateName, data, context) { var sb = new StringBuilder(); //Create a StringBuilder object to serve as a buffer for the output if (context === undefined) { /* * If a context object is not provided then we need to create a new context object and use the StringBuilder as the writer */ this.render(templateName, data, new Context(sb)); } else { var _this = this; /* * If a context is provided then we need to temporarily swap out the writer for the StringBuilder */ context.swapWriter(sb, function() { _this.render(templateName, data, context); }); //Swap in the writer, render the template and then restore the original writer } return sb.toString(); //Return the final string associated with the StringBuilder }, /** * Unloads a template so that it can be reloaded. * * @param templateName */ unload: function(templateName) { delete loadedTemplates[templateName]; $rset('rhtml', templateName, undefined); }, /** * Helper function to return a helper function * * @function * @param uri * @param name * @returns {Function} The corresponding helper function. An exception is thrown if the helper function is not found */ getFunction: _getFunction, /** * Creates a new context object that can be used as the context for * template rendering. * * @param writer {Object} An object that supports a "write" and a "toString" method. * @returns {templating$Context} The newly created context object */ createContext: renderContext.createContext, getHandler: _getHandler, /** * Helper functions (with short names for minification reasons) */ helpers: helpers }; return templating; });