Does BackboneFire support non-AutoSync collection with AutoSync model - javascript

Basically I want this set up:
var Game = Backbone.Model.extend({
defaults: {
},
autoSync: true
});
var Games = Backbone.Firebase.Collection.extend({
url: 'https://<myapp>.firebaseio.com/games',
autoSync: false,
model: Game
});
Each Game should be auto-synced with server data but I do not not want to listen to the entire child_* Firebase family of events. The goal is to update individual item view instead of repainting the whole list.
Kind regards to folks out there and happy coding ;)

You can update individual items by using an Backbone.Firebase.Collection with autoSync enabled. To do re-render individual items you need to listen to when the items fire the change event. This concept is shown in the BackboneFire Quickstart in the Firebase docs.
A quick note however, you cannot mix a Backbone.Firebase.Model with a Backbone.Firebase.Collection.
Todo Model & Collection
In the below sample, notice how a regular Backbone.Model is being used in the Backbone.Firebase.Collection. The collection by default has autoSync enabled.
// A simple todo model
var Todo = Backbone.Model.extend({
defaults: { title: "New Todo" }
});
// Create a Firebase collection and set the 'firebase' property
// to the URL of your Firebase
var TodoCollection = Backbone.Firebase.Collection.extend({
model: Todo,
url: "https://<your-firebase>.firebaseio.com"
});
Todo View
The below sample is a view for an individual todo item. Inside of the initialize function the listenTo method is used to listen to a model's change event. The change event will be fired each time the model is updated either remotely or locally (which persists changes remotely).
// A view for an individual todo item
var TodoView = Backbone.View.extend({
tagName: "li",
template: _.template("<%= title %>"),
initialize: function() {
// whenever the model changes trigger a re-render of the single view
this.listenTo(this.model, "change", this.render);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
});
Render List
With the TodoView set up the list can easily be rendered as well. In the initialize function of the AppView below, we listen to the collection which will be a TodoCollection. Whenever an item is added to the collection the addOne function is executed. The addOne function simply appends a new TodoView to the page.
// The view for the entire application
var AppView = Backbone.View.extend({
el: $('#todoapp'),
initialize: function() {
this.list = this.$("#todo-list"); // the list to append to
// by listening to when the collection changes we
// can add new items in realtime
this.listenTo(this.collection, 'add', this.addOne);
},
addOne: function(todo) {
var view = new TodoView({model: todo});
this.list.append(view.render().el);
}
});

Related

How to render a collection with a Marionette's ItemView?

I'm trying to render the response from an API (JSON) with Backbone.Marionette.ItemView. Not sure why it is not working.
I'm using marionette v2.4.7 (on purpose);
Here is the handlebars template:
<script id="feed-post" type="text/x-handlebars-template">
{{#each posts}}
<img src="{{author.image_url}}" alt="">
<p>{{author.name}}</p>
<span>TODO TIMESTAMP</span>
<p>{{body}}</br>{{topic_type}}</p>
{{/each}}
</script>
Here is my full app.js (all Backbone logic in this file);
// Model
var Post = Backbone.Model.extend({
defaults: {
authorPic: 'Unknown',
authorName: 'Unknown',
timestamp: 'Unknown',
body: 'Not available',
comments: '0'
}
});
// Collection
var Posts = Backbone.Collection.extend({
model: Post,
url: 'http://localhost:4321/blogposts',
initialize: function(){
this.fetch();
}
});
// View
var PostView = Marionette.ItemView.extend({
el: '#content',
template: Handlebars.compile($("#feed-post").html()),
});
//Config
var chunkPosts = new Posts();
var myview = new PostView({collection: chunkPosts});
Also, I tried to console.log the view and it looks like the models are in there.
This answer is tailored to Marionette v2.4.7. LayoutView and ItemView were merged and renamed to View back in v3.0.0.
From the doc on ItemView:
Rendering this view will convert the someCollection collection in to
the items array for your template to use.
You are using posts in your template while the doc says it will be called items.
As a reference, here's the exact code doing that in the ItemView source:
// Serialize the model or collection for the view. If a model is
// found, the view's `serializeModel` is called. If a collection is found,
// each model in the collection is serialized by calling
// the view's `serializeCollection` and put into an `items` array in
// the resulting data. If both are found, defaults to the model.
// You can override the `serializeData` method in your own view definition,
// to provide custom serialization for your view's data.
serializeData: function() {
if (!this.model && !this.collection) {
return {};
}
var args = [this.model || this.collection];
if (arguments.length) {
args.push.apply(args, arguments);
}
if (this.model) {
return this.serializeModel.apply(this, args);
} else {
return {
items: this.serializeCollection.apply(this, args)
};
}
},
The last lines show that for a collection, a new object with items as the only attribute is returned.
It's mentioned that you can override the serializeData function, more information and examples are available in the doc.
You still need to call render on the view and since the collection's fetch is async, you won't have items out of the box so you should wire a listener.
First, don't fetch in the initialize of a collection, it makes the collection pretty much useless for any other use-case.
var Posts = Backbone.Collection.extend({
model: Post,
url: 'http://localhost:4321/blogposts',
});
Listen for the collection sync event, then fetch within the view instead.
var PostView = Marionette.ItemView.extend({
el: '#content',
template: Handlebars.compile($('#feed-post').html()),
initialize: function () {
this.listenTo(this.collection, 'sync', this.render);
this.collection.fetch();
},
});
Marionette even offers collectionEvents:
var PostView = Marionette.ItemView.extend({
// ...snip...
collectionEvents: {
"sync": "render"
}
// ...snip...
});

Passing a model to a LayoutView Backbone.Marionette

I am attempting to pass a model to a LayoutView so that the particular model attributes can be edited in the view.
In my ItemView I have an event that grabs the selected model and assigns it to my global App-
var ItemView = Backbone.Marionette.ItemView.extend({
template: _.template(ItemTemplate),
tagName: "tr",
attributes: function(){
return {
"id": this.model.get('id'),
"class": "tr"
};
},
events: {
"click #edit" : "edit"
},
edit: function() {
App.EditModel = this.model;
console.log(App.EditModel); // <-- prints out the model successfully
App.trigger('edit');
}
})
Then, I am using the App.EditModel in my edit view to pass to my template-
var Layout = Backbone.Marionette.LayoutView.extend({
model: App.EditModel,
template : _.template(EditTemplate),
regions : {
"HomeRegion": "#home"
}
});
return Layout;
});
And in the browser I am getting- "Uncaught ReferenceError: firstName is not defined" because the model is not being mapped correctly.
How should I handle this?
When your layout code is first seen the extend function runs taking in all your options, at this point in time I imagine App.EditModel does not yet exist and so the model is stored as undefined
If you want to pass the model to your LayoutView then you should do this when you instantiate it i.e. new Layout({model: this.model});

Getting Started - Collection is not populating View

I try to render this data by doing
this.listenTo(this.collection, 'reset', this.render);
console.log(this.collection);
I'm assuming a reset event is called when I do a fetch but I'm not 100% sure.
My Collection is defined as such:
var FavoritesTable = Backbone.Collection.extend({
Name: 'FavoritesTable',
model: FavoritesRow,
url: $A.Reg.get('_path_ajax'),
initialize: function (models, options) {
console.log(this.fetch(options));
}
});
If you want fetch to reset a collection you can pass {reset: true} as options. Docs.
According to the source, it does not trigger a reset but a sync event.

Backbone reset a collection on fetch

This is my problem:
I have a container view that holds a collection.
On page load I get some models, populate this collection with them, then render the models
I fire and event
When this event fires, I want to make a call to my api (which returns models based on input parameters)
I then want to remove all existing models from the collection, repopulate with my new models, and then render the models
This is how I set up my model/collection/view
var someModel = Backbone.Model.extend({});
var someCollection = Backbone.Collection.extend({
model: someModel,
url: "api/someapi"
});
var someView = Backbone.View.extend({
events: {
"click #refresh": "refreshCollection"
},
initialize: function () {
this.collection.bind("reset", this.render, this);
},
render: function () {
// render stuff
},
refreshCollection: function (e) {
this.collection.fetch({data: {someParam: someValue});
this.render();
}
});
var app = function (models) {
this.start = function () {
this.models = new someCollection();
this.view = new someView({collection: this.models});
this.view.reset(models);
};
};
My point of interest is here:
refreshCollection: function (e) {
this.collection.fetch({data: {someParam: someValue});
this.render();
}
I pass in some paramaters, and my api returns a json array of models. I want to get rid of all existing models in the collection, and put all of my returned models into the collection, then update the view (with render())
I understand this is possible with collection.set, or collection.reset. Both of these take in an array of models. I don't have an array of models to pass in.
I tried:
this.collection.fetch({
data: {someParam: someValue},
success: function (response) {
doSomethingWith(response.models)
}
});
But I don't know what to do with the models when I get them.
Any pushed in the right direction would be appreciated!
From the fine manual:
fetch collection.fetch([options])
[...] When the model data returns from the server, it uses set to (intelligently) merge the fetched models, unless you pass {reset: true}, in which case the collection will be (efficiently) reset.
So you just need to include reset: true in the options and fetch will call reset to replace the collection's contents with the fetched models:
this.collection.fetch({
data: { ... },
reset: true
});

How can I attach event handler for a collection view in Backbone.js when one of the models is changed?

I have a view that renders a tasks list:
ProjectManager.Views.TasksIndex = Support.CompositeView.extend({
initialize: function() {
_.bindAll(this, "render");
this.collection.bind("add", this.render);
},
render: function () {
this.renderTemplate();
this.renderTasks();
...
},
renderTemplate: function() {
$(this.el).html(JST['tasks/index']({ tasks: this.collection }));
},
renderTasks: function() {
var self = this;
this.collection.each(function(task) {
// only display draft tasks
if (task.get('status') === "draft") {
var taskItem = new ProjectManager.Views.TaskItem({ model: task });
self.renderChild(taskItem);
self.$('#tasks-list').append(taskItem.el);
}
});
}
....
});
I render a view for each task that is in the collection. I would like to be able to delete a task.
I got to the point when after user clicks a delete button for a task I set a status attribute on task model to "deleted". Now somehow I need to bind an event handler in my TasksIndex view to re-render the collection.
I tried
this.collection.bind("change", this.render);
but it didn't work.
How can I propagate event that happened on the model in the child view to the parent view?
this.collection.bind('change', this.render) should call the render method when the model status is changed.
Can you add a console.log('render called'); line to your render method to see if the it's being called when the model status is changed.
I'm thinking the render method is being called but there's some logic elsewhere that is not correctly displaying the tasks.

Categories