I want to write a reusable component as part of my Backbone app. Fundamentally I want to write a form filter helper so I can:
call a func inside a view js file which will create a drop-down which can listen to changes and then trigger changes in data and refreshes the view.
Ultimately I'd like to be able to do something like this:
// generic view.js
// to spawn a dropdown
formFilter('select', data);
// to spawn a series of checkboxes
formFilter('checkbox', data);
Obviously the module code would listen for events and handle the work.
My question is, what is the standard way of creating a reusable component? Google isn't giving me much and the #documentcloud IRC isn't particularly active.
Based on the information you've provided in your question, it's not easy to say how your particular component would be best componentized. However, one powerful strategy for reusability is mixins.
Simply you define the methods in a simple object literal, such as:
Mixins.filterable = {
filterForm: function(selector, data) {
this.$(selector)...
}
}
Mixins.sortable = {
sortForm: function(selector) {
this.$(selector)...
}
}
And then you can mix them into any View's prototype:
_.extend(FooView.prototype, Mixins.filterable, Mixins.sortable);
The mixin methods will then be available in all instances of FooView.
render: function() {
//..
this.filterForm('select', this.model);
}
Because the mixin methods will be bound to the context of the view instance, you can refer to this, and by logical extension, this.$el, inside the mixin methods. This will enable you to listen to the view's events:
Mixins.filterable = {
filterForm: function(selector, data) {
this.$(selector).on('change', this._handleFilterChange);
},
_handleFilterChange: function(e) {
//..
}
}
To make the methods available on all views, mix them into the Backbone.View prototype instead:
_.extend(Backbone.View.prototype, Mixins.filterable, Mixins.sortable);
Related
I have a large number of views (more than 50) which all extend from a single abstract base view, and therefore have a similar layout and many other features in common (event handlers, some custom methods and properties, etc).
I am presently using the initialize method of my base view to define the layout, which involves a subview, somewhat like the following:
App.BaseView = Backbone.View.extend({
//...
initialize: function() {
this.subView = new App.SubView();
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
this.subView.$el = this.$('#subview-container');
this.subView.render();
return this;
},
//...
});
I find, however, that for many views which extend my base view I need to override the initialize method, which calls the base class initialize (I also extend my events hash quite often as well). I dislike having to do this, especially with so many views which extend the base class.
In this post from a Backbone Github repository issue Derick Bailey says:
I'm also not a fan of requiring extending classes to call super
methods for something like initialize. This method is so basic and
so fundamental to any object that extends from a Backbone construct.
It should never be implemented by a base type - a type that is built
with the explicit intent of never being instantiated directly, but
always extended from.
So on this model I should be able to have an initialize available for each inheriting view class. This makes perfect sense to me; but how can I then implement the kind of general layout I need for my inheriting views? In the constructor method?
I don't know if what I want might be possible out-of-the-box with something like Marionette or LayoutManager, both of which I've briefly looked at but never used, but I would much prefer doing this in vanilla Backbone at the moment.
Where to implement the initializing of the base class?
The way I like to do it is to initialize base classes in the constructor leaving the initialize function empty. It makes sense as the initialize function is only a convenience offered by Backbone and is really just an extension of the constructor.
In fact, Backbone do this a lot. Most if not all functions and properties that we override often are there only to be easily overriden.
Here's a quick list of such example:
Model: initialize, defaults, idAttribute, validate, urlRoot, parse, etc.
Collection: initialize, url, model, modelId, comparator, parse, etc.
View: initialize, attributes, el, template, render, events, className, id, etc.
These functions are left to the user to implement his own behaviors and to keep that useful pattern in a base class, they should be kept untouched and the base class behavior should be hooked into other functions if possible.
Sometimes, it can get difficult, like if you want to do something before initialize is called in the constructor, but after the element and other properties are set. In this case, overriding _ensureElement (line 1223) could be a possible hook.
_ensureElement: function() {
// hook before the element is set correctly
App.BaseView.__super__._ensureElement.apply(this, arguments);
// hook just before the initialize is called.
}
This was just an example, and there are almost always a way to get what you want in the base class without overriding a function that the child will also override.
Simple base class
If the base view is used in a small component and overriden by few child views, and mostly used by the same programmer, the following base view could be enough. Use Underscore's _.defaults and _.extend to merge the child class properties with the base class.
App.BaseView = Backbone.View.extend({
events: {
// default events
},
constructor: function(opt) {
var proto = App.BaseView.prototype;
// extend child class events with the default if not already defined
this.events = _.defaults({}, this.events, proto.events);
// Base class specifics
this.subView = new App.SubView();
// then Backbone's default behavior, which includes calling initialize.
Backbone.View.apply(this, arguments);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
// don't set `$el` directly, use `setElement`
this.subView
.setElement(this.$('#subview-container'))
.render();
// make it easy for child view to add their custom rendering.
this.onRender();
return this;
},
onRender: _.noop,
});
Don't set $el directly, use setElement instead.
Then a simple child view:
var ChildView = App.BaseView.extend({
events: {
// additional events
},
initialize: function(options) {
// specific initialization
},
onRender: function() {
// additional rendering
}
});
Advanced base class
If you're facing one of the following situation:
overriding render is problematic, don't like onRender
the events property (or any other property) is a function in the child or the parent or both
the programmer using the base class don't know about its specifics
Then it's possible to wrap the child properties implementation into new functions and Underscore's _.wrap function does just that.
App.BaseView = Backbone.View.extend({
// works with object literal or function returning an object.
events: function() {
return { /* base events */ };
},
// wrapping function
_events: function(events, parent) {
var parentEvents = App.BaseView.prototype.events;
if (_.isFunction(parentEvents)) parentEvents = parentEvents.call(this);
if (parent) return parentEvents; // useful if you want the parent events only
if (_.isFunction(events)) events = events.call(this);
return _.extend({}, parentEvents, events);
},
constructor: function(opt) {
var proto = App.BaseView.prototype;
// wrap the child properties into the parent, so they are always available.
this.events = _.wrap(this.events, this._events);
this.render = _.wrap(this.render, proto.render);
// Base class specifics
this.subView = new App.SubView();
// then Backbone's default behavior, which includes calling initialize.
Backbone.View.apply(this, arguments);
},
/**
* render now serves as both a wrapping function and the base render
*/
render: function(childRender) {
// base class implementation
// ....
// then call the child render
if (childRender) childRender.call(this);
return this
},
});
So the child looks completely normal while keeping the base class behaviors.
var ChildView = App.BaseView.extend({
events: function() {
return {
// additional events
};
},
initialize: function(options) {
// specific initialization
},
render: function() {
// additional rendering
}
});
Potential problems
This could become a problem if you want to override the base class behavior completely, you would need to cancel some of the base class behavior manually in the child class, and it could prove to be confusing.
Say you have a special child used once that need to override the render completely:
var SpecialChildView = App.BaseView.extend({
initialize: function(options) {
// Cancel the base class wrapping by putting
// the this class's prototype render back.
this.render = SpecialChildView.prototype.render;
// specific initialization
},
render: function() {
// new rendering
}
});
So it's not black and white and one should evaluate what is needed and what is going to be in the way and choose the right overriding technique.
I would like to include multiple mixins within a view in Ember.js and more than one of the mixins and/or the view uses a same event (e.g. willInsertElement). I'm running Ember 1.4.0-beta.5.
I understand that the event in each mixin will be overridden by the view. However, I have read that it is possible to use the same event hook in the mixin and view, or multiple mixins included in the same view, by calling this._super(); at the start of the mixin's aforementioned event method. However, I have not been able to successfully make this happen. My question is, thus, how can I write logic within the same event hook in a view and mixin (or multiple mixins included in the same view) so that all the logic within each occurrence of the event hook will be called.
Here is an example:
App.StatsView = Em.View.extend(
App.DateFormatting, {
willInsertElement: function() {
// Some view-specific logic I want to call here
},
});
App.DateFormatting = Em.Mixin.create({
willInsertElement: function() {
this._super(); // This doesn't work.
// Some mixin logic I want to call here
},
});
N.B. One approach here might be to not use a mixin and extend a view instead (because willInsertElement is specific to Em.View), but that isn't maintainable in our apps.
If the different functions you're using are not dependent on each other, it's the best solution to not override the willInsertElement hook, but to tell the function to be raised when the event/hook gets called.
Like:
App.StatsView = Em.View.extend(App.DateFormatting, {
someSpecificFunction: function () {
console.log('beer me');
}.on('willInsertElement')
});
App.DateFormatting = Em.Mixin.create({
dateFormattingFunction: function () {
console.log('beer you');
}.on('willInsertElement')
});
Will keep it short: I have the need to build different components for my app. What I'm calling a "component" here is a collection of methods and constructors that repeats itself in many places of my app but is not necessarily exactly the same.
For the sake of an example, consider a pagination component. It is made of a few methods, a view and a template:
var Pagination = function() {
this.View = Backbone.View.extend({});
this.method = function() {};
this.method2 = function() {};
};
// Create instance
var myComponent = new Pagination();
Heavily abstracted code, but gives the idea. Now, I've created a "Component" constructor to aid me in my task. It is fairly simple:
Component = function() {
this.initialize.apply(this, arguments);
};
_.extend(Component.prototype, {
initialize: function() {}
});
Component.extend = Backbone.Model.extend;
This allows me to build multiple instances of the same component with slightly different methods or contents, without having to rebuild the same thing over and over again:
var Pagination = Component.extend({
View: Backbone.View.extend({}),
method: function() {},
method2: function() {}
}};
// Create instance
var myComponent = new Pagination();
So far so good. myComponent has the list of methods as well as the constructor for the View. Here is where the problem lies: for some of my Components's instances I need to extend not only the component itself, but the Backbone constructors inside it as well. Say, for example, that I want to extend (not replace) the View inside my pagination component. That's were everything falls down.
This is the last approach I've had, but the basic idea is: I have components that repeat themselves frequently in my app but with slight differences between them. These components may include methods, backbone constructors and primitive values inside of them. I need to be able to extend these but ALSO be able to extend it's individual parts (Backbone constructors) if the need arises.Anyone has an idea on how to accomplish this?
I am maintaining a javascript application and I would like there to be a jquery function invoked on pretty much every view. It would go something like this:
SomeView = Backbone.Marionette.ItemView.extend
initialize: ->
#on( 'render', #after_render )
after_render: ->
this.$el.fadeOut().fadeIn()
Clearly there is a better way to do this than have an after_render() in each view? What is the better way to do it? If you can give an answer that includes jasmine tests, I'll <3 you ;)
The event you are looking for is onDomRefresh. See here for the documentation:
https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.view.md#view-domrefresh--ondomrefresh-event
Create your own base view class and put your afterRender code in it. When you create a view, inherit from this class.
var MyApp.ItemView = Backbone.Marionette.ItemView.extend({
afterRender: function() {
// This will be called after rendering every inheriting view.
}
});
var SpecificItemView = MyApp.ItemView.extend({
// this view will automatically inherit the afterRender code.
});
In general, it seems to be considered good practice to define your own base views for all 3 view types. It will enable you to easily add global functionality later.
There is a common pattern used across all Backbone frameworks, normally they have a render method which in turn calls beforeRender, renderTemplate and afterRender methods.
render:function(){
this.beforeRender();
this.renderTemplate();// method names are just indicative
this.afterRender();
return this;
}
In your Base view you can have these methods to be empty functions, and implement them wherever you want it. Not sure this answer applies to Marionette
Combining thibaut's and Robert Levy's answer, the correct solution would be:
var baseView = Backbone.Marionette.ItemView.extend({
onDomRefresh: function() {
// This will be triggered after the view has been rendered, has been shown in the DOM via a Marionette.Region, and has been re-rendered
// if you want to manipulate the dom element of the view, access it via this.$el or this.$('#some-child-selector')
}
});
var SpecificItemView = baseView.extend({
// this view will automatically inherit the onDomRefresh code.
});
I've been hoping to use inheritance in Meteor, but I couldn't find anything about it in the documentation or on Stack Overflow.
Is it possible to have templates inheriting properties and methods from another abstract template, or class?
I think the short answer is no, but here's a longer answer:
One thing I've done to share functionality among templates is to define an object of helpers, and then assign it to multiple templates, like so:
var helpers = {
displayName: function() {
return Meteor.user().profile.name;
},
};
Template.header.helpers(helpers);
Template.content.helpers(helpers);
var events = {
'click #me': function(event, template) {
// handle event
},
'click #you': function(event, template) {
// handle event
},
};
Template.header.events(events);
Template.content.events(events);
It's not inheritance, exactly, but it does enable you to share functionality between templates.
If you want all templates to have access to a helper, you can define a global helper like so (see https://github.com/meteor/meteor/wiki/Handlebars):
Handlebars.registerHelper('displayName',function(){return Meteor.user().profile.name;});
I've answered this question here. While the solution doesn't use inheritance, it allow you to share events and helpers across templates with ease.
In a nutshell, I define an extendTemplate function which takes in a template and an object with helpers and events as arguments:
extendTemplate = (template, mixin) ->
helpers = ({name, method} for name, method of mixin when name isnt "events")
template[obj.name] = obj.method for obj in helpers
if mixin.events?
template.events?.call(template, mixin.events)
template
For more details and an example see my other answer.
Recently, I needed the same functionality in my app so I've decided to create my own package that will do that job out of the box. Although it's still work in progress, you can give it a go.
Basically, the entire method is as follows:
// Defines new method /extend
Template.prototype.copyAs = function (newTemplateName) {
var self = this;
// Creating new mirror template
// Copying old template render method to keep its template
var newTemplate = Template.__define__(newTemplateName, self.__render);
newTemplate.__initView = self.__initView;
// Copying helpers
for (var h in self) {
if (self.hasOwnProperty(h) && (h.slice(0, 2) !== "__")) {
newTemplate[h] = self[h];
}
}
// Copying events
newTemplate.__eventMaps = self.__eventMaps;
// Assignment
Template[newTemplateName] = newTemplate;
};
In your new template (new_template.js) in which you want to extend your abstract one, write following:
// this copies your abstract template to your new one
Template.<your_abstract_template_name>.copyAs('<your_new_template_name>');
Now, you can simply either overwrite your helpers or events (in my case it's photos helper), by doing following:
Template.<your_new_template_name>.photos = function () {
return [];
};
Your will refer to overwritten helper methods and to abstract ones that are not overwritten.
Note that HTML file for new template is not necessary as we refer to abstract one all the time.
Source code is available on Github here!