PDA

View Full Version : Ext.util.Router & Ext.util.Route - Default Route proposal



HendrixMH
23 Feb 2011, 3:56 AM
**UPDATE: sorry i'v made mistake and this post shouldn't be here, if any of the moderators can move this post to the Ext: Feature Requests forum i wold be grateful ***

Sorry if i post it in wrong place but it's only one i known.


I was playing with the MVC of extjs 4 and i'v got some things i like sher, may by my ideas will be useful for others.

I use php and i realy like the Zend Framework way of using the Routers so i write the default router for ExtJS4 withch works as a default router in ZF.

This route recognizes all urls like: :controller/:action/*
* is recognized as aditional params like: param1/value1/param2/value2...

example:
url: userController/showUserAction/userId/44/setParam/setValue

will be recognized as:
{
controller: 'userController',
action: 'showUserAction',
userId: 44,
setParam: 'setValue'
}

And i like to see the default route be configured atuomaticly for me when the application is launched.
Maby just add flag to the Ext.Application, autoRouteInit:true to init router with the default route.

What Do you think of it ?

Configuration:

Ext.Router.draw(function(router) {
var defaultRoute = Ext.create('Ext.util.route.Default');
router.routes.add(defaultRoute);
});

Default Route class:

Ext.define('Ext.util.route.Default', {

/**
* @cfg {String} url The url string to match. Required.
*/

constructor: function(config) {
Ext.apply(this, config, {
defaultController: 'index',
defaultAction: 'index'
});
},

/**
* Recognizes the string and returns the data
* @param {String} url The url to recognize
* @return {Object} The matched data, or false if no match
*/
recognize: function(url) {
var arr = url.split('/');
var params = {};
for(var i=0; i<arr.length; i++) {
var param = null;
var value = null;
if(i==0) {
param = 'controller';
value = arr[i];
} else if(i==1) {
param = 'action';
value = arr[i];
} else {
param = arr[i];
value = arr[i+1];
i++;
}
if(Ext.isEmpty(param)) {
continue;
}
if(Ext.isEmpty(value)) {
value = '';
}
params[param] = value;
}

return Ext.applyIf(params, {
controller: this.defaultController,
action : this.defaultAction,
historyUrl: url
});
},

/**
* Constructs a url for the given config object
* @param {Object} config The config object
* @return {String} The constructed url
*/
urlFor: function(config) {
Ext.applyIf(config, {
controller: this.defaultController,
action : this.defaultAction
});
var url = config.controller+'/'+config.action;

delete config['controller'];
delete config['action'];

for (name in config) {
url = url+'/'+name+'/'+config[name];
}

return url;
}
});

holicon_abg
25 May 2011, 5:26 AM
i put working code to https://github.com/agborkowski/Holicon-Labs/tree/master/extjs-mvc/app/util if u want u can fork it, btw more compliant with REST and others is put args in GET and POST.

For rg.params link with /ctrl/method/id/arg1/arg2/arg323232323 is stupid i think ..

rsqw
20 Nov 2011, 12:43 AM
Hi,
Your router is not bad :), but in my case one application state should contains 5-8 mvc-components,
so I've wrote my own router(Dispatcher).

/* * @class Dispatcher
* @singleton
*
* controls all actions connected with
* parsing window.location.hash
* and creating new location
*/
Ext.define('Dispatcher', {
singleton: true,
request: {},
requires: [
'Ext.History'
],
/*
* @private {Array} List of routes, each route contains regullar expression,
* and route-property that discribes what controllers and actions should run
*/
routes: [],
/*
* @private {Object} set of fields is the same as instance for this.routes
*/
lastRoute: null,
/*
* create hidden form, init listeners
* and perfroms initialization for Ext.History
*/
constructor: function() {
this.initRoutes();
this.historyForm = Ext.getBody().createChild({
tag: 'form',
action: '#',
cls: 'x-hidden',
id: 'history-form',
children: [{
tag: 'div',
children: [{
tag: 'input',
id: Ext.History.fieldId,
type: 'hidden'
}, {
tag: 'iframe',
id: Ext.History.iframeId
}]
}]
});


//this.compileRoutes();
//initialize History management
Ext.History.init();
Ext.History.on('change', this.dispatch, this); //hash was changed by user
},
getRouteSymbols: function(re) {
return re.match(/\:([a-zA-Z0-9]+)/g) || [];
},
saveState: function(newState) {
this.state = newState;
},
/*
* parses current window location, to find actions and controllers
* @returns {Boolean} false if location was incorrect, true overwise
*/
dispatch: function() {
this.resetParamsLastIndex();
var hash = Ext.History.getToken() || "/dashboard";
var oldTime = (new Date()).getTime();


try {
for (var i = 0; i < this.routes.length; i++) {
var rConfig = Ext.clone(this.routes[i]);
var compiled = this.compileRoute(rConfig);
var matches = hash.match(new RegExp(compiled.re));




if (matches == null) {
continue;
}

this.parseParams(matches, rConfig.route, compiled.params);
break;
}
} catch (e) { //location is invalid, ignore an error
Ext.log(e);
return false;
}


if (this.silent) {
this.silent = false;
return false;
}


if (this.getLastRouteName() == null) {
this.setLastRouteName(rConfig.name);
}
var parsedTime = (new Date()).getTime();
console.log("Route parsing ",parsedTime - oldTime);
console.log(routeConfig)
console.log("After Creation Controllers", (new Date()).getTime() - parsedTime);
this.setLastRoute(rConfig);


return true;
},
getCurrentRoute: function() {
return this.currentRoute;
},
setLastRoute: function(newRoute) {
this.lastRoute = newRoute;
},
compileRoute: function(config) {
var route = config.route,
i = 0,
j = 0,
matches = {
global: [],
controller: []
};
var re = config.re.replace(/\:([a-zA-Z0-9]+)/g, function(a, b) {
while (!route[i].params) {
i++;
j = 0;


if (route[i].params) {
break;
}
}


var params = route[i].params[j];


matches.global.push(b);
matches.controller[i] = matches.controller[i] || {
params: []
};


matches.controller[i].params.push(b);


if (j == route[i].params.length - 1) {
++i;
j = 0;
} else {
++j;
}


return params[b];
});


return {
re: re,
params: matches
}
},
/*
* creates location parsing configuration object,
* and applies it to window.location
*
* @param {Object} config
* @param {String} config.name Route name
* @param {Object} config.params Set of parameter names
*
* @returns {undefined}
*/
createLocation: function(config) {
var routeName = config.name || this.getLastRouteName();
var params = config.params || {};
var appRouteList = this.routes;


for (var i = 0, l = appRouteList.length; i < l; ++i) {
var routeConfig = appRouteList[i];


if (routeName == routeConfig.name) {
var location = routeConfig.re;
var symbols = this.getRouteSymbols(location);


for (var j = 0, s = symbols.length; j < s; j++) {
var paramName = symbols[j].substring(1);


if (params[paramName] == undefined) {
params[paramName] = this.getRequest().get(paramName);
}
}


location = location.replace(/\:([a-zA-Z0-9]+)/g, function(a, b) {
return params[b];
});


this.setSilent(config.silent);
this.changeHash(location, routeConfig.name);


return;
}
}





//throw "Location name " + config.name + " is INVALID";
},
setSilent: function(state) {
this.silent = state;
},
isSilent: function() {
return this.silent;
},
parseParams: function(matches, route, symbols) {
matches = matches.slice(1); //remove whole route match
this.request = {
global: {},
ns: {}
};


for (var i = 0, j = 0, k = 0, l = matches.length; i < l; i++) {
while (!route[j].params) {
j++;
k = 0;
if (route[j].params) break;
}


var param = route[j].params[k];
this.request.global[symbols.global[i]] = matches[i];


var key = symbols.controller[j].params[k];


if (this.request.ns[j] == undefined) {
this.request.ns[j] = [matches[i]];
} else {
this.request.ns[j].push(matches[i]);
}


if (k == route[j].params.length - 1) {
++j;
k = 0;
} else {
++k;
}
}


return this.request.global;
},
/*
* @returns {Object} Set of fields that discribes current route,
* including re and parsed route
*/
getLastRoute: function() {
return this.lastRoute;
},
/*
* creates valid location using controller, action and params
* @param {String} controller Name of controller
* @param {String} action Name of the action
* @param {Array} params List of action's params
* @returns {String} valid location
*/
buildRoute: function(controller, action, params) {
var actionParams = '';


if (!params || params.length == 0) {
actionParams = '';
} else {
actionParams = params.length == 9 ? "" : '|' + params.join('|');
}


return '/' + controller + '/' + action + actionParams;
},
/*
* @returns {String} current location that follows after #
*/
getHash: function() {
return Ext.History.getToken();
},
/*
* replaces current location with newHash-param
* @param {String} newHash New location
*/
changeHash: function(newHash, routeName) {
this.resetParamsLastIndex();
this.setLastRouteName(routeName);


var newLocation = newHash.replace(/\^|\$/g, '');
var oldLocation = this.getHash();


if (oldLocation == newLocation) {
this.dispatch();
} else {
Ext.History.add(newLocation);
}
},
setLastRouteName: function(routeName) {
this.lastRouteName = routeName;
},
getLastRouteName: function() {
return this.lastRouteName;
},
getControllerAlias: function(controllerName) {
if (controllerName.indexOf('.') == -1) {
return controllerName;
}


return controllerName.split('.')[0];
},
getBreadcrumbsRoute: function() {
return {
re: this.breadcrumbLastRoute.re,
route: {
controller: this.breadcrumbLastRoute.route.controllers.shift(),
action: this.breadcrumbLastRoute.route.actions.shift(),
params: this.breadcrumbLastRoute.route.params.shift()
}
};
},
getRequest: function() {
return this.createRequestObj(this.request.global);
},
getControllerParams: function() {
var lastIndex = this.getControllerParams.lastIndex;
lastIndex = lastIndex == undefined ? 0 : lastIndex + 1;


return this.request.ns[this.getControllerParams.lastIndex = lastIndex];
},
resetParamsLastIndex: function() {
this.getControllerParams.lastIndex = undefined;
},
createRequestObj: function(request) {
var dispatcher = this;
return new function() {
var me = this;
this.data = request;


this.get = function(key) {
return me.data[key] || false;
}
};
},
initRoutes: function() {
this.routes = [
this.getRouteCompanyProfilesView(),
this.getRouteCompanyDetailsView(),

this.getRouteDefaultPeople(),
this.getRoutePeopleDetails(),
this.getRoutePeopleView(),

this.getRouteDefaultDashboard(),
this.getRouteDefaultTasks(),

this.getRouteDefaultCompany(),
this.getRouteCreateCompany()
];
},
getRouteDefaultPeople: function() {
return {
re: "^\/people$",
name: 'default/people',
route: [{
controller: "people.Controller",
action: 'index'
}]
};
},

getRoutePeopleView: function() {
return {
re: "^\/people\/:peopleId\/:tabName$",
name: 'people/view',
route: [{
controller: "people.Controller",
action: 'view'
}, {
controller: "people.PortraitController",
action: 'index',
params: [{
peopleId: '([0-9]{1,50})'
}, {
tabName: '(documents|contact|experience|relationships|notes|tags)'
}]
}]
};
},
getRouteDefaultDashboard: function() {
return {
re: "^\/dashboard",
name: 'default/dashboard',
route: [{
controller: "dashboard.Controller",
action: 'index'
}]
};
},
getRouteDefaultCompany: function() {
return {
re: "^\/company$",
name: 'default/company',
route: [{
controller: 'company.Controller',
action: 'index'
}]
};
},
getRouteCreateCompany: function() {
return {
re: "^\/company\/create$",
name: 'create/company',
route: [{
controller: 'company.Controller',
action: 'index'
}, {
controller: 'company.Controller',
action: 'create'
}]
};
},
getRouteCompanyProfilesView: function() {
return {
re: "^\/company\/:companyId\/:profileType$",
name: 'company/view/profile',
route: [{
controller: "company.Controller",
action: 'view',
params: [{
companyId: '([0-9]{1,50})'
}, {
profileType: '(profile)'
}]
}, {
controller: "contactInfo.Controller",
action: 'company'
}, {
controller: "activities.WidgetController",
action: 'company'
}, {
controller: "relationships.WidgetController",
action: 'company'
}, {
controller: "profile.Controller",
action: 'company'
}]
}
},

getRouteCompanyDetailsView: function() {
return {
re: "^\/company\/:companyId\/:profileType\/:detailsTabIndex$",
name: 'company/view/details',
route: [{
controller: "company.Controller",
action: 'view',
params: [{
companyId: '([0-9]{1,50})'
}, {
profileType: '(details)'
}, {
detailsTabIndex: '([0-9]{1,50})'
}]
}, {
controller: "contactInfo.Controller",
action: 'company'
}, {
controller: "activities.WidgetController",
action: 'company'
}, {
controller: "relationships.WidgetController",
action: 'company'
}, {
controller: "company.DetailsController",
action: "index"
}]
}
},

getRouteDefaultTasks: function() {
return {
re: "^\/tasks$",
name: 'default/tasks',
route: [{
controller: "tasks.Controller",
action: 'index'
}]
};
}
});