JavaScript framework based on Backbone.js. Extends Backbone router, controller, view and collections. Add some features, e.g.:
- router features
- controller
- grouping controllers in modules
- components (nested views)
- debugger
- etc.
Work in progress...
To automate search of needed controller router in Esencia loads module - usually group of controllers that provide functionality of logically related routes. For example, if you have routes /users, /users/:id, /users/:id/edit, etc. to provide work with user accounts in your app, than you need to create module users to load all controllers for described urls. Name of module must be the same as first url component after router root.
define([
'controllers/users/list',
'controllers/users/view',
'controllers/users/edit'
], function(
UsersListController,
UsersViewController,
UsersEditController
) {
// module must return function that will add controllers to router
return function(router) {
router.controller(new UsersListController());
router.controller(new UsersViewController());
router.controller(new UsersEditController());
};
});Router constructor takes one argument - hash of options. There are following options:
-
rootString, default:
'/'Root url for router.
-
modulesPathString, default:
'modules/'Path for folder with your modules.
-
defaultModuleNameString, default:
'main'Name of module that will be loaded if Esencia cannot extract module name from url. Usually
defaultModuleNameis a name of module for controller that provide functionality of router root url. -
pushStateBoolean, default:
falseDefine if router will use pushstate to manage browser navigation history.
-
autoloadModulesBoolean, default:
trueIf
truethan Esencia will automatically try to load module to get controller for current url. -
namedParametersBoolean, default:
false[todo: add doc for this option]
-
debug[todo: add doc for this option]
-
configObject, default:
{}[todo: add doc for this option]
-
onModuleErrorfunction
RequireJS
defineerror callback to provide error handling of loading modules. Details in RequireJS documentation
require([
'esencia/router'
], function(
Router
) {
var router = new Router({
root: '/app',
modulesPath: 'modules/',
pushState: true,
defaultModuleName: 'main',
namedParameters: true,
autoloadModules: true,
onModuleError: function(err) {
// error handling - maybe notification, navigate to error page
// or something else
}
});
});Router.controller method add controller to current router to provide functionality of url that specified in controller. Router.controller takes two arguments:
-
controllerInstance of controller that will be added to router
-
optionsObject, default:
{}Hash of options. There are following options:
-
processBoolean, default:
falseProcess controller in force mode (without matching url).
-
require([
'esencia/router',
'controllers/layout'
], function(
Router, layoutController
) {
var router = new Router({
root: '/app',
pushState: true,
namedParameters: true,
autoloadModules: false
});
router.controller(new layoutController());
router.start();
});Router.controllers receiving in arguments array of controllers to run Router.controller for each of them.
Note: Router.controllers do not pass options when add controllers to router.
Router.start method using for starting routes handling.
Router.navigate using for correct navigating between application urls. First argument is fragment - destination url (root part will be removed if fragment starts with root). Second argument is hash of options. There are following options:
-
forceBoolean, default:
falseIf
truethan router will navigate tonowhereUrl('___') before navigate tofragment. This trick is using to go to the selected fragment even if it equals to current and to rerender all views that was changed (by default only view of current controller will be rerendered). -
qsObject
Query string hash that will be stringified and added to
fragmentusingtoFragmentfrombackbone.queryparams.
Also options can include trigger (default: true) and replace (default: false) that described in Backbone documentation
router.navigate('/users', {
force: true,
qs: {
search: 'username'
}
});
// router will navigate to '/app/users?search=username' if router.root === '/app'Controller in Esencia is using for preparing data and build chain of views in depending on url. Controller has multiple stage workflow. By default when controller is processed in case of url matching there are next stages:
prepare -> view -> render
If controller is already prepared and contorller's view is attached than processing of controller has only one stage:
renderOnly
Controller.constructor takes only one argument - hash of options. There are following options:
-
urlString
Url or url regex which functionality will be provided by this instance of Esencia
Controller. -
nameString
Name of the controller. Must be unique.
-
parentNameString
Name of parent controller in chain of controllers.
-
modelsObject, default:
{}Hash of Backbone model instances for storing data and using in views.
-
collectionsObject, default:
{}Hash of Esencia collection instances for storing data and using in views.
-
viewOptionsObject, default:
{}Hash of default options that will be applied to context of current controller view.
-
defaultUrlParamsObject, default:
{}Hash of default url params that will be applied when Router navigates to url of this controller.
define([
'esencia/controller',
'views/users'
], function(ParentController, View) {
var Controller = {
name: 'users',
// layout controller was created before
parentName: 'layout',
View: View
};
// other operations with controller
// Controller hash must be extended with Esencia controller
return ParentController.extend(Controller);
});Controller.prepare usually using to fetch collections and models data. Controller.prepare implements prepare stage of controller processing. Argument callback is a function that must be called after end of all preparing operations.
Note: callback must be called even if prepare operations are synchronous.
define([
'esencia/controller',
'views/users',
'collections/users'
], function(ParentController, View, UsersCollection) {
// some code for create controller
Controller.prepare = function(callback) {
this.collections = {
users: new UsersCollection()
};
this.collections.users.fetch({
success: callback
});
};
return ParentController.extend(Controller);
});Esencia View extends Backbone View and add methods to manage nested views and handle render event.
Esencia View instances have following attributes:
-
modelsObject
Hash of data models that was created and fetched in
Controller.prepareor passed tooptionsargument ofView.constructor. -
collectionsObject
Hash of data collections that was created and fetched in
Controller.prepareor passed tooptionsargument ofView.constructor. -
dataObject, default
{}Inner view data that will be passed to template render function.
-
viewsObject
Every View instance has
this.viewshash that contains all nested views. Everythis.viewskey is selector of element to which attached views, value is array of views.Note: Hereinafter will call value of every
this.viewsitem views group. -
eventsObject, default:
{}Hash of view events that described in Backbone documentation.
-
viewsEventsObject, default:
{}Analog of
views.eventsto handle nested views events. Selector inthis.viewsEventsmust to be the same as selector of views group which event you need to handle. -
urlParamsObject
Current
urlParamsfromRouter. -
templateHelpersObject, default:
{}View.templateHelpersis hash of helper functions or data that will be used in templates. Before template is rendered theView.templateHelpersextends bythis.dataof rendering view and passed to template.
There are following options:
-
elelement
If
elis not present inoptionshash thanthis.noelwill be set totrue. Usually you don't need to pass -
dataObject, default:
{}Data that will be set to
this.dataand then will be passed to template render function. -
models,collectionsObject
Values of this options will be set to
this.modelsandthis.collectionsof created view respectively.
define('exampleView', [
'underscore', 'esencia/view'
], function(_, BasicView) {
var View = {
template: _.template('<div class="actionButton"> <%= title %> </div>'),
events: {
'click .actionButton': 'onActionButtonClick'
}
};
View.onActionButtonClick = function(event) {
// some code for handle action
};
View.getData = function() {
return {
title: 'Button'
}
};
return BasicView.extend(View);
});
define('exampleController', [
'esencia/controller', `exampleView`
], function(BasicController, ExampleView) {
var Controller = {
url: '',
view: ExampleView
}
return BasicController.extend(Controller);
});View.initialize is usually using for set nested views.
By default View.initialize is empty.
There are following options:
-
force
Boolean, default:
falseWill rerender views if
trueand view data was changed (seeView.isUnchanged).
View.afterRender is usually using for set jQuery components and some other actions that must to be executed exactly after rendering view template.
By default View.afterRender is empty.
View.beforeDetach is usually using for make some actions before view will be detached from DOM.
By default View.beforeDetach is empty.
View.getData is using for set data to template. View.getData returns hash that will be extended with templateHelpers (see View.templateHelpers) and then will be used in render function.
By default View.getData returns this.data. You can set you value to this.data by calling View.setData(data) (see View.setData).
View.setData using for two purposes:
-
you may specify this method of current view and this method will be called during rerendering to set actual data onto nested views;
-
also you may call this method of nested view object to set data directly to it.
If you want to set data onto nested views you must specify View.setData in current view with a function that will get nested views (see View.getView') and call their View.setDatamethods with hash of actual data.View.setDatawill be called every time when current view will be rendered (even if it will not be rerendered becauseforceisfalseandisUnchangedistrue`).
By default if View.setData will be called without arguments it will do nothing. And if it will be called with data argument it will set data to this.data.
define('exampleNestedView', [
'underscore', 'esencia/view'
], function(_, BasicView) {
var View = {
template: _.template('<div><%= title %></div>'),
};
return BasicView.extend(View);
});
define('exampleView', [
'underscore', 'esencia/view', 'exampleNestedView'
], function(_, BasicView, ExampleNestedView) {
var templateString = '<div id="nestedViewContainer"></div>';
var View = {
template: _.template(templateString),
};
View.initialize = function() {
this.setView(new ExampleNestedView(), '#nestedViewContainer');
};
View.setData = function() {
this.getView('#nestedViewContainer').setData({
// data for nested view will be set every time
// when current view rendering
title: 'Title'
});
};
return BasicView.extend(View);
});
define('exampleController', [
'esencia/controller', `exampleView`
], function(BasicController, ExampleView) {
var Controller = {
url: '',
view: ExampleView
}
return BasicController.extend(Controller);
});When view rerendering than all nested views can be rerendered too. View.isUnchanged is using for defining if you need rerender view. Usually View.isUnchanged returns result of comparison of view data and data from function arguments. If they are different than data was changed, View.isUnchanged returns false and view will be rerendered.
By default View.isUnchanged returns true. It means that view will not be rerendered in case of parent view called its render method.
Note: also you can use View.render({force: true}) to rerender current and all nested views independently from they View.isUnchanged results.
These methods are using for get, set and remove nested views. Their has all or several of these following arguments:
-
viewsEsencia.View instance or Array of Esencia.View instances
View or views to set.
-
selectorString
Selector to find DOM element for attach views. It may be any jQuery selector but id is recommended.
-
indexNumber
Index of view to replace. If index is passed it replace only one view with
indexin views group.
Alias: View.setViews(views, selector, index)
View.setView is using for creating nested views. Nested views will be inserted into existent selector views group or create new.
Alias: View.appendViews(views, selector)
Append views to selector views group. View.appendView is alias for View.insertView without index argument.
Alias: View.prependViews(views, selector)
Prepend views to selector views group.
Alias: View.insertViews(views, selector, index)
View.insertView insert views to specified index of views group. If index is not passed views will View.insertView append to end of views group. If there are no views on selector View.insertView will just set views no matter what value index is.
Alias: View.removeViews(views, selector, index)
Remove view or views from views group by index or views instances list.
Usage:
-
View.removeView(selector, index)Will remove
indexview fromselectorviews group. -
View.removeView(views, selector)Will remove all instances from
viewsArray that are present in views group ofselector.
Note: by default View.removeView doesn't remove views from DOM. If you want to remove views form DOM you may:
-
use
View.remove() -
rerender parent view with
{force: true}. This way is not recommended because it may cause performance problems.
View.remove remove view from DOM.
View.getView returns first or index view from selector views group or null if there are no views attached to selector element.
View.getViews returns views group of selector or null if there are no views attached to selector element.
View.getClosestView returns view that attached to the closest element with class .view-attached.
See manage nested views example in examples/todos
Esencia Collection extends Backbone Collection - override Collection.sync method and add Collection.exec.
Exec custom non-REST method on collection. It triggers exec:[method] event after success collection sync.
define('userView', [
'underscore', 'esencia/view'
], function(_, BasicView) {
var templateString = '<ul><% _.each(users, function(user) { %>' +
'<li class="resetPasswordButton" data-id="<%= user.id %>">' +
'Reset <%= user.name %> password' +
'</li>' +
'<% }) %></ul>';
var View = {
template: _.template(templateString),
events: {
'click .resetPasswordButton': 'onPasswordReset'
}
};
View.onPasswordReset = function(event) {
var id = this.$(event.currentTarget).data('id');
this.collections.users.exec('resetpassword', {
data: {
id: id
},
success: function() {
// handle success password reset
}
});
};
View.getData = function() {
return {
users: this.collections.users.toJSON()
}
};
return BasicView.extend(View);
});MIT