BackboneJS How to pass a value between modules - javascript

I want to display a list of artists based on the letter users can click on an alphabet. But somehow, even though the letter is passed, I get an error e.g. it says the letter is undefined despite the fact my console.log gives me the correct letter. Here is what I did sofar:
My artistsAll.js module:
var ArtistsAll = App.module();
var ArtistsAllView = Backbone.View.extend({
tagName : 'li',
template: 'artistItem',
serialize: function() {
return this.model.toJSON();
}
});
ArtistsAll.View = Backbone.View.extend({
tagName : 'ul',
className : 'artistList',
initialize: function() {
this.collection.on('sync', _.bind(this.render, this));
},
beforeRender: function() {
var self = this;
this.collection.each(function(item) {
self.insertView(new ArtistsAllView({model: item}))
})
}
});
ArtistsAll.ArtistsAllCollection = Backbone.Collection.extend({
url: function() {
return App.APIO + '/i/search/artist?name=' + this.letter;
}
});
return ArtistsAll;
Basically, there is an endpoint for each letter, for example /i/search/artist?name=b
Then, in my controller I have:
var ArtistsAllModule = require('modules/artistsAll');
ArtistController.prototype.initArtistLetter = function(letter) {
this.artistsAllCollection = new ArtistsAllModule.ArtistsAllCollection();
this.artistsAllCollection.letter = letter;
App.useLayout('artistLetter', 'artistLetter').setViews({
'.artistsDiv': new ArtistsAllModule.View({collection: this.artistsAllCollection});
}).render();
this.artistsAllCollection.fetch();
};
All I get, is empty page, no errors.... can someone tell me what the issue is here?

Try to modify your code like the following:
ArtistsAll.View = Backbone.View.extend({
tagName : 'ul',
className : 'artistList',
initialize: function() {
// here's the modification
this.listenTo(this.collection, 'sync', this.render, this); // but where's your render function!
},

Related

Filter Backbone collection and keep original

In the following code, I have set up a view where I am filtering a collection based on a date input. The code works correctly the first time, but obviously the collection resets and then if I filter by date again it doesn't filter from the original collection. What is the best practice to filter collections in backbone and maintain a copy of the original?
window.DataIndexView = Backbone.View.extend({
tagName: 'section',
className: 'data',
events: {
"click #changedate" : "filterDate"
},
initialize: function(){
_.bindAll(this, 'render', 'filterDate');
.template = _.template($("#data_template").html());
this.collection.bind('reset', this.render());
},
render: function(){
var data = this.collection;
$(this.el).html(this.template({}));
data.each(function(point){
});
return this;
},
filterDate: function() {
var startdate = this.$("#startdate").val();
var filtered = _.filter(this.collection, function(item) {
return moment(item.get("date")).valueOf() > moment(startdate, 'MM-DD-YYYY').valueOf();
});
this.collection.reset(filtered);
}
});
_.filter DOES NOT touch your collection. it returns a brand new array. You can use it to instantiate a new collection, if you wish.
I believe what you want can be achieved with something like
filterDate: function() {
var startdate = this.$("#startdate").val();
// Basically a collection clone. It'll keep the same references,
// instead of copying each object
this.original_collection = new YourCollectionType(this.collection.models);
this.collection.reset(_.filter(this.collection, function(item) {
return moment(item.get("date")).valueOf() > moment(startdate, 'MM-DD-YYYY').valueOf();
}));
// Render triggered automagically.
}
You should not filter your collection inside the view. A better way is to create a FilteredCollection from your original collection.
DateFilteredCollection
function DateFilteredCollection(original){
var filtered = new original.constructor();
filtered.filter = function(startdate){
var filteredItems = _.filter(this.collection, function(item) {
return moment(item.get("date")).valueOf() > moment(startdate, 'MM-DD-YYYY').valueOf();
});
filtered.reset(filteredItems);
};
original.on('reset', function(){
filtered.reset(original.models);
});
return filtered;
}
DataIndexView
window.DataIndexView = Backbone.View.extend({
tagName: 'section',
className: 'data',
events: {
"click #changedate" : "filterDate"
},
initialize: function(){
_.bindAll(this, 'render', 'filterDate');
.template = _.template($("#data_template").html());
this.collection.bind('reset', this.render());
},
render: function(){
var data = this.collection;
$(this.el).html(this.template({}));
data.each(function(point){
});
return this;
},
filterDate: function() {
var startdate = this.$("#startdate").val();
this.collection.filter(startdate); // use filter function here
}
});
Creating your view
var original = new OriginalCollection(); // your original collection
var filteredCollection = DateFilteredCollection( original );
var dataIndexView = new window.DataIndexView({
collection: filteredCollection
});
original.fetch(); // fetch the data -> filteredCollection will automatically filter it.

backbone - trying to rerender a view with the next or previous model in a collection

I am trying to get into backbone and have the following which is an attempt at doing an image gallery. I am trying to use render with a model in a collection. I will show the first element of the collection but I would like to add support for simply rerendering with the next element but I don't know how to do this .
I have implemented next and previous on my model like the following:
arc.Item = Backbone.Model.extend({
next: function () {
if (this.collection) {
return this.collection.at(this.collection.indexOf(this) + 1);
}
},previous: function () {
if (this.collection) {
return this.collection.at(this.collection.indexOf(this) - 1);
}
}
});
The problem here (there could be more than the one I am asking about though) is in the loadNext method. How would I get the current location in this collection and to increment it?
arc.ItemsGalleryView = Backbone.View.extend({
el: $('#mig-container'),
events: {'click .next-btn' : 'loadNext',
'click .previous-btn':'loadPrevious' },
template:_.template($('#mig-image-tmp').text()),
initialize: function() {
_.bindAll( this, 'render' );
// render the initial state
var thisModel=this.collection.first();
this.render(thisModel);
},
render: function(xModel) { // <- is it ok to do it this way?
var compiled=this.template(xModel.toJSON());
this.$el.html(compiled);
return this;
},
loadNext: function(){
console.log('i want to load Next');
this.render(this.collection.next()); // <- how would I do this
this.render(this.collection.first().next()); // <- this works by always giving me the second.
// I need to replace first with current
},
loadPrevious: function(){
console.log('i want to load Previous');
}
Or is there a better way to implement this?
thx in advance
edit #1
arc.ItemsGalleryView = Backbone.View.extend({
el: $('#mig-container'),
events: {'click .next-btn' : 'loadNext', 'click .previous-btn':'loadPrevious' },
template:_.template($('#mig-image-tmp').text()),
initialize: function() {
_.bindAll( this, 'render' );
this.render(this.collection.first()); // <- this works correct
},
render: function(xModel) {
console.log(xModel.toJSON());
var compiled=this.template(xModel.toJSON());
this.$el.html(compiled);
return this;
},
loadNext: function(){
console.log('i want to load next');
this.render(this.collection.next()); // <- this doesn't seem to do anything, event is called correctly but doesn't seem to move to next element
},
However if I adjust to this, it will load the 3rd element of the array
loadNext: function(){
console.log('i want to load Previous');
this.render(this.collection.at(2));
},
How would I use this.collection.next() to get this behavior?
thx
What it looks like you're looking for is a way to use the Collection to manipulate the next/prev stuff. What you currently have only puts it on the model. Here's a base Collection I use in my projects:
App.Collection = Backbone.Collection.extend({
constructor: function(models, options) {
var self = this;
var oldInitialize = this.initialize;
this.initialize = function() {
self.on('reset', self.onReset);
oldInitialize.apply(this, arguments);
};
Backbone.Collection.call(this, models, options);
},
onReset: function() {
this.setActive(this.first());
},
setActive: function(active, options) {
var cid = active;
if ( active instanceof Backbone.Model ) {
cid = active.cid;
}
this.each(function(model) {
model.set('current', model.cid === cid, options);
});
},
getActive: function() {
return this.find(function(model) {
return model.get('current');
});
},
next: function() {
return this.at(this.indexOf(this.getActive()) + 1);
},
prev: function() {
return this.at(this.indexOf(this.getActive()) - 1);
}
});
It's probably not perfect, but it works for me. Hopefully it can at least put you on the right track. Here is how I use it:
var someOtherCollection = App.Collection.extend({
model: MyModel
});
kalley's answer is right on, but I will throw in another example.
I would entertain the idea of keeping the current model inside of state model, and work from there. You can store other application information within the state model as well.
Your model declaration would look like the following. Notice I renamed previous to prev. Backbone.Model already has a previous method and we don't want to over-ride it.
var Model = Backbone.Model.extend({
index:function() {
return this.collection.indexOf(this);
},
next:function() {
return this.collection.at(this.index()+1) || this;
},
prev:function() {
return this.collection.at(this.index()-1) || this;
}
});
Have a generic Backbone.Model that holds your selected model:
var state = new Backbone.Model();
In the view you will listen for changes to the state model and render accordingly:
var View = Backbone.View.extend({
el: '#mig-container'
template:_.template($('#mig-image-tmp').html()),
events: {
'click .prev' : 'prev',
'click .next' : 'next'
},
initialize:function() {
this.listenTo(state,'change:selected',this.render);
state.set({selected:this.collection.at(0)});
},
render:function() {
var model = state.get('selected');
this.$el.html(this.template(model.toJSON()));
return this;
},
next:function() {
// get the current model
var model = state.get('selected');
/* Set state with the next model, fires a change event and triggers render.
Unless you are at the last model, then no event will fire.
*/
state.set({selected:model.next()});
},
prev:function() {
var model = state.get('selected');
state.set({selected:model.prev()});
}
});
Here is a demo. I like the state model approach because I'm not storing application-state information within my models.
If you don't like the state model approach, you can always just throw it on the floor:
/ .. code above ../
initialize:function() {
this.model = this.collection.at(0);
this.render();
},
render:function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
next:function() {
this.model = this.model.nxt();
this.render();
},
prev:function() {
this.model = this.model.prev();
this.render();
}

Backbone view not listeningTo a model change

Data structure:
- Measure (collection)
- Measure (model)
- beats (c)
- beat (m)
- on/off (attribute)
- representations (c)
- representation (m)
- currentType (attribute)
- previousType (a)
The representation model is getting called via the transition function, and I notice the change via a console printout, however, the View is not registering the change at all. I have access to click events, so I know the view's el is correct. Why is the listenTo not working in the view?
representaiton model:
define([
'underscore',
'backbone'
], function(_, Backbone) {
var RepresentationModel = Backbone.Model.extend({
initialize: function(options){
this.representationType = options.representationType;
this.previousRepresentationType = undefined;
},
transition: function(newRep){
this.previousRepresentationType = this.representationType;
this.representationType = newRep;
console.error('model change : ' + this.previousRepresentationType + ' ' + this.representationType);
}
});
return RepresentationModel;
});
measureRepresentation View:
define([…], function(…){
return Backbone.View.extend({
initialize: function(options){
if (options) {
for (var key in options) {
this[key] = options[key];
}
}
//Dispatch listeners
…
//Binding
//this was the old way, so I changed to the new listenTo to take advantage of when the view is destroyed.
//this.model.bind('change', _.bind(this.transition, this));
this.listenTo(this.model, 'change', _.bind(this.transition, this));
this.render();
},
render: function(){
// compile the template for a representation
var measureRepTemplateParamaters = {…};
var compiledTemplate = _.template( MeasureRepTemplate, measureRepTemplateParamaters );
// put in the rendered template in the measure-rep-container of the measure
$(this.repContainerEl).append( compiledTemplate );
this.setElement($('#measure-rep-'+this.measureRepModel.cid));
// for each beat in this measure
_.each(this.parentMeasureModel.get('beats').models, function(beat, index) {
measurePassingToBeatViewParamaters = {…};
};
new BeatView(measurePassingToBeatViewParamaters);
}, this);
return this;
},
transition: function(){
console.warn('getting in here'); //NEVER GET HERE
console.log(this.model.get('previousRepresentationType') + '|' + this.model.get('representationType'));
}
});
});
change events only fire when you use model.set to make the changes. You can't just assign new properties. Backbone doesn't use defineProperty style, it's a more explicit style.
this.set({
previousRepresentationType: this.representationType,
representationType: newRep
});

Backbone: Pass Model Attribute to a View returns undefined

var ShapeSizePoolView = Backbone.View.extend({
el : $('#agpb_shape_size_body'),
tmpl : $('#tmpl_agpb_shape_size').html(),
initialize : function() {
this.render();
},
render : function() {
var compiled_template = _.template( this.tmpl, this.model.toJSON() ),
sizes = this.model.get('sizes');
$(this.el).append( compiled_template );
// this is used for our pool sizes
for(var i = 0; i < sizes.length; i++) {
console.log(sizes[i]);
new ShapeSizePoolButtonView(
{
size : sizes[i],
el : $(this.el).find('.agpb_size_list')
});
}
}
});
var ShapeSizePoolButtonView = Backbone.View.extend({
tmpl : $('.tmpl_agpb_shape_size_button').html(),
initialize : function() {
// this.render();
console.log( this.size );
},
render : function() {
var compiled_template = _.template( this.tmpl, this.sizes );
$(this.el).append( compiled_template );
}
});
this.model.get('sizes') returns an array of objects. If I console.log one of the objects in ShapeSizePoolView I get:
{
id: "6",
dimensions: "12'",
price: "649.99",
sort_order: "1"
}
I pass this object to a new view but if I console.log this.size from ShapeSizePoolButtonView I get:
undefined
Does anyone have an idea of where I am going wrong?
Thanks!
From the Backbone docs:
When creating a new View, the options you pass — after being merged
into any default options already present on the view — are attached to
the view as this.options for future reference. There are several
special options that, if passed, will be attached directly to the
view: model, collection, el, id, className, tagName and attributes.
So, you can access this custom option by using this.options:
var ShapeSizePoolButtonView = Backbone.View.extend({
tmpl : $('.tmpl_agpb_shape_size_button').html(),
initialize : function() {
// this.render();
console.log( this.options.size ); // Not this.size
},
....
});
You are passing size to the View constructor, but it will not get copied onto this automatically. You would need to add code like this:
var ShapeSizePoolButtonView = Backbone.View.extend({
initialize : function(options) {
this.size = options.size;
// this.render();
console.log( this.size );
},
Also, you are incorrectly using this.sizes instead of this.size in your render call.

Backbone.js & localstorage

Trying to get my head around backbone.js. This example is using Backbone Boilerplate and Backbone.localStorage and I'm coming up against a confusing problem; When quizes.create(...) is called I get this error:
backbone.js:570 - Uncaught TypeError: object is not a function
model = new this.model(attrs, {collection: this});
Quiz module code:
(function(Quiz) {
Quiz.Model = Backbone.Model.extend({ /* ... */ });
Quiz.Collection = Backbone.Collection.extend({
model: Quiz,
localStorage: new Store("quizes")
});
quizes = new Quiz.Collection;
Quiz.Router = Backbone.Router.extend({ /* ... */ });
Quiz.Views.Question = Backbone.View.extend({
template: "app/templates/quiz.html",
events: {
'click #save': 'saveForm'
},
initialize: function(){
_.bindAll(this);
this.counter = 0;
},
render: function(done) {
var view = this;
namespace.fetchTemplate(this.template, function(tmpl) {
view.el.innerHTML = tmpl();
done(view.el);
});
},
saveForm: function(data){
if (this.counter <= 0) {
$('#saved ul').html('');
}
this.counter++;
var titleField = $('#title').val();
console.log(quizes);
quizes.create({title: titleField});
}
});
})(namespace.module("quiz"));
In your Collection, you're naming model as your Quiz object, not the actual Quiz.Model. So, when you call new this.model(), you're actually calling Quiz() - which is an object, not a function. You need to change the code to:
Quiz.Collection = Backbone.Collection.extend({
model: Quiz.Model, // Change this to the actual model instance
localStorage: new Store("quizes")
});

Categories