alchemymvc
Version:
MVC framework for Node.js
465 lines (379 loc) • 9.77 kB
JavaScript
/**
* The Controller class
* (on the client side)
*
* @constructor
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 1.0.0
* @version 1.0.0
*/
var Controller = Function.inherits('Alchemy.Client.Base', 'Alchemy.Client.Controller', function Controller(conduit, options) {
this.conduit = conduit;
this.options = options || {};
});
/**
* Map these events to methods
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 1.1.1
* @version 1.1.1
*/
Controller.mapEventToMethod({
initializing : 'onInitialize',
filtering : 'onFilter',
starting : 'beforeAction'
});
/**
* Add action object
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 1.0.0
* @version 1.0.0
*/
Controller.constitute(function addActions() {
if (this.name == 'Controller') {
return;
}
// Creating new actions object layer
this.actions = Object.create(this.super.actions || {});
});
/**
* Add an action
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 1.0.0
* @version 1.0.0
*/
Controller.setStatic(function setAction(name, fnc, on_server) {
var class_name = this.name;
if (typeof name == 'function') {
on_server = fnc;
fnc = name;
name = fnc.name;
}
if (on_server == null) {
on_server = true;
}
this.constitute(function setActionWhenConstituted() {
// Constituters are also applied to child classes,
// but in this case we only want the current class
if (this.name != class_name) {
return;
}
if (Blast.isNode && on_server) {
var ServerClass = this.getServerClass();
if (ServerClass) {
ServerClass.setAction(name, fnc);
} else {
console.warn('Server class not found for', name)
}
}
this.actions[name] = fnc;
});
});
/**
* Object where components are stored
*
* @type {Object}
*/
Controller.prepareProperty('components', Object);
/**
* The controller name
*
* @type {string}
*/
Controller.prepareProperty('name', function name() {
return this.constructor.name;
});
/**
* The model linked to this controller
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 1.0.0
* @version 1.4.0
*
* @type {Alchemy.Model}
*/
Controller.prepareProperty(function model() {
let model_name = this.name,
namespace = this.constructor.namespace;
if (namespace != 'Alchemy.Client.Controller' && namespace != 'Alchemy.Controller') {
if (namespace.startsWith('Alchemy.Client.Controller.')) {
namespace = namespace.slice(25);
} else if (namespace.startsWith('Alchemy.Controller.')) {
namespace = namespace.slice(19);
}
model_name = namespace + '.' + model_name;
}
let instance = this.getModel(model_name);
return instance;
});
/**
* Alias to the viewRender
*
* @type {Hawkejs.ViewRender}
*/
Controller.setProperty(function view_render() {
return this.conduit.view_render;
});
/**
* Enable a component
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 1.0.0
* @version 1.0.0
*
* @param {string} name The name of the component
* @param {Object} options
*/
Controller.setMethod(function addComponent(name, options) {
var underscored,
space;
if (!options) {
options = {};
}
name = name.classify();
if (!Blast.Classes.Alchemy.Client.Component[name]) {
return false;
}
underscored = name.underscore();
this.components[underscored] = new Blast.Classes.Alchemy.Client.Component[name](this, options);
}, false);
/**
* Change the response URL (or disable it)
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 1.3.0
* @version 1.3.0
*
* @param {string|RURL|boolean} new_url
*/
Controller.setMethod(function setResponseUrl(new_url) {
// @TODO: will only work when called on the server-side
if (this.conduit) {
this.conduit.setResponseUrl(new_url);
}
});
/**
* Set a variable for ViewRender, through conduit
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 0.2.0
* @version 0.2.0
*
* @param {string} name
* @param {Mixed} value
*/
Controller.setMethod(function set(name, value) {
if (arguments.length == 1) {
return this.view_render.set(name);
}
return this.view_render.set(name, value);
});
/**
* Set a variable for ViewRender, encode HTML if it's a string
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 0.4.1
* @version 0.4.1
*
* @param {string} name
* @param {Mixed} value
*/
Controller.setMethod(function safeSet(name, value) {
if (arguments.length == 1) {
return this.view_render.set(name);
}
if (typeof value == 'string') {
value = value.encodeHTML();
}
return this.view_render.set(name, value);
});
/**
* Set the page title
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 0.4.1
* @version 0.4.1
*
* @param {string} title
*/
Controller.setMethod(function setTitle(title) {
this.set('pagetitle', title);
this.view_render.set_title(title);
});
/**
* Set an internal variable for ViewRender
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 0.2.0
* @version 1.0.0
*
* @param {string} name
* @param {Mixed} value
*/
Controller.setMethod(function internal(name, value) {
if (arguments.length == 1) {
return this.view_render.internal(name);
}
return this.view_render.internal(name, value);
});
/**
* Expose a variable for ViewRender
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 0.2.0
* @version 1.0.0
*
* @param {string} name
* @param {Mixed} value
*/
Controller.setMethod(function expose(name, value) {
if (arguments.length == 1) {
return this.view_render.expose(name);
}
return this.view_render.expose(name, value);
});
/**
* Render the given template
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 1.0.0
* @version 1.1.0
*
* @param {number} status
* @param {Array} templates
*/
Controller.setMethod(function render(status, templates) {
var that = this,
conduit = this.conduit,
output;
if (templates == null) {
templates = status;
status = conduit.status || 200;
}
templates = this.view_render.add(templates);
Function.parallel(function rendering(next) {
that.emit('rendering', templates, next);
}, function done(err) {
if (err != null) {
throw err;
}
let pledge = hawkejs.scene.handleServerResponse(conduit.url, conduit.options, that.view_render);
pledge.done(conduit.callback);
});
});
/**
* End the response
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 1.1.0
* @version 1.1.0
*
* @param {string|Object} message
*/
Controller.setMethod(function end(message) {
return this.conduit.end(message);
});
/**
* Perform the wanted action and fire expected events
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 0.2.0
* @version 1.4.0
*
* @param {string} name The name of the action to execute
* @param {Array} args Arguments to apply to the action
*/
Controller.setMethod(async function doAction(name, args) {
var that = this;
if (Blast.isNode && this.conduit) {
if (this.conduit.aborted) {
return;
}
let session = this.conduit.getSession(false);
if (session) {
session.action_count++;
}
}
if (this.constructor.actions[name] == null) {
return this.conduit.notFound(new TypeError('Action "' + name + '" was not found on ' + this.constructor.name));
}
if (!this.initial_action_arguments) {
this.initial_action_arguments = args;
}
const route = this.conduit?.route;
if (route) {
if (route.options?.title) {
// @TODO: Add support for objects with language keys
let title = route.options.title;
if (alchemy.settings && alchemy.settings.frontend.title_suffix) {
title += alchemy.settings.frontend.title_suffix;
}
this.setTitle(title);
}
if (route.requires_data_for_translation && route.visible_location !== false && this.conduit.prefix) {
let route_translations = route.getRouteTranslations(this, this.conduit);
if (route_translations) {
route_translations = await route_translations;
this.internal('current_route_translations', route_translations);
}
}
if (route.visible_location !== true) {
this.setResponseUrl(route.visible_location);
}
}
try {
let result = await this.issueEvent('initializing');
if (result === false) {
return;
}
result = await this.issueEvent('filtering');
if (result === false) {
return;
}
result = await this.issueEvent('starting', [name]);
if (result === false) {
return;
}
await this.constructor.actions[name].apply(this, args);
} catch (err) {
this.conduit.error(err);
}
});
/**
* Create client controller class for specific controller name
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 1.0.5
* @version 1.0.5
*
* @param {string} controller_name
*/
Controller.setStatic(function getClass(controller_name) {
var model_constructor,
ControllerClass,
parent_path,
class_name,
class_path,
config,
key;
// Construct the name of the class
class_name = controller_name;
// Construct the path to this class
class_path = 'Alchemy.Client.Controller.' + class_name;
// Get the class
ControllerClass = Object.path(Blast.Classes, class_path);
if (ControllerClass == null) {
model_constructor = Function.create(class_name, function ControllerConstructor(record, options) {
ControllerConstructor.super.call(this, record, options);
});
// @TODO: inherit from parents
parent_path = 'Alchemy.Client.Controller';
ControllerClass = Function.inherits(parent_path, model_constructor);
}
return ControllerClass;
});
// Make this class easily available
Hawkejs.Controller = Controller;