I am trying to create a singleton object as a model for a view on backbone, and I want to re-render the view whenever this singleton object is being changed and updated. I am not sure the following code would be the right way of doing it or not
Model
define(function(require) {
var Singleton = require("modules/Singleton");
var Singleton = null;
var SingletonHolder = {
init: function() {
Singleton = new Singleton();
return Singleton.fetch();
},
current: function() {
return Singleton;
},
refresh: function() {
return Singleton.fetch();
}
};
return SingletonHolder;
});
Controller
var currentObj = SingletonHolder.current();
var tempView = new TempView({
model: currentObj
});
View
var TempView = Backbone.View.extend({
initialize: function() {
this.listenTo(this.model, "change", this.render);
}
render: function() {
...
}
});
For some reasons it doesn't work. Did i miss anything ? I also tried to call Singleton.refresh which it goes to the server side and fetches the latest data from the database, but it doesn't detect the changes and re-render the view.
You don't have to define a Singleton if you already use requirejs.
Singleton:
define(['models/foo'], function(FooModel){
return new FooModel;
})
Controller:
define(['foosingleton', 'tempview'], function(fooSingleton, TempView){
var tempView = new TempView({
model: fooSingleton
});
});
Here is a similar question: Is it a bad practice to use the requireJS module as a singleton?
Related
I am learning Backbone and JavaScript. I am stuck at one error and this error might be related to pure JavaScript and not Backbone.
While going through tutorial of Backbone (does not uses requireJS), I found below line of code.
var FirstSubViewModel = Backbone.View.extend({
render: function() {
var source = $("#vehicleTemplate").html();
var template = _.template(source);
this.$el.html(template(this.model.toJSON()));
this.$el.attr("data-color", this.model.get("color"));
return this;
},
});
We can clearly see that code returns this, and everything works fine.
Now I am trying to do the same in my own code base (I have used require.JS.
My view Model: With Error: Not Working
define(['backbone'], function(Backbone) {
var FirstSubViewModel = Backbone.View.extend({
template: _.template($('#myChart-template').html()),
render: function() {
$(this.el).html(this.template());
var ctx = $(this.el).find('#lineChart')[0];
return this;
},
initialize: function() {
this.render();
}
});
});
My Controller:
define(['backbone', 'FirstSubViewModel'], function(Backbone, FirstSubViewModel)
{ var ch = new dashboardModel.chart({});
new FirstSubViewModel({
^^ HERE I GET ERROR
el: '#chartAnchor1',
model: ch
});
});
My view Model: Working Totally Fine
define(['backbone'], function(Backbone) {
var FirstSubViewModel = Backbone.View.extend({
template: _.template($('#myChart-template').html()),
render: function() {
$(this.el).html(this.template());
var ctx = $(this.el).find('#lineChart')[0];
// return this; Commented Out!**
},
initialize: function() {
this.render();
}
});
return FirstSubViewModel;
});
Everything works fine if I use return FirstSubViewModel at the bottom rather than having return this in the render function . But I want to try return this in the render function and want everything to work. I don't want to do return FirstSubViewModel in the end.
Error when used "return this" in the render function:
FirstSubViewModel is not a constructor
Last return defines what will be included in other modules and in this case is required. Without this this module return undefined.
Lets try in console:
x = undefined
new x()
TypeError: x is not a constructor
return FirstSubViewModel is mandatory. return this in render function is recommended.
Code:
define(['backbone'], function (Backbone) {
var FirstSubViewModel = Backbone.View.extend({
template: _.template($('#myChart-template').html()),
render: function () {
console.log("inside first sub view render");
$(this.el).html(this.template());
var ctx = $(this.el).find('#lineChart')[0];
return this;
},
initialize: function () {
this.render();
}
});
return FirstSubViewModel; // Works as expected.
});
Have got some problems with sorting and rendering data with backbone.js
There is sorting by 'title' in comparator.
This.model.collection has models after sorting by title, but when rendering starts models views after sorting by order.
var TodoList = Backbone.Collection.extend({
model: Todo,
comparator: function(todo) {
return todo.get('title');
},
//function for sorting
sortByDate: function () {
this.comparator = function(todo){
return todo.get('title');
};
this.sort();
}
});
var TodoView = Backbone.View.extend({
tagName: "li",
template: _.template($('#item-template').html()),
initialize: function() {
this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'destroy', this.remove);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
Providing a comparator for a collection just ensures that it's models are maintained in a sorted order, if you want to render it in that order you just need to retrieve the models from the collection (most commonly this is done in your collection view) and render them.
For example in your case since you don't have a collection view you can do the following
todoList.each(function (todo) {
$('#output').append(new TodoView({model: todo}).el);
});
Generally though, you would have this code as part of your collection view. You might also want to maintain a reference to your views so you can easily re-render or remove them. for example
var TodoCollectionView = Backbone.View.extend({
views: {},
render: function () {
var frag = document.createDocumentFragment();
this.collection.each(function (model) {
var view = this.viewForModel(model);
frag.appendChild(view.render().el);
},this);
this.$el.html(frag);
},
viewForModel: function (model) {
var view;
if (this.views[model.cid]) {
view = this.views[model.cid];
} else {
view = new TodoView({model: model});
this.views[model.cid] = view;
}
return view;
}
});
Here's a link to a jsbin
app.Model.Brand = Backbone.Model.extend({});
app.Collection.BrandCollection = Backbone.Collection.extend({
model: app.Model.Brand,
parse: function (response) {
return response.brandDTOList;
}
});
var brandcollection = new app.Collection.BrandCollection();
brandcollection.url = '/brands';
brandcollection.fetch({
success: function (collection, response, options) {
app.views.brandline = new app.View.BrandPanelView({
model: brandcollection
});
$('#tab-content').empty();
$('#tab-content').append(app.views.brandline.render());
}
});
In this view a single model is passed.
app.View.BrandItemPanelView = Backbone.View.extend({
initialize: function (options) {
this.views = {};
this.model.bind('destroy', this.remove, this);
},
events: {
'click .bra-edt': 'brandEditAction',
},
brandEditAction: function () {
this.model.get('image');
}
});
When i do this.model.get('image'); i get get is not a function.
I am not sure why i am getting such a error.
I tried to deep clone it but still no success. The values are there.
var newModel = new app.Model.Brand();
newModel = $.extend(true, {}, self.model);
newModel.get('image')
Your problem is where you are passing in your BrandCollection, instead of passing it in as a collection you are passing it in as a model.
So instead of
app.views.brandline = new app.View.BrandPanelView({
model: brandcollection
});
You probably want to do
app.views.brandline = new app.View.BrandPanelView({
collection: brandcollection
});
How are you instantiating the BrandItemPanelView?
You will have to pass in the model explicitly:
var view = new app.View.BrandItemPanelView({ model: myModel });
If myModel is in fact a Backbone model, it ought to have the get function...
view.model // should return your model
view.brandEditAction() // should run the function and get the 'image' attribute from your model
I am trying to return profile.toJSON() to an object so as to use it outside the above code. I am not understanding exactly how backbone function works, so i declare a global variable obj and trying to parse data with obj = profile.toJSON(). When i use console.log(obj) it displays successfully my data. When I put console outside the above code it returns underfined.
var obj;
var ProfileView = Backbone.View.extend(
{
el: "#profiles",
template: _.template($('#profileTemplate').html()),
render: function(eventName)
{
_.each(this.model.models, function(profile)
{
var profileTemplate = this.template(profile.toJSON());
obj = profile.toJSON();
$(this.el).html(profileTemplate);
}, this);
return this;
}
});
You're taking it by the wrong end. Precreate your model and pass it to a view. Don't try to extract something from the view rendering code, it's not meant to be used this way.
var Profile = Backbone.Model.extend({});
var ProfileCollection = Backbone.Collection.extend({
model: Profile
});
var ProfileListView = Backbone.View.extend({
...
// Everything render does is rendering
render: function() {
this.collection.each(function(model) {
this.$el.append(
this.template(model.toJSON);
);
}, this);
}
...
});
// Your profile instance is defined outside the view, making
// it de facto available to outside code
var profile = new Profile({
name: 'Fere Res',
rep: 48
});
// The profile we just created gets added to a collection
var profiles = new ProfileCollection([profile]);
// We create the profile list view and pass it the collection
var view = new ProfileListView({collection: profiles});
// When we render the view, the render() code defined above is called.
// You can easily see that all the params/variables it uses are in place
view.render();
// Rendering is done, let's check our model is still available
console.log(profile.toJSON()); // :)
I ve got this code which actually fetch data from json file:
$(function() {
var Profile = Backbone.Model.extend({
defaults: {
tstamp: "",
map:"",
tagsCloud:"",
sentiment: "",
usersCloud: "",
timeline: "",
highlights: "",
signals: ""
},
initialize: function() {
}
});
var ProfileList = Backbone.Collection.extend({
model: Profile,
url: 'data.json'
});
var ProfileView = Backbone.View.extend({
el: "#profiles",
template: _.template($('#profileTemplate').html()),
render: function(eventName) {
_.each(this.model.models, function(profile){
var profileTemplate = this.template(profile.toJSON());
//obj = profile.toJSON();
//console.log(obj.tstamp);
$(this.el).html(profileTemplate);
}, this);
return this;
}
});
var profiles = new ProfileList();
var profilesView = new ProfileView({model: profiles});
setInterval(function() {
profiles.fetch({reset: true});
}, 400); // Time in milliseconds
profiles.bind('reset', function () {
profilesView.render();
});
});
I tried to add profiles to a new collection:
var profiles1 = new ProfileList([profiles]);
var view = new ProfileView({collection: profiles1});
view.render();
console.log(profile.toJSON());
I ve got console message: Cannot read property 'models' of undefined
I'm relatively new to Backbone and though I know the general idea of how to use it, my learning has been rapid and I'm probably missing some key elements.
So I have a collection that contains an attribute called "type" which can be article, book, video, class. I have the view rendering and everything but I need to be able to filter the collection when links are clicked.
My question is - how can I get it to filter down the collection and still be able to refilter the original collection when I click on another type?
Here's the gist of my code, I simplified it for easy reading:
var TagsView = Backbone.View.extend({
initialize: function(query) {
this.collection = new TagsCollection([], {query: self.apiQuery} );
this.collection.on('sync', function() {
self.render();
});
this.collection.on('reset', this.render, this);
},
render: function() {
//renders the template just fine
},
filter: function() {
//filtered does work correctly the first time I click on it but not the second.
var filtered = this.collection.where({'type':filter});
this.collection.reset(filtered);
}
});
update: I managed to get this working. I ended up triggering a filter event.
var TagsCollection = Backbone.Collection.extend({
initialize: function(model, options) {
this.query = options.query;
this.fetch();
},
url: function() {
return '/api/assets?tag=' + this.query;
},
filterBy: function(filter) {
filtered = this.filter(function(asset) {
return asset.get('type') == filter;
});
this.trigger('filter');
return new TagsCollection(filtered, {query: this.query});
},
model: AssetModel
});
And then in my view, I added some stuff to render my new collection.
var TagsView = Backbone.View.extend({
initialize: function(query) {
this.collection = new TagsCollection([], {query: self.apiQuery} );
this.collection.on('sync', function() {
self.render();
});
this.collection.on('filter sync', this.filterTemplate, this);
this.collection.on('reset', this.render, this);
},
render: function() {
//renders the template just fine
},
filterCollection: function(target) {
var filter = $(target).text().toLowerCase().slice(0,-1);
if (filter != 'al') {
var filtered = this.collection.filterBy(filter);
} else {
this.render();
}
},
filterTemplate: function() {
filterResults = new TagsCollection(filtered, {query: self.apiQuery});
console.log(filterResults);
$('.asset').remove();
filterResults.each(function(asset,index) {
dust.render('dust/academy-card', asset.toJSON(), function(error,output) {
self.$el.append(output);
});
});
},
});
The reason it's not working a second time is because you're deleting the models that don't match your filter when you call reset. That's normal behaviour for the reset function.
Instead of rendering with the view's main collection, try using a second collection just for rendering which represents the filtered data of the original base collection. So your view MIGHT look something like:
var TagsView = Backbone.View.extend({
filter: null,
events: {
'click .filter-button': 'filter'
},
initialize: function (query) {
this.baseCollection = new TagsCollection([], {query: self.apiQuery} );
this.baseCollection.on('reset sync', this.filterCollection, this);
this.collection = new Backbone.Collection;
this.collection.on('reset', this.render, this);
},
render: function () {
var self = this,
data = this.collection.toJSON();
// This renders all models in the one template
dust.render('some-template', data, function (error, output) {
self.$el.append(output);
});
},
filter: function (e) {
// Grab filter from data attribute or however else you prefer
this.filter = $(e.currentTarget).attr('data-filter');
this.filterCollection();
},
filterCollection: function () {
var filtered;
if (this.filter) {
filtered = this.baseCollection.where({'type': this.filter});
} else {
filtered = this.baseCollection.models;
}
this.collection.reset(filtered);
}
});
To remove any filters, set a button with class filter-button to have an empty data-filter attribute. collection will then be reset with all of baseCollection's models
Here's a better answer to this. Instead of making it so complicated, you can just use the where method. Here's my replacement solution for the question above.
filterby: function(type) {
return type === 'all' ? this : new BaseCollection(this.where({type: type});
});
You can try using comparator function of your Collection.
http://backbonejs.org/#Collection-comparator
Basically its is like sorting your collection.