alchemymvc
Version:
MVC framework for Node.js
568 lines (460 loc) • 11.2 kB
JavaScript
/**
* The Conduit class
* (on the client side)
*
* @constructor
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 1.0.0
* @version 1.1.3
*/
var Conduit = Function.inherits('Alchemy.Client.Base', 'Alchemy.Client.Conduit', function Conduit(url, options) {
// Reference to ourselves
this.conduit = this;
// Open url options
this.options = options || {};
// The original requested url
this.url = url;
// New cookies
this.new_cookies = {};
// The headers to "send"
this.response_headers = {};
// Parameter overrides
this.param_overrides = {};
});
/**
* Handle a local url
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 1.1.0
* @version 1.2.2
*
* @param {RURL} url
* @param {Object} options
*
* @return {Pledge}
*/
Conduit.setStatic(function handleUrlLocal(url, options) {
var section_name,
path_prefix,
section,
conduit,
prefix = '',
routes,
pledge,
tasks,
route,
path,
key;
if (typeof url == 'string') {
url = RURL.parse(url);
}
if (!options) {
options = {};
}
path = url.pathname;
pledge = new Blast.Classes.Pledge();
for (key in hawkejs.scene.exposed.prefixes) {
path_prefix = '/' + key + '/';
if (path.startsWith(path_prefix)) {
prefix = key;
path = '/' + path.after(path_prefix);
break;
}
}
routes = hawkejs.scene.getAlchemyRoutes();
tasks = [];
// @TODO: paths should have the same order as on the server
for (section_name in routes) {
section = routes[section_name];
Object.each(section, function eachRoute(route, section_name) {
var definition = route.compiled_paths[prefix];
if (!definition) {
return;
}
tasks.push(function testDefinition(next) {
var temp = definition.test(path, conduit);
if (!temp) {
return next();
}
if (temp.then) {
return temp.then(function resolved(value) {
if (!value) {
return next(null);
}
doRoute(route, value, next);
}).catch(function rejected(err) {
return next(err);
});
}
doRoute(route, temp, next);
});
});
}
if (tasks.length) {
conduit = new Conduit(url, options);
}
if (conduit) {
for (let key in options) {
let info = Blast.Classes.Develry.Request.getMethodInfo(key);
if (info && info.method != 'GET') {
conduit.method = info.method.toLowerCase();
break;
}
}
if (!conduit.method) {
conduit.method = 'get';
}
}
Function.series(tasks, function done(err) {
if (err) {
return pledge.reject(err);
}
pledge.reject('No matching client-side route found');
});
function doRoute(route, value, next) {
var controller;
if (!route.controller || !route.action) {
return next();
}
controller = Blast.Classes.Alchemy.Client.Controller[route.controller];
if (!controller || !controller.actions[route.action]) {
return next();
}
// Set the breadcrumb
conduit.internal('breadcrumb', route.breadcrumb);
// Put conduit back on top
value.unshift(conduit);
// Create the controller instance
controller = new controller(conduit);
// See if a render root element is given
if (options.root) {
controller.renderer.root = options.root;
}
// Add the callback
conduit.callback = function callback(err, result) {
if (err) {
pledge.reject(err);
}
pledge.resolve(result);
};
// Call the action
try {
let result = controller.constructor.actions[route.action].apply(controller, value);
if (result && result.then) {
result.catch(conduit.callback);
}
} catch (err) {
pledge.reject(err);
}
}
return pledge;
}, false);
/**
* When this response was sent
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 1.1.0
* @version 1.1.0
*/
Conduit.setProperty('ended', false);
/**
* The "body" property
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 1.0.0
* @version 1.0.0
*/
Conduit.setProperty(function body() {
var opts = this.options,
data = opts.post || opts.put || opts.options || opts.patch || opts.body;
if (!data) {
data = opts.body = {};
}
return data;
}, false);
/**
* The "view_render" property
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 1.0.0
* @version 1.1.0
*/
Conduit.setProperty(function view_render() {
return this.renderer;
}, false);
/**
* The "renderer" property
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 1.1.0
* @version 1.1.5
*/
Conduit.setProperty(function renderer() {
if (!this._renderer) {
if (this.parent && this.parent != this && this.parent.renderer) {
this._renderer = this.parent.renderer.createSubRenderer();
} else {
this._renderer = hawkejs.createRenderer();
}
// Pass url parameters to the client
this._renderer.internal('url', this.url);
if (Blast.isBrowser) {
this._renderer.language = hawkejs.scene.exposed.active_prefix;
}
}
return this._renderer;
}, false);
/**
* Throw an error when a conduit is checksummed
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 1.1.0
* @version 1.1.0
*/
Conduit.setMethod(Blast.checksumSymbol, function toChecksum() {
throw new Error('Conduit instances can not be checksummed');
});
/**
* Get a parameter from the route, post or get query or cookie
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 0.2.0
* @version 1.0.0
*
* @param {string} name
* @param {Mixed} value Overrides the value if set
*/
Conduit.setMethod(function param(name, value) {
var length = arguments.length;
if (length == 0) {
return Object.assign({}, this.params, this.url.query, this.body);
} else if (length == 2) {
if (!this.param_overrides) {
this.param_overrides = {};
}
this.param_overrides[name] = value;
return;
}
if (this.param_overrides && this.param_overrides[name] != null) {
return this.param_overrides[name];
}
if (this.body && this.body[name] != null) {
return this.body[name];
}
if (this.url && this.url.query && this.url.query[name] != null) {
return this.url.query[name];
}
if (this.params && this.params[name] != null) {
return this.params[name];
}
return this.cookie(name);
});
/**
* Set a variable for ViewRender
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 0.2.0
* @version 0.2.0
*
* @param {string} name
* @param {Mixed} value
*/
Conduit.setMethod(function set(name, value) {
if (arguments.length == 1) {
return this.renderer.set(name);
}
return this.renderer.set(name, value);
});
/**
* Set an internal variable for ViewRender
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 0.2.0
* @version 0.2.0
*
* @param {string} name
* @param {Mixed} value
*/
Conduit.setMethod(function internal(name, value) {
if (arguments.length == 1) {
return this.renderer.internal(name);
}
return this.renderer.internal(name, value);
});
/**
* Expose a variable for ViewRender
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 0.2.0
* @version 0.2.0
*
* @param {string} name
* @param {Mixed} value
*/
Conduit.setMethod(function expose(name, value) {
if (arguments.length == 1) {
return this.renderer.expose(name);
}
return this.renderer.expose(name, value);
});
/**
* Get the best locale to use for this connection
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 1.0.0
* @version 1.0.0
*
* @param {Array} locales Locales to choose from
*
* @return {string}
*/
Conduit.setMethod(function chooseBestLocale(locales) {
if (!locales || !locales.length) {
return this.locales[0] || 'en';
}
for (let i = 0; i < locales.length; i++) {
if (this.locales.indexOf(locales[i]) > -1) {
return locales[i];
}
}
return 'en';
});
/**
* Check if this request has a permission
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 1.2.5
* @version 1.2.5
*
* @param {string} permission
*
* @return {boolean} True if the user has the permission, false otherwise
*/
Conduit.setMethod(function hasPermission(permission) {
if (alchemy.permission_checker != null) {
return alchemy.permission_checker.conduitHasPermission(this, permission);
}
return false;
});
if (Blast.isNode) {
return;
}
/**
* End the call
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 1.0.0
* @version 1.1.0
*
* @param {string} name
* @param {Mixed} value Overrides the value if set
*/
Conduit.setMethod(function end(message) {
this.ended = new Date();
if (this.callback) {
return this.callback(null, message);
}
});
/**
* Get/set cookies
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 1.0.0
* @version 1.0.0
*
* @param {string} name
* @param {Mixed} value Overrides the value if set
*/
Conduit.setMethod(function cookie(name, value) {
return hawkejs.scene.cookie.apply(hawkejs.scene, arguments);
});
/**
* Throw an error
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 1.0.0
* @version 1.0.0
*
* @param {number} status Response statuscode
* @param {Error} message Optional error to send
*/
Conduit.setMethod(function error(status, message) {
if (typeof status != 'number') {
message = status;
}
throw new Error(message);
});
/**
* Local redirect
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 1.1.0
* @version 1.1.0
*
* @param {number} status 3xx redirection codes. 302 (temporary redirect) by default
* @param {string|Object} options Options or url
*/
Conduit.setMethod(async function redirect(status, options) {
var temp,
url;
if (typeof status != 'number') {
options = status;
status = 302;
}
// Let's just do it like this for now
url = options;
try {
await Conduit.handleUrlLocal(url);
} catch (err) {
// Fallback to an openUrl request
return alchemy.openUrl(url);
}
});
/**
* Get the alchemy routes, compile paths
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 1.0.0
* @version 1.0.0
*
* @return {Object}
*/
Hawkejs.Scene.setMethod(function getAlchemyRoutes() {
var exposed_routes = hawkejs.scene.exposed.routes,
section_name,
section,
prefix,
route,
name;
if (this._alchemy_routes && this._alchemy_routes == exposed_routes) {
return exposed_routes;
}
for (section_name in exposed_routes) {
section = exposed_routes[section_name];
for (name in section) {
route = section[name];
route.compiled_paths = {};
for (prefix in route.paths) {
route.compiled_paths[prefix] = new Blast.Classes.Alchemy.PathDefinition(route.paths[prefix], {end: true});
}
}
}
this._alchemy_routes = exposed_routes;
return exposed_routes;
});
/**
* Intercept Scene#openUrl calls
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 1.0.0
* @version 1.1.0
*
* @param {RURL} url
* @param {Object} options
*
* @return {Pledge}
*/
Hawkejs.Scene.setMethod(function interceptOpenUrl(url, options) {
return Conduit.handleUrlLocal(url, options);
});