How do I add a custom method to a backbone model? - backbone.js

I've tried:
initialize: function() {
if (this.get("id") == "modelOfInterest") {
var func = function() {
//do some stuff with the model
}
_.bind(func, this)
}
}
and
initialize: function() {
if (this.get("id") == "modelOfInterest") {
var func = function() {
//do some stuff with the model
}
this.on("func", func, this);
}
}
However in both cases:
myModelInstance.func(); //object has no method func
I'd prefer not to use _.bindAll().
I've edited the code above to show that I am trying to bind func to only one model. The model is initialize when it is added to a collection : all the models fire initialize at the same time and I just want to bind func to one of them.

Any reason not to do the obvious?
Model = Backbone.Model.extend({
func: function() {
},
})

Assign func as a property of your model in your if block.
var Model = Backbone.Model.extend({
initialize:function() {
if (this.get('id') === 1) {
this.func = function() {
// your logic here
};
this.on('func',this.func,this);
}
}
});

Static methods should be declared on a second dictionary within the .extend call:
SomeModel = Backbone.Model.extend({
initialize: function(){}
},{
someStaticFunction: function(){
//do something
}
});
http://danielarandaochoa.com/backboneexamples/blog/2013/11/13/declaring-static-methods-with-backbone-js/

Try this:
SomeModel = Backbone.Model.extend({
initialize: function(){},
someFunction: function(){
//do something
}
});
And this:
var model = new SomeModel();
model.someFunction();

Related

Overriding Knockout View-Model function

I am using knockout and backbone in my application. My test-view.js look like this:
define([
"knockout",
"./base",
"./../viewmodels/test-vm",
"text!./../templates/test-template.html"
],
function(ko, BaseView, TestViewModel, template) {
var TestView = BaseView.extend({
template: template,
initialize: function() {
this.viewModel = new TestViewModel();
},
render: function(){
this.$el.html(template);
return this;
},
postRender: function() {
ko.applyBindings(this.viewModel, this.el);
}
});
return TestView;
});
test-template.html:
<button class="btn" data-bind="click: test">test</button>
and test-vm.js as follows:
define([],
function() {
function TestViewModel() {
var self = this;
self.test = function () {
alert("in test view model");
};
}
return TestViewModel;
});
When I click button, self.test is invoked. My question is how can I extend TestViewModel in another file and override test function to do some specific things? Thanks in advance!
I don't see any reason you can't use the commonly used "Classical inheritance with Object.create" approach as described here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
// Define the base class
var TestViewModel = function () {
this.sharedProperty = true;
};
TestViewModel.prototype.test = function() {
log("testing TestViewModel");
};
// Define the extended class
var ExtendedTestViewModel = function() {
TestViewModel.call(this);
};
// Copy the prototype and reset the constructor
ExtendedTestViewModel.prototype = Object.create(TestViewModel.prototype);
ExtendedTestViewModel.constructor = ExtendedTestViewModel;
// Override the inherited `test` function
ExtendedTestViewModel.prototype.test = function() {
// To call the base method:
// (skip this if you want to completely override this function)
TestViewModel.prototype.test.call(this);
// Add functionality:
log("testing ExtendedTestViewModel");
};
// Create an instance
var etvm = new ExtendedTestViewModel();
// This will log both the inherited message, as well as the extended message
etvm.test();
// ExtendedTestViewModel has all properties that are in TestViewModel
log(etvm.sharedProperty);
// utils
function log(msg) {
var pre = document.createElement("pre");
pre.appendChild(document.createTextNode(msg));
document.body.appendChild(pre);
};

Backbone defaults view events

Is it possible to make a set of default events which exist in every view? For example if every view in my application includes a settings button
events: {
"click #settings" : "goSettings"
},
...
goSettings: function() {
// settings show function
});
How can I can package this event to be included in every view in my application?
The problem is that View#extend simply overwrites existing properties so you can't put your 'click #settings' in a base class and subclass that. However, you can easily replace extend with something of your own that merges events. Something like this:
var B = Backbone.View.extend({
events: {
'click #settings': 'goSettings'
}
}, {
extend: function(properties, classProperties) {
properties.events = _({}).extend(
properties.events || { },
this.prototype.events
);
return Backbone.View.extend.call(this, properties, classProperties);
}
});
And then extend B instead of Backbone.View for your views.
Demo: http://jsfiddle.net/ambiguous/Kgh3V/
You can create a base view with the event(s) and functions, then make your other views inherit from it. I like the pattern described here, because it's simple to set up and easy to override as needed: http://www.scottlogic.com/blog/2012/12/14/view-inheritance-in-backbone.html
A base view looks like this:
var BaseSearchView = function(options) {
this.inheritedEvents = [];
Backbone.View.call(this, options);
}
_.extend(BaseView.prototype, Backbone.View.prototype, {
baseEvents: {},
initialize: function(options) {
// generic initialization here
this.addEvents({
"click #settings" : "goSettings"
});
this.initializeInternal(options);
},
render: function() {
// generic render here
this.renderInternal();
return this;
},
events: function() {
var e = _.extend({}, this.baseEvents);
_.each(this.inheritedEvents, function(events) {
e = _.extend(e, events);
});
return e;
},
addEvents: function(eventObj) {
this.inheritedEvents.push(eventObj);
},
goSettings: function() {
// settings show function
}
});
BaseView.extend = Backbone.View.extend;
And your child classes like this:
var MyView = BaseView.extend({
initializeInternal: function(options) {
// do something
// add event just for this child
this.addEvents({
"click #differentSettings" : "goSettings"
});
},
renderInternal: function() {
// do something
}
});

Backbone inheritance, merging the render() function

So I have this current situation:
app.Ui.ModalView = Backbone.View.extend({
events: {
},
initialize: function() {
},
render: function() {
var that = this;
var model = this.model.toJSON();
that.$el.html(that.template(_.extend(this.params || {}, {
model: model,
})));
return this;
}
});
and then the inherited view:
app.Views.childView = kf.Ui.ModalView.extend({
template: JST["templates/app/blah/blah-edit.html"],
events: {
},
initialize: function() {
var that = this;
this.events = _.extend({}, app.Ui.ModalView.prototype.events, this.events);
app.Ui.ModalView.prototype.initialize.apply(this, arguments);
},
render: function(){
// add extra logic in this render function, to run as well as the inherited render function?
}
});
So, I don't want to override the parent's render(), but to add extra functionality to it, how would I go about doing that?
Two ways to achieve this: Either you can add explicit support for overriding the behaviour by creating a "render hook" in the base class, or you'll have to call the overridden base method from the superclass method:
Render hook in base class:
app.Ui.ModalView = Backbone.View.extend({
render: function() {
//if this instance (superclass) defines an `onRender` method, call it
if(this.onRender) this.onRender();
//...other view code
}
}
app.Views.childView = kf.Ui.ModalView.extend({
onRender: function() {
//your custom code here
}
});
Call base class method from super class:
app.Views.childView = kf.Ui.ModalView.extend({
render: function() {
//your custom code here
//call the base class `render` method
kf.Ui.ModalView.prototype.render.apply(this, arguments);
}
});

Is there a way to change Marionette ItemView template dynamically with RequireJS?

I'm trying to manipulate itemViews dynamically in a Marionette CollectionView. The collections have the same models, but i defined templateName argument inside the models.
The question is, can i manipulate the ItemView template by this argument?
ItemView:
define(['text!templates/ComponentItemViewTemplate.html','models/ComponentModel'], function (template, model) {
var ItemView = Backbone.Marionette.ItemView.extend({
template: _.template(template),
model: model
});
return ItemView;
});
CollectionView:
define(['views/ComponentItemView', 'views/LoadingView'], function(ItemView, LoadingView) {
var ComponentListView = Backbone.Marionette.CollectionView.extend({
emptyView : LoadingView,
id: "component-list",
itemView: ItemView,
events: {
'click .title span' : 'show'
},
appendHtml: function(collectionView, itemView, index){//i would like to render different templates, for different models.
itemView.$el.draggable({ helper: "clone", cancel: ".component .title span", connectToSortable: ".ui-sortable" });
collectionView.$el.append(itemView.el);
},
show: function(r) {
var target = $(r.target);
if( target.parent().hasClass('open') ){
target.parent().removeClass('open');
target.parent().next().slideDown('fast');
}else{
target.parent().addClass('open');
target.parent().next().slideUp('fast');
}
}
});
return ComponentListView;
});
Thanks!
You can override getTemplate function and write your custom logic there. The Marionette documentation recommends the following option:
MyView = Backbone.Marionette.ItemView.extend({
getTemplate: function(){
if (this.model.get("foo")){
return "#some-template";
} else {
return "#a-different-template";
}
}
});
I think gumballhead is on the right track. You can override the getTemplate function to do this.
MyCollectionView = Marionette.CollectionView.extend({
// ...
getItemView: function(item){
// get the template from the item... or wherever else it comes from
return new MyViewType({
template: item.get("the-template")
});
}
});
Hope that does what you need
First of all i'd like to thanks for everybody who tried to help me.
I resolved my own problem.
Here is the sollution, if somebody need it:
define(['models/ComponentModel'], function (model) {
var ItemView = Backbone.Marionette.ItemView.extend({
model: model,
render: function() {
var that = this;
var data = this.serializeData();
require(['text!templates/components/editor/' + that.model.get('editor_template') + '.html'], function(Template){
var html = _.template(Template, data);
that.$el.html(html);
});
}
});
return ItemView;
});
edited: (Better sollution)
Suggestions are welcome!

Backbone.js - Correct way of filtering and displaying collection data in a view

I have got a huge list of tasks loaded on the start.
I want to show them depending on selected list / inbox, so that there won't be additional loadings for each list.
window.Task = Backbone.Model.extend({});
window.TasksCollection = Backbone.Collection.extend({
model: Task,
url: '/api/tasks',
inbox: function() {
return this.filter(function(task) {
return task.get('list') == null;
});
},
list: function(id) {
return this.filter(function(task) {
return task.get('list') == id;
});
}
});
window.tasks = new TasksCollection;
window.TaskView = Backbone.View.extend({
tagName: 'li',
template: _.template($('#item-template').html()),
initialize: function() {
_.bindAll(this, 'render', 'close');
this.model.bind('change', this.render);
this.model.view = this;
},
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
this.setContent();
return this;
},
});
window.TasksView = Backbone.View.extend({
el: '#todo-list',
collection: tasks,
initialize: function() {
_.bindAll(this, 'render');
this.collection.bind('reset', this.render);
this.collection.fetch();
},
render: function() {
var t = this;
$(t.el).html('');
this.collection.each(function(task) {
var view = new TaskView({ model:task });
$(t.el).append( view.render().el );
});
return this;
},
});
window.Nicetask = Backbone.Router.extend({
routes: {
'': 'inbox',
'/inbox': 'inbox',
'/list/:id': 'list',
},
initialize: function() {
_.bindAll(this, 'inbox', 'list');
window.tasksView = new TasksView;
},
inbox: function() {
tasks.reset( tasks.inbox() );
},
list: function(id) {
tasks.reset( tasks.list(id) );
}
});
This code works, but the reset() function removes other tasks in actual list from tasks collection. And on another route, tasks collection is empty.
Is there any reasonable way to achieve this? thanks for any idea.
ps: backbone novice
UPDATE
Thx to #sled and #ibjhb for comments, here is snippet of working solution.
window.TasksView = Backbone.View.extend({
el: '#todo-list',
collection: Backbone.Collection.extend(),
initialize: function() {
_.bindAll(this, 'render', 'addOne', 'addAll');
this.collection.bind('add', this.addOne);
this.collection.bind('reset', this.render);
},
render: function(data) {
$(this.el).html('');
_.each(data, function(task) {
this.addOne(task);
}, this);
return this;
},
addOne: function(task) {
var view = new TaskView({ model:task });
$(this.el).append( view.render().el );
},
});
window.Nicetask = Backbone.Router.extend({
routes: {
'': 'inbox',
'/inbox': 'inbox',
'/today': 'today',
'/list/:id': 'list',
},
initialize: function() {
_.bindAll(this, 'inbox', 'today');
window.tasksView = new TasksView;
window.menuView = new MenuListView;
tasks.fetch();
},
inbox: function() {
tasksView.render( tasks.inbox() );
},
today: function() {
tasksView.render( tasks.today() );
},
list: function(id) {
tasksView.render( tasks.list(id) );
}
});
I think you need to use another collection. For example, in your inbox, do this:
inbox: function(){
currentCollection = new TasksCollection(tasks.inbox());
}
I haven't tested this but when you do a .reset(); you are removing all your models and loading the ones passed in.
#sled there's typos in the code you posted, see comments inline. Did you post this as a project somewhere?
// add models
add: function(models, options) {
// TYPO: next line was missing, so single models not handled.
models = _.isArray(models) ? models.slice() : [models];
var self = this;
models = _.filter(models, this.filter);
// return if no models exist
// TYPO: returned undefined, so was not chainable
if(models.length == 0) { return this; }
// actually add the models to the superset
this.superset.add(models, options);
return this;
},
// remove models
remove: function(models, options) {
// TYPO: next line was missing, so single models not handled.
models = _.isArray(models) ? models.slice() : [models];
// remove model from superset
this.superset.remove(_.filter(_.filter(models, function(cm) {
// TYPO: not 'm != null', causes error to be thrown
return cm != null;
}), this.filter), options);
// TYPO: missing return so not chainable
return this;
},
one quick amendment to you solution, you are using
$(this.el).html('');
My understanding is your the views and related event bindings will still exist in the browser memory, so you ideally need to use view.remove() on the TaskView to correctly clear the event bindings as well as the html.
This is a slightly different take on the answer as I have been looking for a solution to a similar problem, hope this may be of help to others.
My problem: - to filter a complete collection by attributes of the model. eg. a user clicks the models view, gets a list of (some of) the attributes, selecting an attribute filters the collection to only show ones with the same value.
The route I am taking is by calling a method on the collection from the view, in my case the view is specific to a model so:
this.model.collection.myFilter(attr,val);
where attr is an attribute of the model associated with the collection, then in the filter something like
myFilter: function(attr, val){
var groupByAttr = this.groupBy(function(article){
var res = (val === undefined)? true : (article.get(attr) == val);
article.set({selected:res});
return res;
});
return groupByAttr;
}
I have used ._groupBy as this returns 2 arrays (positive / negative) that may be of use. By setting the mode attribute "selected", and binding to this in the model view I can easily toggle a class which shows or hides the view.
if(val === undefined) is added as a simple way of clearing a filter by calling the same method without a value.

Resources