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:
-
root
String, default:
'/'
Root url for router.
-
modulesPath
String, default:
'modules/'
Path for folder with your modules.
-
defaultModuleName
String, default:
'main'
Name of module that will be loaded if Esencia cannot extract module name from url. Usually
defaultModuleName
is a name of module for controller that provide functionality of router root url. -
pushState
Boolean, default:
false
Define if router will use pushstate to manage browser navigation history.
-
autoloadModules
Boolean, default:
true
If
true
than Esencia will automatically try to load module to get controller for current url. -
namedParameters
Boolean, default:
false
[todo: add doc for this option]
-
debug
[todo: add doc for this option]
-
config
Object, default:
{}
[todo: add doc for this option]
-
onModuleError
function
RequireJS
define
error 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:
-
controller
Instance of controller that will be added to router
-
options
Object, default:
{}
Hash of options. There are following options:
-
process
Boolean, default:
false
Process 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:
-
force
Boolean, default:
false
If
true
than 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). -
qs
Object
Query string hash that will be stringified and added to
fragment
usingtoFragment
frombackbone.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:
-
url
String
Url or url regex which functionality will be provided by this instance of Esencia
Controller
. -
name
String
Name of the controller. Must be unique.
-
parentName
String
Name of parent controller in chain of controllers.
-
models
Object, default:
{}
Hash of Backbone model instances for storing data and using in views.
-
collections
Object, default:
{}
Hash of Esencia collection instances for storing data and using in views.
-
viewOptions
Object, default:
{}
Hash of default options that will be applied to context of current controller view.
-
defaultUrlParams
Object, 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:
-
models
Object
Hash of data models that was created and fetched in
Controller.prepare
or passed tooptions
argument ofView.constructor
. -
collections
Object
Hash of data collections that was created and fetched in
Controller.prepare
or passed tooptions
argument ofView.constructor
. -
data
Object, default
{}
Inner view data that will be passed to template render function.
-
views
Object
Every View instance has
this.views
hash that contains all nested views. Everythis.views
key is selector of element to which attached views, value is array of views.Note: Hereinafter will call value of every
this.views
item views group. -
events
Object, default:
{}
Hash of view events that described in Backbone documentation.
-
viewsEvents
Object, default:
{}
Analog of
views.events
to handle nested views events. Selector inthis.viewsEvents
must to be the same as selector of views group which event you need to handle. -
urlParams
Object
Current
urlParams
fromRouter
. -
templateHelpers
Object, default:
{}
View.templateHelpers
is hash of helper functions or data that will be used in templates. Before template is rendered theView.templateHelpers
extends bythis.data
of rendering view and passed to template.
There are following options:
-
el
element
If
el
is not present inoptions
hash thanthis.noel
will be set totrue
. Usually you don't need to pass -
data
Object, default:
{}
Data that will be set to
this.data
and then will be passed to template render function. -
models
,collections
Object
Values of this options will be set to
this.models
andthis.collections
of 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:
false
Will rerender views if
true
and 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 because
forceis
falseand
isUnchangedis
true`).
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:
-
views
Esencia.View instance or Array of Esencia.View instances
View or views to set.
-
selector
String
Selector to find DOM element for attach views. It may be any jQuery selector but id is recommended.
-
index
Number
Index of view to replace. If index is passed it replace only one view with
index
in 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
index
view fromselector
views group. -
View.removeView(views, selector)
Will remove all instances from
views
Array 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