I've been migrating my app from RethinkDb to Firebase and have run into a wall trying to get my collections to save when calling collection.create(). The odd thing is, I have two instances of Backbone.Firebase.Collection in my app and one of them is able to create new objects properly. The other collection, which is seemingly identical, cannot write data and generates no error messages.
I have two Collections, each with their own type of Model:
1) Projects (Collection) -> Project (Model)
2) Projects (Collection) -> Shots (Collection) -> Shot (Model)
Here are the definitions for the above collections/models:
/* Projects Collection - An ordered list of Projects */
var ProjectModelFirebase = require('../models/projectModelFirebase.js');
module.exports = Backbone.Firebase.Collection.extend({
model: ProjectModelFirebase,
firebase: new Firebase(app.fbUrl),
initialize: function() {
}
});
/* Project Model - data layer for a single Project */
module.exports = Backbone.Firebase.Model.extend({
firebase: new Firebase(app.fbUrl),
initialize: function() {
}
});
/* Shots Collection - An ordered list of Shots */
var ShotModelFirebase = require('../models/shotModelFirebase.js');
module.exports = Backbone.Collection.extend({
model: ShotModelFirebase,
firebase: new Firebase(app.fbUrl),
initialize: function() {
}
});
/* Shot Model - data layer for a single Shot */
module.exports = Backbone.Firebase.Model.extend({
firebase: new Firebase(app.fbUrl),
initialize: function() {
}
});
In my Projects route (routes file), I bind the collection to the view normally and call this.collection.create with some inputs from the template. I'm able to successfully create projects in this way and they show up in my Forge with no problem. I call the view from my route function:
// Display list of latest projects
projectsCollectionFirebase = new ProjectsCollectionFirebase();
var projectsView = new ProjectsView({collection: projectsCollectionFirebase});
In my Project route (routes file), I bind the project collection to the view and retrieve the project's information from Firebase normally:
// Display a single project
projectModelFirebase = new ProjectModelFirebase({id: project});
var projectView = new ProjectView({model: projectModelFirebase});
I then proceed (projectView file) to create a new collection of shots:
shotsCollectionFirebase = new ShotsCollectionFirebase();
shotsView = new ShotsView({ collection: shotsCollectionFirebase, project: this.model.get('id') });
The Shots view (ShotsView file) renders the template and input fields for the user to input a shot.
From here, I call this.collection.create when a user clicks the 'Save' button. Backbone recognizes the new model and updates the view accordingly, but Firebase doesn't act on it. There is an error message in the console that states 'Save called on a Firebase model, ignoring.' but I do not have any saves called out explicitly in my code.
I've tried turning on debugging and noticed that the 'projects' create command causes this message:
r:0: update {"path":"/projects/test","value":{"shots":null}}
Whereas the 'shots' collection does not ever trigger an 'update' event.
The files are nearly identical. The only thing difference I can spot is that the first example has only one collection loaded at a time whereas the second example loads a collection, grabs the data, then loads another collection as a result.
Any help would be appreciated.
You can find the full code for the project in my repo here: https://github.com/bdickason/shots/tree/firebase
There are two modes of operation in Backfire, one with models/collection that have the word 'Firebase' in them, eg: Backbone.Firebase.Model/Backbone.Firebase.Collection; and the other mode that uses regular backbone collections and models but have the 'firebase' property defined on them that overrides the backbone sync() method.
These two modes should not be mixed and matched. For example, if you're using a Backbone.Firebase.Collection, all the models in that collection will automatically be synchronized with the server any time a change is made to any model (same for Backbone.Firebase.Model). Calling save, or sync doesn't do anything because the changes are saved and synced in realtime as they happen.
Sometimes this isn't what you want in the app, for example, you want the user to explicitely click a save button before saving anything to Firebase. In these cases, it's best to use a regular Backbone collection or model, and simply provide the firebase property on them. You then need to call sync or save explicitely to put any changes on the local model/collection into Firebase.
Related
Inside an application we allow users to create new records, related to an existing record. To achieve this, we use actions something like this:
createUser() {
var route = this;
var model = this.store.createRecord('user', {
client: route.modelFor('client'),
});
route.transitionTo('user.update', model);
},
The user.update route renders a user-form component, using the model that was passed in the transition. The same route is also used to update existing users.
The issue with this approach is as follows; when refreshing the page, the page errors because the route fails to find the respective record when querying the store (at this point, the URL is /users/null/update). Ideally I'd pass the client (or client.id) argument in the URL so that:
The page can be reloaded without issue.
The client associated with the user is set correctly.
How can I achieve this in Ember.js? I know that this can easily be done using nested routes (by nesting the user.update route inside a client route), but this doesn't make sense visually.
The relevant parts of the router are as follows:
this.route('clients');
this.route('client', {path: 'clients/:id'}, function() {
this.route('users');
});
this.route('user', {path: 'users/:id'}, function() {
this.route('update');
});
All I do in the user/update.hbs template is {{user-form user=model}}
The problem is that the model you just created has no id at that point because it is not saved, ember can´t route to a model without an id, if possible save the model before you try to transition to the route, if you don´t want to save the model because the user can cancel the action check this thread where a user had the same problem (if I understand you problem correctly), I provided a solution for that problem that I´m using in my own project
https://stackoverflow.com/a/33107273/2214998
Using Ember, we have a list of shoes which is fetched from a database. These are listed at '/shoes.
this.resource('shoes', function() {
this.route('new');
this.route('show', {path: ':shoe_id'});
this.route('edit', {path: ':shoe_id/edit'});
});
Only the first 10 shoes in the MongoDB collection are listed in the view, as specified in our webb API. When creating a new shoe (using the nested route 'new'), and transitioning back to '/shoes', the new shoe is added to the current 'shoes' model.
export default Ember.ObjectController.extend({
actions: {
save: function() {
this.get('model').save();
this.transitionToRoute('shoes');
}
}
});
This results in a list of 11 shoes. In other words, it does not use the route and make a new API call. Instead, it is added to the current list of shoes in the model. When refreshing the page, the result is rendered as intended, fetching the 10 first records of the DB collection.
We would like to make the ’transitionToRoute’ execute the route and re-fetch the model instead of just adding it to the current model. We have seen a few examples of how ’this.refresh()’ and ’this.reload()’ can be used inside the controller's 'model' scope body but these examples have not worked for us.
Is it possible to make a ’transitionToRoute’ refresh the model with new database values using the 'shoes' route?
Based on what you wrote, I'm guessing you're trying to use pagination and only want the first 10 shoes to be listed on your /shoes route?
If so, the "Ember Way" is to always keep all your models in sync and never have to do special work just to get the view to update artificially. In this case, Ember has a local store of shoes where it initially has 10 items. Then you add one more, it gets saved both the database and to the Ember local store and so now Ember thinks (correctly) that you have 11 shoes. Just because Mongo returns 10 shoes doesn't mean your entire data set is 10 shoes.
So, the best way to handle this situation is to have your view display an accurate projection of your underlying model data. In other words, don't tell your view to display "all shoes". Tell it to display a "filtered list of all shoes"!
In practice, I've seen two types of filtering on ArrayController. One is just to return the first n values. For that use good old javascript slice (See MDN docs). The second is to use the Ember filter function. See Ember Docs.
Ultimately, your controller would something like this:
Shoes Controller:
export default Ember.ArrayController.extend( PaginatorClientSideMixin, {
shoesFilteredOption1: function() {
return this.get('arrangedContent') // 'arrangedContent' is the sorted list of underlying content; assumes your backing model is the DS.RecordArray of shoes
// this use of slice takes an array and returns the first 10 elements
.slice( 0, 10 );
// we depend on 'arrangedContent' because everytime this changes, we need to recompute this value
}.property('arrangedContent')
shoesFilteredOption2: function() {
return this.get('arrangedContent') // 'arrangedContent' is the sorted list of underlying content; assumes your backing model is the DS.RecordArray of shoes
// here we're filtering the array to only return "active" shoes
.filter( function(item, index, self ) {
if (item.isActive) { return true; }
})
}.property('arrangedContent')
});
Then on your Handlebars template read from shoesFilteredOption1 instead of content or model.
I am trying to wrap my head around Backbone, more specifically how the an application flows throughout it's life. Unfortunately at my job I do not have access (or say for that matter) on how our API is structured. We have many different calls from different time periods with crazy inconsistent structure.
Overriding fetch or sync is not a problem to standaraize the return but what I run into (at the very beginning of my dive in the a Backbone application) is a how to layout the actual code.
Here is my real world example. This page is non-critical and I am trying to re-write it with Backbone. Here is the flow:
Page loads a list of genre types from a call
Clicking on a genre type loads sub genres based off of the genre type (the sub genre type requres a genre code as the parameter)
Clicking on the sub genre type loads all products with that criteria.
I can get pretty far but at some point I feel the code is getting mangled - or doesn't feel natural. Like I am shoving things in.
So my official questions is: How do I manage a Backbone app?
Here is a summary of my though process:
I created a global namespace as one should
var App = App || {};
Okay, lets start with the main application view as all examples show:
App.MainView = Backbone.View.extend({
//this loads the outer stuff
//and creates an instance of the Genre View
});
Alright pretty straightforward, I am going to need a genre model, collection, and view (this applies to sub genre as well)
App.Genre = Backbone.Model.extend();
App.Genres = Backbone.Collection.extend({
url: 'returns a list of genres',
model: App.Genre,
initialize: function() {
this.fetch();
},
fetch: function() {
var self = this;
$.ajax({
url: this.url,
success: function(response) {
**format return**
self.add(formattedArrayOfModels);
}
});
}
});
Now on to the view, the confusing part
App.GenreView = Backbone.View.extend({
el: 'element',//easy enough
tmpl: 'my handlebars template',//implementing handlebars...no problem
initialize: function() {
//this produces a collection full of genres
this.genreList = new App.Genres();
this.genreList.on('add', _.bind(this.render, this));
},
render: function() {
//rendering not a problem, pretty straight forward
}
});
Up until here I have no problems. The genre list loads and we're good to go. So, now when the user clicks a genre I want it to load a sub genre
events: {
'click a': 'getSubGenres'
},
getSubGenres: function(e) {
}
Here is my problem. In getSubGenres do I keep it local?
var subGenre = new App.SubGenreView();
Or should I make it part of the Genre view?
this.subGenre = new App.SubGenreView();
Should I somehow put it in a parent object so it can be accessed by other views? How do I control things like that?
And if I already have a collection of sub genres how do I just use the loaded collection (instead of another ajax call).
Is this the approach you would use?
couple of things before I answer,
first: the fetch function doesn't need an $ajax call since it's its job, so, you can evaluate error:function(){} and success:function(){} immediately inside fetch, but that's assuming that the URL is set correctly.
second: one thing that helped me a lot in my backbone keyboard-head-fight is the addy osmani Backbone Fundamentals which contains a very rich tutorial in pdf format.
now back to the question: from my experience, you will mostly need 'this', so it's a good habbit to get used to it, plus there is something that solves a lot of these issues if implemented correctly: backbone layoutmanager
anyway, the decision of where to place the subview, is totally a design decision in your case and depends a lot on how you structure your page and files.
about how to use the "collection" that is preloaded: I really didn't get it, because the collection you're talking about contains all the subgenres, so usually it shouldn't change even if the view changes to a certain genre view, you are still able to use it.
but still everything I said, is relative to how you structure your files, I do an app.js and a router.js and lots of other files, but the main work is always on the main two, so basically I always get access to everything.
I hope this answered your question
In working with Titanium's Alloy framework, the models are using Backbone.js. I'm new to both Backbone.js and Alloy.
I'm familiar with ExtJS however, and in ExtJS, you can create a form, and then say something like:
form.loadRecord(record); // where record is an instance of a model
and then
form.getRecord(record); // When you're saving the form, get the record
var vals = form.getValues(); // And then get the values from the form that the user changed
record.setValues(vals); // Update your record with whatever values the user changed.
So my question is, what is the pattern for forms in Alloy? Or is there not one?
I could easily make my own functions, however I'm running into an issue where the app crashes when trying to bind a model to a form object like this:
that.model = model; // where model was passed in to the form.loadRecord function.
I'm going through the process of learning Backbone.js and I've come across a few things that look like they work... but behind the scenes, they might be causing some problems. One such issue is design patterns for swapping views.
I have a menu on the left (and corresponding view) that contains a list of users. Each user, when clicked, will display their corresponding list of movies to the right into another view. I do this by getting the model of the clicked user, building a new Movie collection based on that model, and rendering a new Movie View (to the right). If I click on another user, it does the same thing: it gets the user model, builds a new Movie collection, and renders a new Movie View to the right, replacing the DOM element. This looks fine -- but I'm worried about all of the new objects/bindings that are being created, and potential issues that could arise. So, what are my alternatives?
(1) Should I be trying to have the view redraw when the collection changes? How do I do this if I'm creating new collections?
(2) Or should I be unbinding everything when another user is clicked?
Here is my userlist view:
Window.UsersList = Backbone.View.extend({
el: $("#users"),
initialize: function(){
this.collection.bind('reset', this.render, this);
},
render: function(){
var users = [];
this.collection.each(function(user){
users.push(new UsersListItem({model: user}).render().el);
},this);
$(this.el).html(users);
return this;
}
});
In my UsersListItem view I capture the click event and call show_user_movies in my controller:
show_user_movies: function(usermodel){
// fetchMovies() lazy-loads and returns a new collections of movies
var movie_collection = usermodel.fetchMovies();
movie_list = new MovieList({collection: movie_collection, model: usermodel});
movie_list.render();
},
Thanks for any suggestions.
Just re-use the same MovieList view along with it's associated collection, using reset(models) to update the models in the collection, which should re-render the view. You can use the same pattern you have above with your MovieList view binding to the collection's reset event and re-rendering itself at that time. Note that usermodel.fetchMovies() doesn't follow the backbone asynchronous pattern of taking success/error callbacks, so I don't think the code works as is (maybe you simplified for the purpose of this question), but the point is when the new set of models arrives from the server, pass it to movie_list.collection.reset and you're good to go. This way you don't have to worry about unbinding events and creating new views.
show_user_movies: function(usermodel){
// fetchMovies() lazy-loads and returns a new collections of movies
movie_list.collection.reset(usermodel.fetchMovies().models);
},