StackGenVis: Alignment of Data, Algorithms, and Models for Stacking Ensemble Learning Using Performance Metrics
https://doi.org/10.1109/TVCG.2020.3030352
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
354 lines
9.9 KiB
354 lines
9.9 KiB
4 years ago
|
'use strict';
|
||
|
|
||
|
var debug = require('debug')('base:templates:render');
|
||
|
var utils = require('../utils');
|
||
|
|
||
|
module.exports = function(proto) {
|
||
|
debug('loading render methods onto %s, <%s>', proto.constructor.name);
|
||
|
|
||
|
/**
|
||
|
* Set view types for a collection.
|
||
|
*
|
||
|
* @param {String} `plural` e.g. `pages`
|
||
|
* @param {Object} `options`
|
||
|
*/
|
||
|
|
||
|
if (typeof proto.viewType !== 'function') {
|
||
|
proto.viewType = viewType;
|
||
|
}
|
||
|
|
||
|
function viewType(plural, types) {
|
||
|
var len = types.length, i = 0;
|
||
|
while (len--) {
|
||
|
var type = types[i++];
|
||
|
this.viewTypes[type] = this.viewTypes[type] || [];
|
||
|
if (this.viewTypes[type].indexOf(plural) === -1) {
|
||
|
this.viewTypes[type].push(plural);
|
||
|
}
|
||
|
}
|
||
|
return types;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Iterates over `renderable` view collections
|
||
|
* and returns the first view that matches the
|
||
|
* given `view` name
|
||
|
*/
|
||
|
|
||
|
function findView(app, name) {
|
||
|
var keys = app.viewTypes.renderable;
|
||
|
var len = keys.length;
|
||
|
var i = -1;
|
||
|
|
||
|
var res = null;
|
||
|
while (++i < len) {
|
||
|
res = app.find(name, keys[i]);
|
||
|
if (res) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get a view from the specified collection, or list,
|
||
|
* or iterate over view collections until the view
|
||
|
* is found.
|
||
|
*/
|
||
|
|
||
|
function getView(app, view) {
|
||
|
if (typeof view !== 'string') {
|
||
|
return view;
|
||
|
}
|
||
|
if (app.isCollection) {
|
||
|
view = app.getView(view);
|
||
|
|
||
|
} else if (app.isList) {
|
||
|
view = app.getItem(view);
|
||
|
|
||
|
} else {
|
||
|
view = findView(app, view);
|
||
|
}
|
||
|
return view;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Compile `content` with the given `locals`.
|
||
|
*
|
||
|
* ```js
|
||
|
* var indexPage = app.page('some-index-page.hbs');
|
||
|
* var view = app.compile(indexPage);
|
||
|
* // view.fn => [function]
|
||
|
*
|
||
|
* // you can call the compiled function more than once
|
||
|
* // to render the view with different data
|
||
|
* view.fn({title: 'Foo'});
|
||
|
* view.fn({title: 'Bar'});
|
||
|
* view.fn({title: 'Baz'});
|
||
|
* ```
|
||
|
*
|
||
|
* @name .compile
|
||
|
* @param {Object|String} `view` View object.
|
||
|
* @param {Object} `locals`
|
||
|
* @param {Boolean} `isAsync` Load async helpers
|
||
|
* @return {Object} View object with compiled `view.fn` property.
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
proto.compile = function(view, locals, isAsync) {
|
||
|
if (typeof locals === 'boolean') {
|
||
|
isAsync = locals;
|
||
|
locals = {};
|
||
|
}
|
||
|
|
||
|
if (typeof locals === 'function' || typeof isAsync === 'function') {
|
||
|
throw this.formatError('compile', 'callback');
|
||
|
}
|
||
|
|
||
|
locals = utils.extend({settings: {}}, locals);
|
||
|
|
||
|
// if `view` is a string, see if it's a cached view
|
||
|
if (typeof view === 'string') {
|
||
|
view = getView(this, view);
|
||
|
}
|
||
|
|
||
|
// handle `preCompile` middleware
|
||
|
this.handleOnce('preCompile', view);
|
||
|
|
||
|
// determine the name of the engine to use
|
||
|
var ext = utils.resolveEngineExt(view, locals, this.options);
|
||
|
// get the actual engine (object)
|
||
|
var engine = this.getEngine(ext);
|
||
|
|
||
|
// throw if an engine is not defined
|
||
|
if (typeof engine === 'undefined') {
|
||
|
throw this.formatError('compile', 'engine', formatExtError(view, ext));
|
||
|
}
|
||
|
|
||
|
// clean up engine name, for consistency
|
||
|
ext = engine.options.ext;
|
||
|
engine.options.engineName = engine.options.name;
|
||
|
delete engine.options.name;
|
||
|
|
||
|
// get engine options (settings)
|
||
|
var engineOpts = utils.merge({}, locals.settings, engine.options);
|
||
|
var ctx = cacheContext(this, view, locals);
|
||
|
|
||
|
// apply layout
|
||
|
view = this.applyLayout(view);
|
||
|
|
||
|
// Bind context to helpers before passing to the engine.
|
||
|
this.bindHelpers(view, ctx, (ctx.async = !!isAsync));
|
||
|
|
||
|
// shallow clone the context and locals
|
||
|
engineOpts = utils.merge({}, ctx, engineOpts, this.mergePartials(engineOpts));
|
||
|
|
||
|
// compile the string
|
||
|
view.fn = engine.compile(view.content, engineOpts);
|
||
|
|
||
|
// handle `postCompile` middleware
|
||
|
this.handleOnce('postCompile', view);
|
||
|
return view;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Asynchronously compile `content` with the given `locals` and callback. _(fwiw, this
|
||
|
* method name uses the unconventional "*Async" nomenclature to allow us to preserve the
|
||
|
* synchronous behavior of the `view.compile` method as well as the name)_.
|
||
|
*
|
||
|
* ```js
|
||
|
* var indexPage = app.page('some-index-page.hbs');
|
||
|
* app.compileAsync(indexPage, function(err, view) {
|
||
|
* // view.fn => compiled function
|
||
|
* });
|
||
|
* ```
|
||
|
* @name .compileAsync
|
||
|
* @param {Object|String} `view` View object.
|
||
|
* @param {Object} `locals`
|
||
|
* @param {Boolean} `isAsync` Pass true to load helpers as async (mostly used internally)
|
||
|
* @param {Function} `callback` function that exposes `err` and the `view` object with compiled `view.fn` property
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
proto.compileAsync = function(view, locals, isAsync, cb) {
|
||
|
if (typeof locals === 'function') {
|
||
|
return this.compileAsync(view, {}, false, locals);
|
||
|
}
|
||
|
if (typeof isAsync === 'function') {
|
||
|
return this.compileAsync(view, locals, false, isAsync);
|
||
|
}
|
||
|
if (typeof locals === 'boolean') {
|
||
|
return this.compileAsync(view, {}, locals, isAsync);
|
||
|
}
|
||
|
|
||
|
// if `view` is a string, see if it's a cached view
|
||
|
if (typeof view === 'string') {
|
||
|
view = getView(this, view);
|
||
|
}
|
||
|
|
||
|
if (typeof view.engineStack === 'undefined') {
|
||
|
utils.define(view, 'engineStack', {});
|
||
|
}
|
||
|
|
||
|
locals = utils.extend({settings: {}}, locals);
|
||
|
var ctx = this.context(view, locals);
|
||
|
var app = this;
|
||
|
|
||
|
// handle `preCompile` middleware
|
||
|
this.handleOnce('preCompile', view, function(err, file) {
|
||
|
if (err) return cb(err);
|
||
|
|
||
|
// determine the name of the engine to use
|
||
|
var ext = utils.resolveEngineExt(file, ctx, app.options);
|
||
|
|
||
|
// get the actual engine (object)
|
||
|
var engine = app.getEngine(ext);
|
||
|
|
||
|
if (typeof engine === 'undefined') {
|
||
|
cb(app.formatError('compile', 'engine', formatExtError(file, ext)));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
ext = engine.options.ext;
|
||
|
engine.options.engineName = engine.options.name;
|
||
|
delete engine.options.name;
|
||
|
|
||
|
// get engine options (settings)
|
||
|
var engineOpts = utils.merge({}, locals.settings, engine.options);
|
||
|
|
||
|
// apply layout
|
||
|
app.applyLayoutAsync(file, function(err, view) {
|
||
|
if (err) return cb(err);
|
||
|
|
||
|
// Bind context to helpers before passing to the engine.
|
||
|
app.bindHelpers(view, ctx, (ctx.async = !!isAsync));
|
||
|
|
||
|
app.mergePartialsAsync(engineOpts, function(err, partials) {
|
||
|
if (err) return cb(err);
|
||
|
|
||
|
// shallow clone the context and engineOpts
|
||
|
var mergedSettings = utils.merge({}, engineOpts, ctx, partials);
|
||
|
|
||
|
var content = view.content;
|
||
|
if (view.engineStack.hasOwnProperty(ext)) {
|
||
|
content = view.engineStack[ext].content;
|
||
|
}
|
||
|
|
||
|
// compile the string
|
||
|
view.fn = engine.compile(content, mergedSettings);
|
||
|
utils.engineStack(view, ext, view.fn, content);
|
||
|
|
||
|
// handle `postCompile` middleware
|
||
|
app.handleOnce('postCompile', view, cb);
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Render a view with the given `locals` and `callback`.
|
||
|
*
|
||
|
* ```js
|
||
|
* var blogPost = app.post.getView('2015-09-01-foo-bar');
|
||
|
* app.render(blogPost, {title: 'Foo'}, function(err, view) {
|
||
|
* // `view` is an object with a rendered `content` property
|
||
|
* });
|
||
|
* ```
|
||
|
* @name .render
|
||
|
* @param {Object|String} `view` Instance of `View`
|
||
|
* @param {Object} `locals` Locals to pass to template engine.
|
||
|
* @param {Function} `callback`
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
proto.render = function(view, locals, cb) {
|
||
|
if (typeof locals === 'function') {
|
||
|
cb = locals;
|
||
|
locals = {};
|
||
|
}
|
||
|
|
||
|
if (typeof cb !== 'function') {
|
||
|
throw this.formatError('render', 'callback');
|
||
|
}
|
||
|
|
||
|
// if `view` is a string, see if it's a cached view
|
||
|
if (typeof view === 'string') {
|
||
|
view = getView(this, view);
|
||
|
}
|
||
|
|
||
|
if (typeof view.localsStack === 'undefined') {
|
||
|
utils.define(view, 'localsStack', []);
|
||
|
}
|
||
|
|
||
|
var ctx = cacheContext(this, view, locals);
|
||
|
var app = this;
|
||
|
|
||
|
// handle `preRender` middleware
|
||
|
this.handleOnce('preRender', view, function(err, file) {
|
||
|
if (err) return cb(err);
|
||
|
|
||
|
// get the engine
|
||
|
var ext = utils.resolveEngineExt(file, ctx, app.options);
|
||
|
var engine = app.getEngine(ext);
|
||
|
|
||
|
if (!engine) {
|
||
|
cb(app.formatError('render', 'engine', formatExtError(file, ext)));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// since we already merged context, add it to a property
|
||
|
// so it can be used by `compile`
|
||
|
if (app.options.cacheContext === true) {
|
||
|
file._context = ctx;
|
||
|
}
|
||
|
|
||
|
// compile the view
|
||
|
app.compileAsync(file, ctx, true, function(err, view) {
|
||
|
if (err) return cb(err);
|
||
|
|
||
|
// build the context one more time in case it has changed
|
||
|
var context = cacheContext(app, file, ctx);
|
||
|
|
||
|
// render the view
|
||
|
engine.render(view.fn, context, function(err, res) {
|
||
|
if (err) {
|
||
|
// rethrow is a noop if `options.rethrow` is not true
|
||
|
var renderErr = app.rethrow('render', err, view, context);
|
||
|
if (app.hasListeners('error')) {
|
||
|
app.emit('error', renderErr || err);
|
||
|
}
|
||
|
cb(renderErr || err);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
view.localsStack.push(locals);
|
||
|
view.content = res;
|
||
|
delete view._context;
|
||
|
|
||
|
// handle `postRender` middleware
|
||
|
app.handleOnce('postRender', view, cb);
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
};
|
||
|
};
|
||
|
|
||
|
function formatExtError(view, ext) {
|
||
|
if (ext && typeof ext === 'string' && ext.trim()) {
|
||
|
return ext;
|
||
|
}
|
||
|
return view.basename;
|
||
|
}
|
||
|
|
||
|
function cacheContext(app, file, context) {
|
||
|
if (typeof app.options.cacheContext === 'function') {
|
||
|
file._context = app.options.cacheContext(file, context, app);
|
||
|
} else if (app.options.cacheContext === true && typeof file.context === 'function') {
|
||
|
return (file._context || file.context(context));
|
||
|
} else if (typeof file.context === 'function') {
|
||
|
return file.context(app.cache.data, context);
|
||
|
} else {
|
||
|
return context;
|
||
|
}
|
||
|
}
|