mvcjs
Version:
Powerful lightweight mvc framework for nodejs
448 lines (416 loc) • 14.1 kB
JavaScript
"use strict";
var di = require('../di'),
core = di.load('core'),
Type = di.load('typejs'),
ViewInterface = di.load('interface/view'),
ModuleInterface = di.load('interface/module'),
swig = di.load('swig'),
error = di.load('error'),
fs = di.load('fs'),
component = di.load('core/component'),
logger = component.get('core/logger'),
View;
/**
* @license Mit Licence 2014
* @since 0.0.1
* @author Igor Ivanovic
* @name View
*
* @constructor
* @description
* View is responsible for delivering correct view
*/
View = ViewInterface.inherit(
{
swig: Type.OBJECT,
preloaded: Type.OBJECT,
paths: Type.ARRAY,
aliasRegex: Type.REGEX,
normalizers: Type.ARRAY
},
{
_construct: function View_construct(config) {
var defaults;
// extend
this.aliasRegex = /@{.*}/g;
this.preloaded = {};
this.paths = [];
this.normalizers = [];
this.config = core.extend({
cache: false,
autoescape: true,
varControls: ['{{', '}}'],
tagControls: ['{%', '%}'],
cmtControls: ['{#', '#}'],
locals: {},
cacheComponent: false,
views: '@{appPath}/views/',
suffix: '.twig',
extensions: false,
defaultTheme: 'default',
themes: []
}, config);
this.normalizers.push(this.config.views);
di.setAlias('viewsPath', this.config.views);
if (Type.isArray(this.config.themes)) {
if (this.config.themes.indexOf(this.config.defaultTheme) === -1) {
this.config.themes.push(this.config.defaultTheme);
}
this.config.themes.forEach(function (name) {
this.paths.push(this.config.views + name + '/');
}.bind(this));
} else {
throw new error.HttpError(500, this.config, 'View.construct: themes are not array type');
}
this.setModulesViewsPath(di.getAlias('modulesPath'));
if (Type.assert(Type.STRING, this.config.suffix)) {
this.suffix = new RegExp(this.config.suffix + '$');
} else {
throw new error.HttpError(500, this.config, 'View.construct: view suffix must be string type');
}
// create new swig env
this.config.loader = {
resolve: this.resolve.bind(this),
load: this.load.bind(this)
};
defaults = core.extend({}, this.config);
// don't use swig cache!
if (this.config.cache) {
if (this.config.externalTemplateCaching) {
defaults.cache = {
get: this.externalTemplateCaching.get.bind(this),
set: this.externalTemplateCaching.set.bind(this)
};
} else {
defaults.cache = {
get: this.getPreloaded.bind(this),
set: this.setPreloaded.bind(this)
};
}
}
this.swig = new swig.Swig(defaults);
if (this.config.extensions) {
di.load(this.config.extensions)(this, di);
}
if (this.config.cache) {
this.paths.forEach(this.preloadTemplates.bind(this));
}
logger.info('View.construct:', this.config);
},
/**
* @since 0.0.1
* @author Igor Ivanovic
* @method View#setModulesViewsPath
*
* @description
* Set modules path
*/
setModulesViewsPath: function View_setModulesViewsPath(dir) {
var list, name, moduleToLoad, LoadedModule, moduleInstance;
if (this.isDir(dir)) {
list = this.readDir(dir);
while (true) {
name = list.shift();
if (!name) {
break;
}
moduleToLoad = dir + '/' + name;
try {
LoadedModule = di.load(moduleToLoad);
} catch (e) {
throw new error.HttpError(500, {path: moduleToLoad}, 'Missing module', e);
}
if (!Type.assert(Type.FUNCTION, LoadedModule)) {
throw new error.HttpError(500, {path: moduleToLoad}, 'Module must be function type');
}
moduleInstance = new LoadedModule(name);
if (!(moduleInstance instanceof ModuleInterface)) {
throw new error.HttpError(500, moduleInstance, 'Module must be instance of ModuleInterface "core/module"');
}
this.normalizers.push(moduleInstance.getViewsPath());
this.config.themes.forEach(function (name) {
this.paths.push(moduleInstance.getViewsPath() + name + '/');
}.bind(this));
}
}
},
/**
* @since 0.0.1
* @author Igor Ivanovic
* @method View#setPreloaded
*
* @description
* Set preloaded template
*/
setPreloaded: function View_setPreloaded(key, value) {
this.preloaded[key] = value;
},
/**
* @since 0.0.1
* @author Igor Ivanovic
* @method View#getPreloaded
*
* @description
* Get preloaded template
* @return {string|boolean}
*/
getPreloaded: function View_getPreloaded(key) {
if (this.preloaded.hasOwnProperty(key)) {
return this.preloaded[key];
}
return false;
},
/**
* @since 0.0.1
* @author Igor Ivanovic
* @method View#isFile
*
* @description
* Is file
* @return {string|boolean}
*/
isFile: function View_isFile(path) {
try {
path = di.normalizePath(path);
return fs.statSync(path).isFile();
} catch (e) {
logger.warn('View.isFile:', {path: path, e: e});
}
return false;
},
/**
* @since 0.0.1
* @author Igor Ivanovic
* @method View#isDir
*
* @description
* Is directory
* @return {string|boolean}
*/
isDir: function View_isDir(path) {
try {
path = di.normalizePath(path);
return fs.statSync(path).isDirectory();
} catch (e) {
logger.warn('View.isDir:', {path: path, e: e});
}
return false;
},
/**
* @since 0.0.1
* @author Igor Ivanovic
* @method View#readDir
*
* @description
* Read directory
*/
readDir: function View_readDir(path) {
path = di.normalizePath(path);
return fs.readdirSync(path);
},
/**
* @since 0.0.1
* @author Igor Ivanovic
* @method View#preloadTemplates
*
* @description
* Preload all templates at bootstrap
*/
preloadTemplates: function View_preloadTemplates(dir) {
var list, name;
if (this.isDir(dir)) {
list = this.readDir(dir);
while (true) {
name = list.shift();
if (!name) {
break;
}
this.preloadTemplates(di.normalizePath(dir + '/' + name));
}
} else if (this.isFile(dir) && this.suffix.test(dir)) {
process.nextTick(function compileTemplateAsync() {
this.setPreloaded(dir, this.swig.compileFile(dir));
}.bind(this));
}
},
/**
* @since 0.0.1
* @author Igor Ivanovic
* @method View#resolve
*
* @description
* Resolve view
* @return {string}
*/
resolve: function View_resolve(toPath, fromPath) {
var file = di.normalizePath(toPath),
themes = this.config.themes.slice(),
theme,
re,
filePath,
normalizers = this.normalizers.slice(),
isNormalized = false,
path,
pathRegex,
pathReplace,
trace = [];
// file name normalizers
while (normalizers.length) {
path = di.normalizePath(normalizers.shift());
pathReplace = path.replace(/\\/g, '\\\\');
pathRegex = new RegExp(pathReplace);
if (file.match(pathRegex)) {
file = file.replace(pathRegex, '').replace(this.suffix, '');
isNormalized = true;
break;
}
}
// try normalize fromPath
if (!isNormalized && !!fromPath) {
normalizers = this.normalizers.slice();
fromPath = di.normalizePath(fromPath);
// file name normalizers
while (normalizers.length) {
path = di.normalizePath(normalizers.shift());
pathReplace = path.replace(/\\/g, '\\\\');
pathRegex = new RegExp(pathReplace);
if (fromPath.match(pathRegex)) {
isNormalized = true;
break;
}
}
}
// check themes
if (isNormalized) {
while (themes.length) {
theme = di.normalizePath(themes.shift() + '/');
pathReplace = theme.replace(/\\/g, '\\\\');
re = new RegExp('^' + pathReplace);
file = file.replace(re, '');
filePath = di.normalizePath(path + theme + '/' + file + this.config.suffix);
if (this.isFile(filePath)) {
return filePath;
}
trace.push({
theme: theme,
path: path,
filePath: filePath
});
}
}
throw new error.HttpError(500, {
from: fromPath,
load: toPath,
filePath: filePath,
paths: this.paths,
isNormalized: isNormalized,
file: file,
path: path,
trace: trace,
themes: this.config.themes
}, "View.resolve: template don't exists");
},
/**
* @since 0.0.1
* @author Igor Ivanovic
* @method View#load
*
* @description
* Set load view
* @return {string};
*/
load: function View_load(path, cb) {
var template = '';
try {
template = di.readFileSync(path);
} catch (e) {
logger.error('ViewLoader.load:', {
path: path,
cb: cb,
e: e
});
}
return template;
},
/**
* @since 0.0.1
* @author Igor Ivanovic
* @method View#setLoader
*
* @description
* Set loader
*/
setLoader: function View_setLoader(resolve, load) {
this.swig.setDefaults({
loader: {
resolve: resolve,
load: load
}
});
},
/**
* @since 0.0.1
* @author Igor Ivanovic
* @method View#setFilter
*
* @description
* Set swig filter
*/
setFilter: function View_setFilter(name, method) {
this.swig.setFilter(name, method);
},
/**
* @since 0.0.1
* @author Igor Ivanovic
* @method View#setTag
*
* @description
* Set swig tag
*/
setTag: function View_setTag(name, parse, compile, ends, blockLevel) {
this.swig.setTag(name, parse, compile, ends, blockLevel);
},
/**
* @since 0.0.1
* @author Igor Ivanovic
* @method View#setExtension
*
* @description
* Set swig extension
*/
setExtension: function View_setExtension(name, parse, compile, ends, blockLevel) {
this.swig.setExtension(name, parse, compile, ends, blockLevel);
},
/**
* @since 0.0.1
* @author Igor Ivanovic
* @method View#render
*
* @description
* Set swig extension
* @return {string}
*/
render: function View_render(source, locals, escape) {
return this.swig.render(source, {
locals: locals,
autoescape: !escape
});
},
/**
* @since 0.0.1
* @author Igor Ivanovic
* @method View#renderFile
*
* @description
* Render file
* @return {string}
*/
renderFile: function View_renderFile(templateName, locals, viewsPath) {
if (!viewsPath) {
viewsPath = di.getAlias('viewsPath');
}
return this.swig.renderFile(viewsPath + templateName, locals);
}
}
);
module.exports = View;