BX.use('brixy', 'modules/es/types.jsxinc');
/**
* @module 'brixy.mvc.Router'
*/
BX.module.define('brixy.mvc.Router', function() {
var types = BX.module('brixy.es.types');
/**
* Route object.
* @class
* @alias module:'brixy.mvc.Router'~Route
* @param {Object|string} [config] - Initial setting. (optional)
*/
function Route(config) {
this.controller = '';
this.forward = '';
this.action = '';
this.data = null;
this.refine(config);
}
/**
* Returns a string representation of the object.
* @method
* @return {string}
*/
Route.prototype.toString = BX.toString;
/**
* Refines properties of the route.
* @param {Object|string} config - Object may refine properties 'controller', 'forward', 'action', 'data'. String refines 'forward' property.
*/
Route.prototype.refine = function(config) {
if (config && types.isString(config)) {
this.forward = config;
return;
}
else if (typeof config !== 'object')
return;
config.controller && (this.controller = config.controller);
config.forward && (this.forward = config.forward);
config.action && (this.action = config.action);
if (config.data && typeof config.data === 'object' && config.data !== this.data)
fillDataProps(config.data, this);
};
// create new data object, copy properties (don't modify the saved router data)
function fillDataProps(data, route) {
var p,
d = route.data;
route.data = {};
if (d) {
for (p in d) {
route.data[p] = d[p];
}
}
for (p in data) {
route.data[p] = data[p];
}
}
/**
* Gets a human readable string representation of this route.
* Note: This is not an inverse operation to the `parseRequestString()` method.
* @return {string}
*/
Route.prototype.toHumanString = function() {
var r = '';
if (this.controller)
r += this.controller;
else if (this.forward)
r += this.forward;
if (this.action)
r += Route._actionSeparator + this.action;
if (this.data)
r += Route._dataSeparator + '...';
return r;
};
/**
* Checks if route has a valid format (ie. looks like Route).
* @param {Object|string} route - Route object. Property 'controller' or 'forward' is required. String is shortcut to {forward: route}.
* @throws Exception if route has an invalid format.
*/
Route.validate = function(route) {
if (route && types.isString(route))
return;
if (typeof route !== 'object')
throw Error('Object type is expected.');
if (!('controller' in route) && !('forward' in route))
throw Error('Missing "controller" or "forward" property.');
};
/* Action separator used in the request string. */
Route._actionSeparator = ':';
/* Data separator used in the request string. */
Route._dataSeparator = '?';
/**
* Parses the request string to route-like object.
* Request format: `"controller:action?data"`. Controller part is required.
*
* | Part of request | Description |
* | --- | --- |
* | controller | shall not contain characters `:` and `?` |
* | action separator | default is `:` |
* | action | shall not contain character `?` |
* | data separator | default is `?` |
* | data | set of properties and its values in the form `property1=value1&property2=value2` |
*
* @example <caption>Valid request strings:</caption>
* "controller"
* "controller:action"
* "controller:action?par1=value1"
* "controller?par1=value1"
* "controller:action?par1=value1&par2=value2"
* "controller:action?par1&par2=value2"
* @param {string} request
* @return {Object} - {controller: string, action: string, data: Object|null}
* @throws Exception if request has an invalid format.
*/
Route.parseRequestString = function(request) {
var a = '',
d = null,
c = String(request),
ra = types.escapeRegexpStr(Route._actionSeparator),
rd = types.escapeRegexpStr(Route._dataSeparator),
r = c.match(RegExp('^([^' + ra + rd + ']+?)(?:' + ra + '([^' + rd + ']*))?(?:' + rd + '(.*))?$'));
if (r) {
c = r[1];
a = r[2] || '';
if (r[3]) { // fill data properties
d = {};
r[3].replace(RegExp('([^=&]+)(?:=([^&]*))?', 'g'), function($0, $1, $2) {
d[$1] = $2;
});
}
}
if (!c)
throw Error('Bad request format. Controller or route name is not specified.');
return {controller: c, action: a, data: d};
};
/**
* Router object. It holds a repository of named routes.
* @class
* @alias module:'brixy.mvc.Router'~Router
* @param {Function} [RouteType={@link module:'brixy.mvc.Router'~Route}] - route constructor (optional)
* @throws Exception
*/
function Router(RouteType) {
if (RouteType == undefined)
this._Route = Route;
else if (typeof RouteType !== 'function')
Error('Route must be a function.');
else
this._Route = RouteType;
this._routes = {}; // own routes
}
/**
* Returns a string representation of the object.
* @method
* @return {string}
*/
Router.prototype.toString = BX.toString;
/**
* Saves a set of routes and replaces existing route set. The correct format is verified.
* @param {Object} routes - Set of routes in the form `{name1: route1, name2: route2, ...}`.
* @throws Exception if routes has an invalid format.
*/
Router.prototype.setRoutes = function(routes) {
try {
if (!routes || typeof routes !== 'object')
throw Error('Object type is expected.');
var validateR = this._Route.validate,
r;
if (validateR) { // need validation
for (r in routes) {
if (routes.hasOwnProperty(r))
validateR(routes[r]);
}
}
this._routes = routes;
}
catch (e) {
throw BX.error('brixy.mvc.Router.setRoutes()', Error('Routes object has an invalid format.' + (r ? ' Route "' + r + '" failed.' : '')), e);
}
};
/**
* Adds a new route. The correct format is verified.
* @param {string} name - Route name.
* @param {Object} route - Route object.
* @throws Exception if route has an invalid format.
*/
Router.prototype.addRoute = function(name, route) {
try {
if (!name)
throw Error('Route name is expected.');
this._Route.validate(route);
this._routes[name] = route;
}
catch (e) {
throw BX.error('brixy.mvc.Router.addRoute()', Error('Route "' + name + '" has an invalid format.'), e);
}
};
/**
* Removes the route.
* @param {string} name - Route name.
*/
Router.prototype.removeRoute = function(name) {
if (this.hasRoute(name)) {
this._routes[name] = undefined;
delete this._routes[name];
}
};
/**
* Gets a saved Route object or null.
* @param {string} name - Route name.
* @return {Object|null} Route object or null.
*/
Router.prototype.getRoute = function(name) {
if (this.hasRoute(name)) {
var R = this._Route;
return new R(this._routes[name]);
}
return null;
};
/**
* Checks if the router has a saved route of this name.
* @param {string} name - Route name.
* @return {boolean}
*/
Router.prototype.hasRoute = function(name) {
return this._routes.hasOwnProperty(name);
};
/**
* Creates the Route object from request. This one is not saved as named route.
* @param {string|Object} request - Request string or route-like object.
* @return {Object} Route object.
* @throws Exception on error.
*/
Router.prototype.createRoute = function(request) {
var R = this._Route;
if (!types.isString(request)) {
if (request instanceof R) // is Route
return request;
try {
R.validate(request); // looks like Route
return new R(request);
}
catch (e) {
throw BX.error('brixy.mvc.Router.createRoute()', Error('Request "' + request + '" is not a valid object.'), e);
}
}
var req,
route;
try {
req = R.parseRequestString(request);
route = this.getRoute(req.controller);
if (route) {
req.controller = '';
route.refine(req);
}
else {
route = new R(req);
}
}
catch (e) {
throw BX.error('brixy.mvc.Router.createRoute()', Error('Cannot create a route from the request "' + request + '".'), e);
}
return route;
};
// publish the class
return {
/**
* Route class.
* @memberOf module:'brixy.mvc.Router'
* @type {module:'brixy.mvc.Router'~Route}
*/
Route: Route,
/**
* Router class.
* @memberOf module:'brixy.mvc.Router'
* @type {module:'brixy.mvc.Router'~Router}
*/
Me: Router
};
});
►