Skip to content

Commit

Permalink
Implement client-side page renderer.
Browse files Browse the repository at this point in the history
  • Loading branch information
Denis Rechkunov committed Mar 24, 2014
1 parent 05760fa commit 1364b3e
Show file tree
Hide file tree
Showing 7 changed files with 274 additions and 46 deletions.
55 changes: 43 additions & 12 deletions lib/PageRendererBase.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,17 @@

module.exports = PageRendererBase;

var MODULE_CONTEXT_PREFIX_SEPARATOR = '_';
var MODULE_AND_CONTEXT_SEPARATOR = '_';

/**
* Creates new instance of base page renderer.
* @param {ModuleLoader} $moduleLoader Module loader to get modules.
* @param {Logger} $logger Logger to log messages.
* @constructor
*/
function PageRendererBase($moduleLoader) {
function PageRendererBase($moduleLoader, $logger) {
this._modulesByNames = $moduleLoader.getModulesByNames();
this._logger = $logger;
this._initializeTemplates();
}

Expand All @@ -66,14 +68,11 @@ PageRendererBase.prototype._placeholdersByIds = null;
PageRendererBase.prototype._placeholderIds = null;

/**
* Returns true if page renderer can render this page.
* @param {string} pageName Name of page to render.
* @returns {boolean}
* Current logger.
* @type {Logger}
* @private
*/
PageRendererBase.prototype.canRender = function (pageName) {
return this._modulesByNames.hasOwnProperty(pageName) &&
this._modulesByNames[pageName].rootPlaceholder;
};
PageRendererBase.prototype._logger = null;

/**
* Initializes all templates data structures for fast access.
Expand All @@ -95,12 +94,44 @@ PageRendererBase.prototype._initializeTemplates = function () {
continue;
}

var id = moduleName +
MODULE_CONTEXT_PREFIX_SEPARATOR + placeholderName;
var id = this._joinModuleNameAndContext(moduleName,
placeholderName);

this._placeholderIds.push(id);
this._placeholdersByIds[id] =
currentPlaceholders[placeholderName];
}
}
};
};

/**
* Splits module name and some context (placeholder name, parameter name etc).
* @param {string} str Some string starting on module name.
* @returns {{moduleName: string, context: string}} Object with module name
* and context.
* @protected
*/
PageRendererBase.prototype._splitModuleNameAndContext =
function (str) {
var pair = str.split(MODULE_AND_CONTEXT_SEPARATOR);
if (pair.length !== 2) {
return null;
}

return {
moduleName: pair[0],
context: pair[1]
};
};

/**
* Returns joined module name and any context.
* @param {string} moduleName Name of module.
* @param {string} context Any context (placeholder name, parameter name etc).
* @returns {string}
* @private
*/
PageRendererBase.prototype._joinModuleNameAndContext =
function (moduleName, context) {
return moduleName + MODULE_AND_CONTEXT_SEPARATOR + context;
};
8 changes: 5 additions & 3 deletions lib/RequestRouterBase.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,12 @@ RequestRouterBase.prototype._parseParameters = function (urlText) {
$queryString: urlInfo.search,
$hash: urlInfo.hash
},
parametersByModules = {
globalParameters = {},
additionalParameters = {
$service: serviceParameters,
$global: {}
$global: globalParameters
},
parametersByModules = Object.create(additionalParameters),
currentPair,
currentModuleName,
currentModuleParameterName;
Expand All @@ -96,7 +98,7 @@ RequestRouterBase.prototype._parseParameters = function (urlText) {

if (!parametersByModules.hasOwnProperty(currentModuleName)) {
parametersByModules[currentModuleName] =
Object.create(serviceParameters);
Object.create(additionalParameters);
}

parametersByModules[currentModuleName][currentModuleParameterName] =
Expand Down
2 changes: 1 addition & 1 deletion lib/client/ModuleLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ ModuleLoader.prototype.getModulesByNames = function () {
placeholder.name);

self._templateProvider.registerCompiled(fullName,
placeholder.compiledTemplate);
placeholder.compiledSource);

module.placeholders[placeholder.name] = placeholder;

Expand Down
193 changes: 181 additions & 12 deletions lib/client/PageRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,35 +33,204 @@
module.exports = PageRenderer;

var util = require('util'),
url = require('url'),
PageRendererBase = require('../PageRendererBase');

util.inherits(PageRenderer, PageRendererBase);

var NON_MODULE_NAME_REGEXP = /^\$(.)*/,
TRACE_RENDER_PLACEHOLDER = 'Render placeholder "%s" of module "%s"';

/**
* Creates instance of client-side page renderer.
* @param {ModuleLoader} $moduleLoader Module loader to load modules set.
* @param {Window} $window Browser window to render content.
* @param {Logger} $logger Logger to log messages.
* @constructor
* @extends PageRendererBase
*/
function PageRenderer($moduleLoader) {
PageRendererBase.call(this, $moduleLoader);
function PageRenderer($moduleLoader, $window, $logger) {
PageRendererBase.call(this, $moduleLoader, $logger);
this._window = $window;
}

/**
* Current browser window.
* @type {Window}
* @private
*/
PageRenderer.prototype._window = null;

/**
* Renders changed placeholders at client-side.
* @param {Document} document HTML document where to render.
* @param {Object} parameters Set of parameters.
* @param {Object} parametersByModules Set of parameters.
* @param {Function} next Next function.
*/
PageRenderer.prototype.render = function (document, parameters, next) {
next();
PageRenderer.prototype.render = function (parametersByModules, next) {
var self = this,
queue,
currentModule,
currentModuleName,
rendered = {};

var placeholderCheck = function (placeholderName) {
var id = self._joinModuleNameAndContext(currentModule.name,
placeholderName),
element = self._window.document.getElementById(id);

if (!element) {
return;
}

var item = Object.create(currentModule.placeholders[placeholderName]);
item.element = element;
self._renderTraverse(rendered, parametersByModules, item,
function () {
if (queue.length === 0) {
next();
}
});
};

if (Object.keys(parametersByModules.$global).length > 0) {
// call render for all placeholders which are there in page
queue = Object.keys(this._modulesByNames);
return;
} else {
// call render only for modules with changed parameters
queue = Object.keys(parametersByModules);
}

while (queue.length > 0) {
currentModuleName = queue.shift();
if (NON_MODULE_NAME_REGEXP.test(currentModuleName)) {
continue;
}
currentModule = this._modulesByNames[currentModuleName];

Object.keys(currentModule.placeholders)
.forEach(placeholderCheck);
}
};

/**
* Returns true if page renderer can render this page.
* @param {string} pageName Name of page to render.
* @returns {boolean}
* Does rendering traversal through all placeholders hierarchy.
* @param {Object} rendered Set of rendered placeholders.
* @param {Object} parameters Set of parameters.
* @param {Object} item Placeholder item to render.
* @param {Function} callback Callback on finish.
* @private
*/
PageRenderer.prototype.canRender = function (pageName) {
return true;
};
PageRenderer.prototype._renderTraverse =
function (rendered, parameters, item, callback) {
var queue = [item],
iteration = this._getIterationAction(rendered, queue, parameters,
callback);
iteration();
};

/**
* Gets action function for one traversal iteration.
* @param {Object} rendered Set of rendered placeholders.
* @param {Array} queue Queue of placeholder items to render.
* @param {Object} parameters Set of parameters.
* @param {Function} callback Callback function on finish.
* @returns {Function}
* @private
*/
PageRenderer.prototype._getIterationAction =
function (rendered, queue, parameters, callback) {
var self = this;

// traversal iteration action
var iterationAction = function () {
var currentItem = queue.shift(),
currentId = currentItem.element.id,
currentParameters = parameters[currentItem.moduleName] || {},
currentHtml,
currentModule = self._modulesByNames[currentItem.moduleName];

// html forming end handler
var endHandler = function () {
currentItem.element.innerHTML = currentHtml;
rendered[currentId].html = currentHtml;
// check if placeholder has something to render
// inside itself
self._placeholderIds
.forEach(function (id) {
if (id === currentId) {
return;
}
var placeholderElement =
currentItem.element.querySelector('#' + id);
if (!placeholderElement) {
return;
}

var item =
Object.create(self._placeholdersByIds[id]);
item.element = placeholderElement;
queue.push(item);
});
endCheckAction();
};

// module render data handler
var dataHandler = function (error, data) {
if (error) {
self._logger.error(error);
rendered[currentItem.id] = {
data: null,
html: ''
};
endCheckAction();
return;
}

if (!(currentId in rendered)) {
rendered[currentId] = {
data: data,
html: ''
};
}

currentHtml = rendered[currentId].html || '';

if (currentHtml.length === 0) {
self._logger.trace(util.format(TRACE_RENDER_PLACEHOLDER,
currentItem.name, currentItem.moduleName));

var stream = currentItem.getTemplateStream(data);
stream.on('data', function (chunk) {
currentHtml += chunk;
});
stream.on('end', endHandler);
} else {
endHandler();
}
};

try {
if (currentId in rendered) {
dataHandler(null, rendered[currentId].data);
} else {
currentModule.implementation.render(currentItem.name,
currentParameters, dataHandler);
}
} catch (e) {
self._logger.error(e);
endCheckAction();
}
};

// determines is required to continue process the queue
var endCheckAction = function () {
if (queue.length > 0) {
setTimeout(iterationAction, 0);
} else {
callback();
}
};

return iterationAction;
};

0 comments on commit 1364b3e

Please sign in to comment.