UNPKG

mmir-lib

Version:

MMIR (Mobile Multimodal Interaction and Relay) library

1,122 lines (920 loc) 39.3 kB
define (['mmirf/commonUtils','mmirf/languageManager','mmirf/controllerManager','mmirf/presentationManager','mmirf/parserModule','mmirf/viewConstants', 'mmirf/logger', 'module' ], /** * A Utility class for rendering parsed (eHTML) templates, or more specifically ParsingResult objects.<br> * * @example mmir.parser.RenderUtils.render(parseResult, contentElementList); * * @class RenderUtils * @name mmir.parser.RenderUtils * @export RenderUtils as mmir.parser.RenderUtils * @public * @static * @hideconstructor * */ function ( commonUtils, languageManager, controllerManager, presentationManager, parser, ViewConstants, Logger, module ) { /** * Object containing the instance of the class RenderUtils * * @type RenderUtils * * @private * @memberOf mmir.parser.RenderUtils# */ var instance = null; /** * the logger for the RenderUtils * * @type mmir.tools.Logger * * @private * @memberOf mmir.parser.RenderUtils# */ var logger = Logger.create(module); //internal "constants" for the RENDERING mode /** * @private * @memberOf mmir.parser.RenderUtils# */ var RENDER_MODE_LAYOUT = 0; /** * @private * @memberOf mmir.parser.RenderUtils# */ var RENDER_MODE_PARTIAL = 2; /** * @private * @memberOf mmir.parser.RenderUtils# */ var RENDER_MODE_VIEW_CONTENT = 4; /** * @private * @memberOf mmir.parser.RenderUtils# */ var RENDER_MODE_VIEW_DIALOGS = 8; /** * @private * @memberOf mmir.parser.RenderUtils# */ var RENDER_MODE_JS_SOURCE = 16; /** * @private * @memberOf mmir.parser.RenderUtils# */ var RENDER_MODE_JS_SOURCE_FORCE_VAR_PREFIX = 32; /** * @private * @memberOf mmir.parser.RenderUtils# */ var DATA_NAME = parser.element.DATA_NAME; /** * @private * @memberOf mmir.parser.RenderUtils# */ var PARAM_DATA_NAME = parser.element.DATA_ARGUMENT_NAME; /** * @private * @memberOf mmir.parser.RenderUtils# */ var PARAM_ARGS_NAME = parser.element.ARGUMENT_ARGUMENT_NAME; /** * HELPER for detecting if an object is an Array * * @function * * @private * @memberOf mmir.parser.RenderUtils# * * @see mmir.CommonUtils#isArray */ var isArray = commonUtils.isArray; /** * helper for sorting an Arrays. * * Notes: * 1. all array elements must have a function {Number} getStart() * 2. the array will be sorted ascending by getStart(), e.g. sort by occurrence in the raw template-text * * Usage example: * <code> * theArray.sort(sortAscByStart); * </code> * * @private * @memberOf mmir.parser.RenderUtils# */ var sortAscByStart=function(parsedElem1, parsedElem2){ return parsedElem1.getStart() - parsedElem2.getStart(); }; /** * Constructor-Method of Singleton mmir.parser.RenderUtils * * @private * @ignore * * @memberOf mmir.parser.RenderUtils# */ function constructor(){ //private members. /** * @type mmir.LanguageManager * @name localizer * @private * @memberOf mmir.parser.RenderUtils# */ var localizer = languageManager; /** * Prepares the layout: * * after loading a layout file, this methods prepares the layout * for rendering content into it * (i.e. "prepare layout definition for later view-renderings"). * * * NOTE: this does not actually render the layout for "viewing" * (see renderContent(..))! * * @param {ParsingResult} result the parsing result for the layout string * @param {Array<mmir.view.ContentElement>} contentForArray (usually this would be NULL for pre-rendering layouts) * @param {Number} renderingMode the rendering mode for layouts * @returns {String} the (pre-) rendered layout * * * @private * @memberOf mmir.parser.RenderUtils# * @name renderLayoutImpl */ function renderLayout(result, contentForArray, renderingMode) { //TODO need to enable dynamic elements for this LAYOUT-rendering // (e.g. for 'calculating' variables that can be used as @yield-arguments ... should vars for this be disabled?) //create list of all template-expressions var all = result.scripts.concat( result.styles//[DISABLED: only process script- and style-tags at this stage], result.yields, result.localizations ); //sort list by occurrence: all.sort(sortAscByStart); var renderResult = new Array(); var pos = 1; for(var i=0, size = all.length; i < size; ++i){ var scriptElem = all[i]; //render the "static" content, beginning from the // lastly rendered "dynamic" element up to the start // of the current "dynamic" element: renderResult.push(result.rawTemplateText.substring(pos-1, scriptElem.getStart())); //render the current "dynamic" element: renderElement(scriptElem, contentForArray, renderingMode, result.rawTemplateText, renderResult); //set position-marker for "static" content after entry position // of current "dynamic" element: pos = scriptElem.getEnd() + 1; //alert('Replacing \n"'+rawTemplateText.substring(scriptElem.getStart(), scriptElem.getEnd())+'" with \n"'+content+'"'); } if(pos - 1 < result.rawTemplateText.length){ renderResult.push(result.rawTemplateText.substring(pos - 1)); } return renderResult.join(''); } /** * Prepares JavaScript source code for usage in rendering the template (view/partial etc.). * * The replacement-list contains information which parts of the raw JavaScript code should be * modified (e.g. indices [start,end] for replacing text in the source code). * * The function returns the modified JavaScript source code as a String. * * * If the mode is <code>RENDER_MODE_JS_SOURCE_FORCE_VAR_PREFIX</code>, the variable-names that correspond * to replacementObjectsList are check: if a name does not start with @, then the name will prepended with @ before * rendering. * * @private * @memberOf mmir.parser.RenderUtils# * @name renderJSSourceImpl */ function renderJSSource(rawJSSourceCode, replacementObjectsList, renderingMode) { if(!replacementObjectsList || replacementObjectsList.length < 1){ return rawJSSourceCode; //////////////////////////// EARLY EXIT ////////////////////////// } var all = replacementObjectsList; //sort list by occurrence: all.sort(sortAscByStart); var renderResult = new Array(); var pos = 1; for(var i=0, size = all.length; i < size; ++i){ var scriptElem = all[i]; //render the "static" content, beginning from the // lastly rendered "dynamic" element up to the start // of the current "dynamic" element: renderResult.push(rawJSSourceCode.substring(pos-1, scriptElem.getStart())); //render the current "dynamic" element: renderElement(scriptElem, null, renderingMode, rawJSSourceCode, renderResult); //set position-marker for "static" content after entry position // of current "dynamic" element: pos = scriptElem.getEnd() + 1; //alert('Replacing \n"'+rawTemplateText.substring(scriptElem.getStart(), scriptElem.getEnd())+'" with \n"'+content+'"'); } if(pos - 1 < rawJSSourceCode.length){ renderResult.push(rawJSSourceCode.substring(pos - 1)); } return renderResult.join(''); } /** * Render a View * * Renders the contents into a layout definition (i.e. "render for viewing"). * * @param {String} htmlContentString the "raw" content string that was parsed * @param {Array<mmir.view.YieldDeclaration>} yieldDeclarationsArray a list of yield-declarations for the parsed htmlContentString * @param {Array<mmir.view.ContentElement>} contentForObjectsArray a list of content-for objects for the parsed htmlContentString. This list must supply a corresponding object for each entry in the <tt>yieldDeclarationsArray</tt>. * @param {Number} renderingMode the render mode * @param {Object} data the rendering data * @returns {String} the evaluated and rendered view-content * * * @private * @function * @memberOf mmir.parser.RenderUtils# * @name renderContentImpl */ function renderContent(htmlContentString, yieldDeclarationsArray, contentForArray, renderingMode, data) { yieldDeclarationsArray.sort(sortAscByStart); var renderResult = new Array(); var pos = 1; for(var i=0, size = yieldDeclarationsArray.length; i < size; ++i){ var yieldDeclaration = yieldDeclarationsArray[i]; if( (renderingMode === RENDER_MODE_VIEW_CONTENT && yieldDeclaration.getAreaType() !== ViewConstants.CONTENT_AREA_BODY) || (renderingMode === RENDER_MODE_VIEW_DIALOGS && yieldDeclaration.getAreaType() !== ViewConstants.CONTENT_AREA_DIALOGS) ){ continue; } //render the "static" content, beginning from the // lastly rendered "dynamic" element up to the start // of the current "dynamic" element: renderResult.push(htmlContentString.substring(pos-1, yieldDeclaration.getStart())); //render the current "dynamic" element: renderYield(yieldDeclaration, contentForArray, renderingMode, htmlContentString, renderResult, data); //set position-marker for "static" content after entry position // of current "dynamic" element: pos = yieldDeclaration.getEnd() + 1; } if(pos - 1 < htmlContentString.length){ renderResult.push(htmlContentString.substring(pos - 1)); } return renderResult.join(''); } /** * Renders a ContentElement object into the renderingBuffer. * * * @param {mmir.view.ContentElement} contentElement * the ContentElement object that should be rendered * @param {Array} renderingBuffer * of Strings (if <code>null</code> a new buffer will be created) * @param {Object} data * the data/arguments/variables object; * the event data with which the rendering was invoked is accessible via <DATA_NAME>[<PARAM_DATA_NAME>] * * @param {Array<mmir.view.ContentElement>} [contentForObjectsArray] OPTIONAL * for rendering layouts, i.e. when YieldDeclarations are contained in the contentElement.allContentElements fields: * a list of content-for objects for the parsed htmlContentString. This list must supply a corresponding object for each entry in the <tt>yieldDeclarationsArray</tt>. * @returns {Array} * a list of Strings the renderingBuffer where the contents of this object are added at the end of the Array * * @private * @memberOf mmir.parser.RenderUtils# * @name renderContentElementImpl */ function renderContentElement(contentElement, renderingBuffer, data, contentForObjectsArray){ //create "buffer" if necessary: var renderResult = getRenderingBuffer(renderingBuffer); //initialize the contentElement with the current rendering-data object: contentElement.setRenderData(data); contentForObjectsArray = contentForObjectsArray || null; var pos = 1; //iterate over elements, and render them into the "buffer": for(var i=0, size = contentElement.allContentElements.length; i < size; ++i){ var childContentElement = contentElement.allContentElements[i]; //render the "static" content, beginning from the // lastly rendered "dynamic" element up to the start // of the current "dynamic" element: renderResult.push(contentElement.definition.substring(pos-1, childContentElement.getStart())); if(!contentForObjectsArray && childContentElement.isYield()){ logger.e('encountered mmir.view.YieldDeclaration, but now ContentFor list was supplied'); } else { //render the current "dynamic" element: renderElement( childContentElement, contentForObjectsArray,//<- contentForArray: must be specified, if not used (only for LAYOUTS) RENDER_MODE_VIEW_CONTENT,//<- renderingMode: render as normal view (i.e. generate all replacements) contentElement.getRawText(), renderResult, data, contentElement ); } //set position-marker for "static" content after entry position // of current "dynamic" element: pos = childContentElement.getEnd() + 1; //alert('Replacing \n"'+rawTemplateText.substring(childContentElement.getStart(), childContentElement.getEnd())+'" with \n"'+content+'"'); } //append the last part, i.e. if there is some template-text after the last element: if(pos - 1 < contentElement.definition.length){ if(pos === 1){ renderResult.push(contentElement.definition); } else { renderResult.push(contentElement.definition.substring(pos-1)); } } return renderResult; } /** * HELPER creates a new rendering buffer if neccessary * @returns {Array} rendering buffer * * @private * @memberOf mmir.parser.RenderUtils# */ function getRenderingBuffer(renderingBuffer){ if(renderingBuffer)// && isArray(renderingBuffer)) return renderingBuffer; return new Array(); } /** * @private * @memberOf mmir.parser.RenderUtils# */ function renderElement(elem, contentForArray, renderingMode, rawTemplateText, renderingBuffer, data, /*optional: */ containingContentElement) { var type = elem.type; if(type === parser.element.INCLUDE_SCRIPT){ return renderIncludeScript(elem, renderingMode, rawTemplateText, renderingBuffer, data); } else if(type === parser.element.INCLUDE_STYLE){ return renderIncludeStyle(elem, renderingMode, rawTemplateText, renderingBuffer, data); } else if(type === parser.element.LOCALIZE){ return renderLocalize(elem, renderingMode, rawTemplateText, renderingBuffer, data); } else if(type === parser.element.YIELD_DECLARATION){ return renderYield(elem, contentForArray, renderingMode, rawTemplateText, renderingBuffer, data); } else if(type === parser.element.ESCAPE_ENTER){ return renderEscape(elem, renderingMode, rawTemplateText, renderingBuffer); } else if(type === parser.element.ESCAPE_EXIT){ return renderEscape(elem, renderingMode, rawTemplateText, renderingBuffer); } else if(type === parser.element.COMMENT){ return renderComment(elem, renderingMode, rawTemplateText, renderingBuffer, data); } else if(type === parser.element.HELPER){ return renderHelper(elem, renderingMode, rawTemplateText, renderingBuffer, data, containingContentElement); } else if(type === parser.element.YIELD_CONTENT){ //ignore: this should not be rendered itself, but instead its content should be rendered // in for the corresponding yield-declaration element. logger.warn('ParseUtil.renderElement: encountered YIELD_CONTENT for '+elem.name+' -> this sould be handled by renderYieldDeclaration!'); } else if(type === parser.element.IF){ return renderIf(elem, renderingMode, rawTemplateText, renderingBuffer, data, containingContentElement); } else if(type === parser.element.FOR){ return renderFor(elem, renderingMode, rawTemplateText, renderingBuffer, data, containingContentElement); } else if(type === parser.element.RENDER){ return renderPartial(elem, renderingMode, rawTemplateText, renderingBuffer, data, containingContentElement); } else if(type === parser.element.BLOCK){ return renderScriptBlock(elem, renderingMode, rawTemplateText, renderingBuffer, data); } else if(type === parser.element.STATEMENT){ return renderScriptStatement(elem, renderingMode, rawTemplateText, renderingBuffer, data); } else if(type === parser.element.VAR_DECLARATION){ return renderVarDeclaration(elem, renderingMode, rawTemplateText, renderingBuffer, data); } else if(type === parser.element.VAR_REFERENCE){ return renderVarReference(elem, renderingMode, rawTemplateText, renderingBuffer, data); } else { logger.error('ParseUtil.renderElement: unknown element type -> '+type); return null; } } /** * @private * @memberOf mmir.parser.RenderUtils# */ function renderRaw(elem, renderingMode, rawTemplateText, renderingBuffer){ renderingBuffer = getRenderingBuffer(renderingBuffer); renderingBuffer.push( rawTemplateText.substring(elem.getStart(), elem.getEnd()) ); return renderingBuffer; } /** * @private * @memberOf mmir.parser.RenderUtils# */ function renderIncludeScript(elem, renderingMode, rawTemplateText, renderingBuffer, data){ renderingBuffer = getRenderingBuffer(renderingBuffer); renderingBuffer.push('<script type="text/javascript" charset="utf-8" src="'); renderingBuffer.push( elem.getValue(elem.scriptPath, elem.scriptPathType, data) ); renderingBuffer.push('.js"></script>'); return renderingBuffer; } /** * @private * @memberOf mmir.parser.RenderUtils# */ function renderIncludeStyle(elem, renderingMode, rawTemplateText, renderingBuffer, data){ renderingBuffer = getRenderingBuffer(renderingBuffer); renderingBuffer.push('<link rel="stylesheet" type="text/css" href="'); if(parser.stylesRoot){ renderingBuffer.push(parser.stylesRoot); } renderingBuffer.push( elem.getValue(elem.stylePath, elem.stylePathType, data) ); renderingBuffer.push('.css" />'); return renderingBuffer; } /** * @private * @memberOf mmir.parser.RenderUtils# */ function renderLocalize(elem, renderingMode, rawTemplateText, renderingBuffer, data){ renderingBuffer = getRenderingBuffer(renderingBuffer); if(RENDER_MODE_LAYOUT === renderingMode){ return renderRaw(elem, renderingMode, rawTemplateText, renderingBuffer); } var name = elem.getValue(elem.name, elem.nameType, data); var text = localizer.getText(name); if(!text){ logger.warn('RenderUtils.renderLocalize: could not find localization text for "'+elem.name+'"'); } else{ renderingBuffer.push(text); } return renderingBuffer; } /** * @private * @memberOf mmir.parser.RenderUtils# */ function renderHelper(elem, renderingMode, rawTemplateText, renderingBuffer, data, containingContentElement){ renderingBuffer = getRenderingBuffer(renderingBuffer); if(RENDER_MODE_LAYOUT === renderingMode){ return renderRaw(elem, renderingMode, rawTemplateText, renderingBuffer); } var name = elem.getValue(elem.helper, elem.helperType, data); //set arguments, if helper-statement was given a data-argument: var prevArgs = null; if(typeof elem.argsEval !== 'undefined'){ //TODO handle scope & collisions more elaborately? if(typeof data[PARAM_ARGS_NAME] !== 'undefined'){ prevArgs = data[PARAM_ARGS_NAME]; } data[PARAM_ARGS_NAME] = elem.argsEval(data); } var text = containingContentElement.getController().performHelper(name, data[PARAM_DATA_NAME], data[PARAM_ARGS_NAME]); //clean-up: handle scope for ARGS delete data[PARAM_ARGS_NAME]; if(prevArgs !== null){ data[PARAM_ARGS_NAME] = prevArgs; } if(typeof text !== 'string'){ logger.debug('RenderUtils.renderHelper: not a STRING result for '+containingContentElement.getController().getName()+'::Helper.'+name+'(), but '+(typeof text)); text = text === null || typeof text === 'undefined'? '' + text : text.toString(); } //TODO HTML escape for toString before pushing the result (?) renderingBuffer.push(text); return renderingBuffer; } /** * @private * @memberOf mmir.parser.RenderUtils# */ function renderPartial(elem, renderingMode, rawTemplateText, renderingBuffer, data, containingContentElement){ renderingBuffer = getRenderingBuffer(renderingBuffer); if(RENDER_MODE_LAYOUT === renderingMode){ return renderRaw(elem, renderingMode, rawTemplateText, renderingBuffer); } var partialName = elem.getValue(elem.partial, elem.partialType, data); //set arguments, if render-statement was given a data-argument: var prevArgs = null; if(typeof elem.argsEval !== 'undefined'){ //TODO handle scope & collisions more elaborately? if(typeof data[PARAM_ARGS_NAME] !== 'undefined'){ prevArgs = data[PARAM_ARGS_NAME]; } data[PARAM_ARGS_NAME] = elem.argsEval(data); } //get the Controller object: var ctrlName = elem.getValue(elem.controller, elem.controllerType, data); var ctrl; //check if we already have the controller: if(containingContentElement.getController() && containingContentElement.getController().getName() == ctrlName){ ctrl = containingContentElement.getController(); } else { //...if not: retrieve controller ctrl = controllerManager.getController(ctrlName); } //TODO (?) move getPartial-method from PresentationManager (i.e. remove dependency here)? //NOTE previously, there was a dependency cycle: upon loading of templateRendererUtils.js, the presentationManager was not yet loaded. // This should not happen anymore, but just to be save, load the presentationManager, if it is not available yet if(!presentationManager){ presentationManager = require('mmirf/presentationManager'); } var partial = presentationManager.getPartial(ctrl, partialName); if(!partial){ logger.warn('RenderUtils.renderPartial: no partial for controller '+containingContentElement.getController().getName()+', with name >'+partialName+'<'); } else { renderContentElement(partial.getContentElement(), renderingBuffer, data); } //clean-up: handle scope for ARGS delete data[PARAM_ARGS_NAME]; if(prevArgs !== null){ data[PARAM_ARGS_NAME] = prevArgs; } return renderingBuffer; } /** * @private * @memberOf mmir.parser.RenderUtils# */ function renderIf(elem, renderingMode, rawTemplateText, renderingBuffer, data, containingContentElement){ renderingBuffer = getRenderingBuffer(renderingBuffer); if(RENDER_MODE_LAYOUT === renderingMode){ return renderRaw(elem, renderingMode, rawTemplateText, renderingBuffer); } var evalCondResult = elem.ifEval(data); if(evalCondResult) { renderContentElement(elem.content, renderingBuffer, data); } else if(elem.elseContent) { renderContentElement(elem.elseContent.content, renderingBuffer, data); } return renderingBuffer; } /** * @private * @memberOf mmir.parser.RenderUtils# */ function renderFor(elem, renderingMode, rawTemplateText, renderingBuffer, data, containingContentElement){ renderingBuffer = getRenderingBuffer(renderingBuffer); if(RENDER_MODE_LAYOUT === renderingMode){ return renderRaw(elem, renderingMode, rawTemplateText, renderingBuffer); } if(elem.forControlType === 'FORITER'){ //FOR-type: for(prop in obj)... //get iterator for prop-values: var it = elem.forIterator(data); var current; while( it.hasNext() ){ current = it.next(); //make the prop-name available in inner FOR-block through the data-object: data[elem.forPropName] = current; try{ //render inner FOR-content: renderContentElement(elem.content, renderingBuffer, data); } catch(err){ //FIXME experimental mechanism for BREAK within @for // (need to add syntax for this: @break) //simulate BREAK statement: if(err == 'break'){//FIXME use internal/private element for this! (add @break to syntax?) break; } else { //if it is an error: re-throw it throw err; } } } } else { //FOR-type: for(var i=0; i < size; ++i)... elem.forInitEval(data); while( elem.forConditionEval(data) ){ try{ //render inner FOR-content: renderContentElement(elem.content, renderingBuffer, data); } catch(err){ //simulate BREAK statement: if(err == 'break'){//FIXME use internal/private element for this! (add @break to syntax?) break; } else { //if it is an error: re-throw it throw err; } } //execute INCREMENT of FOR-statement: elem.forIncrementEval(data); } } return renderingBuffer; } /** * @private * @memberOf mmir.parser.RenderUtils# */ function renderScriptBlock(elem, renderingMode, rawTemplateText, renderingBuffer, data, containingContentElement){ renderingBuffer = getRenderingBuffer(renderingBuffer); if(RENDER_MODE_LAYOUT === renderingMode){ return renderRaw(elem, renderingMode, rawTemplateText, renderingBuffer); } evaluate(elem.scriptContent, data, elem, containingContentElement); //return unchanged renderingBuffer return renderingBuffer; } /** * @private * @memberOf mmir.parser.RenderUtils# */ function renderScriptStatement(elem, renderingMode, rawTemplateText, renderingBuffer, data, containingContentElement){ renderingBuffer = getRenderingBuffer(renderingBuffer); if(RENDER_MODE_LAYOUT === renderingMode){ return renderRaw(elem, renderingMode, rawTemplateText, renderingBuffer); } var result = evaluate(elem.scriptContent, data, elem, containingContentElement); //TODO escape rendered string (?) renderingBuffer.push(result); return renderingBuffer; } /** * @private * @memberOf mmir.parser.RenderUtils# */ function renderVarDeclaration(elem, renderingMode, rawTemplateText, renderingBuffer, data, containingContentElement){ renderingBuffer = getRenderingBuffer(renderingBuffer); if(RENDER_MODE_LAYOUT === renderingMode){ return renderRaw(elem, renderingMode, rawTemplateText, renderingBuffer); } //NOTE all template-vars start with special char @ // var fieldName = '@'+ elem.getValue(elem.name, elem.nameType, data); var varName = elem.getValue(elem.name, elem.nameType);//FIXME: do not invoke with data; we only want the VAR-name!//, data); var fieldName = '@'+ varName; //initialize field for var-declaration if(typeof data[fieldName] === 'undefined'){ data[fieldName] = null; //TODO impl. structures/mechanisms for deleting/removing the var on exiting // the scope of the corresponding ContentElement // ... with special case when field already existed before: // (1) delete/remove only on the outer most scope exit // (2) on 'inner' exits we need to restore the value that the variable had // when we entered the scope (i.e. when we "overwrote" the existing var // with an inner local var) // --> (a) we need to store the value here when var already exists // (b) on parsing JS code we need to consider var-declarations, i.e. @-variables // that are proceeded with a 'var'-statement, e.g.: 'var @varName = ...' } // TODO handle case when field already exists (?) //return unchanged renderingBuffer return renderingBuffer; } /** * @private * @memberOf mmir.parser.RenderUtils# */ function renderVarReference(elem, renderingMode, rawTemplateText, renderingBuffer, data, containingContentElement){ renderingBuffer = getRenderingBuffer(renderingBuffer); if(RENDER_MODE_LAYOUT === renderingMode){ return renderRaw(elem, renderingMode, rawTemplateText, renderingBuffer); } var varName = rawTemplateText.substring(elem.getStart(),elem.getEnd()); //handle "import"/"export" of call/args-data if(varName === PARAM_DATA_NAME || varName === PARAM_ARGS_NAME){ //TODO should there be a check included -> for var-existance? // --> currently: on assignment, if var does not exists, it will be created // --> change (?), so that there is a check first, and if var does not exists an ReferenceError is thrown (?) renderingBuffer.push(DATA_NAME); renderingBuffer.push('["'); if(renderingMode === RENDER_MODE_JS_SOURCE_FORCE_VAR_PREFIX){ //ensure that the replacement variable-name starts with an @: if( ! varName[0] === '@'){ varName = '@' + varName; } } //extract var-name from original source-code (NOTE this var must start with @) renderingBuffer.push(varName); renderingBuffer.push('"]'); } else { //render variable (without leading @ symbol) renderingBuffer.push(varName[0] === '@'? varName.substring(1) : varName); } return renderingBuffer; } /** * @private * @memberOf mmir.parser.RenderUtils# */ function renderEscape(elem, renderingMode, rawTemplateText, renderingBuffer){ renderingBuffer = getRenderingBuffer(renderingBuffer); if(RENDER_MODE_LAYOUT === renderingMode){ return renderRaw(elem, renderingMode, rawTemplateText); } renderingBuffer.push( elem.text); return renderingBuffer; } /** * @private * @memberOf mmir.parser.RenderUtils# */ function renderComment(elem, renderingMode, rawTemplateText, renderingBuffer){ //render comment: omit comment text from rendering! // renderingBuffer = getRenderingBuffer(renderingBuffer); // // if(RENDER_MODE_LAYOUT === renderingMode){ // return renderRaw(elem, renderingMode, rawTemplateText); // } // var comment = rawTemplateText.substring(elem.getStart()+2,elem.getEnd()-2); // renderingBuffer.push( comment ); return renderingBuffer; } /** * @private * @memberOf mmir.parser.RenderUtils# */ function getContentForYield(name, contentForArray){ for(var i=0, size = contentForArray.length; i < size; ++i){ if(name === contentForArray[i].getName()){ return contentForArray[i]; } } return null; } /** * @private * @memberOf mmir.parser.RenderUtils# */ function renderYield(elem, contentForArray, renderingMode, rawTemplateText, renderingBuffer, data){ renderingBuffer = getRenderingBuffer(renderingBuffer); if(RENDER_MODE_LAYOUT === renderingMode){ return renderRaw(elem, renderingMode, rawTemplateText); } else { var name = elem.getValue(elem.name, elem.nameType, data); var contentFor = getContentForYield(name, contentForArray); if(!contentFor){ logger.info('ParseUtil.renderYield: could not find content-definition for yield '+name); return renderingBuffer; } if(contentFor.hasDynamicContent()){ return renderContentElement(contentFor, renderingBuffer, data); } else { renderingBuffer.push(contentFor.getRawText()); return renderingBuffer; } // return contentFor.toHtml(); } } /** * @private * @memberOf mmir.parser.RenderUtils# */ function evaluate(evalStatement, data, element, containingContentElement){ var result = element.scriptEval(data); return result; } /** * HELPER for creating the data-object * @private * @memberOf mmir.parser.RenderUtils# */ function createInternalData(eventData){ //create DATA object that contains (or will be filled with) // 1. event data (in PARAM_DATA_NAME) // TODO 2. arguments (for template expressions that support arguments; in ARGS_NAME) // 3. declared template variables (under the name of their declaration) // TODO 4. variables in template script-blocks/script-statements that were not declared (for avoiding global var declarations when evaluating these script fragements) var dataArgs = new Object(); dataArgs[PARAM_DATA_NAME] = eventData; return dataArgs; } /** @lends mmir.parser.RenderUtils.prototype */ return { //public members: /** * Renders a layout in preparation for displaying content: * This function should be used to preperare the layout content, so that its * views can be rendered into it (needs to be done only once, after the layout is loaded). * * @param {mmir.parser.ParsingResult} parseResult the parsed view template * @param {mmir.view.ContentElement[]} [contentForArray] * @returns {String} the prepared layout content * * @public * @memberOf mmir.parser.RenderUtils.prototype */ renderLayout: function(parseResult, contentForArray){ return renderLayout(parseResult, contentForArray, RENDER_MODE_LAYOUT); }, /** * Renders a view. * * <p>During rendering, the view's template-expressions are evaluated, and the results rendered into * the returned String. * * @param {String|mmir.view.ContentElement} htmlContentString * the original view-content of the layout-template text, see {@link mmir.view.Layout#getBodyContents} * or a ContentElement with its YieldDeclarations in its allContentElements field (by default yields are not contained in ContentElement.allContentElements) * @param {Array<mmir.view.YieldDeclaration>} yieldDeclarationsArray * a list of yield-declarations of the layout * @param {Array<mmir.view.ContentElement>} contentForObjectsArray * a list of content-for objects of the view. This list must supply a corresponding objecet for each entry in the <tt>yieldDeclarationsArray</tt>. * @param {Object} [data] OPTIONAL * a JSON object which's fields will be available during rendering/evaluation of the template expressions * @returns {String} the evaluated and rendered view-content * * @public * @memberOf mmir.parser.RenderUtils.prototype */ renderViewContent: function(htmlContentString, yieldDeclarationsArray, contentForObjectsArray, data){ var dataArgs = createInternalData(data); if(typeof htmlContentString === 'string'){ return renderContent(htmlContentString, yieldDeclarationsArray, contentForObjectsArray, RENDER_MODE_VIEW_CONTENT, dataArgs); } else { return renderContentElement( htmlContentString, null,//<- yields should be in htmlContentString.allContentElements dataArgs, contentForObjectsArray ).join(''); } }, /** * Renders a single {@link mmir.view.ContentElement} object. * * <p>During rendering, the view's template-expressions are evaluated, and the results rendered into * the returned String. * * @param {mmir.view.ContentElement} contentElement the ContentElement object that should be rendered * @param {Object} [data] a JSON object which's fields will be available during rendering/evaluation of the template expressions * @param {Array<String>} [renderingBuffer] if provided, the partial rendering results will be appended to this Array * @returns {String} the evaluated and rendered ContentElement; if <tt>renderingBuffer</tt> was provided and not empty, the result will be prepended with the concatenated contents of the Array's Strings * * @public * @memberOf mmir.parser.RenderUtils.prototype */ renderContentElement: function(contentElement, data, renderingBuffer/*optional*/){ var dataArgs = createInternalData(data); return renderContentElement(contentElement, renderingBuffer, dataArgs); }, /** * Renders the dialog content for a view. * * <p>During rendering, the view's template-expressions are evaluated, and the results rendered into * the returned String. * * @param {String} htmlContentString the original dialog-content of the layout-template text, see {@link mmir.view.Layout#getDialogsContents} * @param mmir.view.YieldDeclaration[]} yieldDeclarationsArray a list of yield-declarations of the layout * @param {mmir.view.ContentElement[]} contentForObjectsArray a list of content-for objects of the view. This list must supply a corresponding objecet for each entry in the <tt>yieldDeclarationsArray</tt>. * @param {Object} [data] a JSON object which's fields will be available during rendering/evaluation of the template expressions * @returns {String} the evaluated and rendered dialog-content * * @public * @memberOf mmir.parser.RenderUtils.prototype */ renderViewDialogs: function(htmlContentString, yieldDeclarationsArray, contentForObjectsArray, data){ var dataArgs = createInternalData(data); return renderContent(htmlContentString, yieldDeclarationsArray, contentForObjectsArray, RENDER_MODE_VIEW_DIALOGS, dataArgs); }, /** * Prepares JavaScript source code for usage in rendering the template (view/partial etc.). * * The replacement-list contains information which parts of the raw JavaScript code should be * modified (e.g. indices [start,end] for replacing text in the source code). * * The function returns the modified JavaScript source code as a String. * * * If the mode is <code>isForcePrefix == true</code>, the variable-names that correspond * to replacementObjectsList are check: if a name does not start with @, then the name will prepended with @ before * rendering. * * @param {String} rawJSSourceCode the original JavaScript source code * @param {mmir.parser.ParsingResult[]} replacementObjectsList * @param {Boolean} [isForcePrefix] * * @public * @memberOf mmir.parser.RenderUtils.prototype */ renderJS: function(rawJSSourceCode, replacementObjectsList, isForcePrefix){ var mode = isForcePrefix? RENDER_MODE_JS_SOURCE_FORCE_VAR_PREFIX : RENDER_MODE_JS_SOURCE; return renderJSSource(rawJSSourceCode, replacementObjectsList, mode); } };//END: return{} }//END: constructor() instance = new constructor(); //FIXME should the renderer be exported to parser.RenderUtils here? parser.RenderUtils = instance; return instance; });//END: define(..., function(){ //}( this.mmir = this.mmir || {} ));