Ember.js - Rendering additional data for a model - javascript

I have an app model and apps have an id and name.
this.resource("apps", function() {
this.route('show', { path: ':app_id' });
});
I'd like to make the show view show metrics about the app, but the query is pretty intense, so I don't want to include it in the call to the index view (let's say this is a table).
I'm not sure if this is possible with ember-data because the app would already be in the store with the simplified payload and not be re-requested for the show view to get the metrics.
Then my head went to making metrics a completely different model accessible from apps/1/metrics and then making it another model and everything.
But if I sideload the data, i have to provide ID references to the metrics for a particular app. And it's hasOne so there's not really IDs as there would be for a database backed model.
What's the best way to load in additional data about a model or expand the information supplied in the show view?
The backend is Rails and this is an ember-cli project.

The easiest way is to retrieve the data in the Route's afterModel handler:
var ShowRoute = Ember.Route.extend({
model: function(params) {
// Load the model and return it.
// This will only fire if the model isn't passed.
},
afterModel: function(model, transition) {
// Load the rest of the data based on the model and return it.
// This fires every time the route re-loads (wether passed in or via model method).
}
});

Related

grab multiple instances of ID within an application

I have the below that is setup and working properly.
require(['models/profile'], function (SectionModel) {
var sectionModel = new SectionModel({id: merchantId, silent: true});
sectionModel.fetch({
success: function (data) {
$('#merchant-name').html(data.attributes.merchantName);
}
});
});
But it will only work in one instance. I am wondering how to correctly edit the above code to allow multiple instances.
<h3 id="merchant-name"></h3>
The content is generated within 'Save' function.
merchantName:$('#merchantName').val(),
What you want to do is set up the rest of the components for the Backbone application. The beauty of Backbone.js is it's ability to separate collections, models and views so your logic stays in a proper place.
You'll want to use an AJAX call to retrieve your models from the server using a Collection. Then, use the collection's reset function.
Here's an example of how you might fetch a collection of models from the server.
var MyCollectionType = Backbone.Collection.extend({
getModelsFromServer:function()
{
var me = this;
function ajaxSuccess(data, textStatus, jqXHR)
{
me.reset(data);
}
$.ajax(/* Insert the ajax params here*/);
}
});
var collectionInstance = new MyCollectionType({
model:YourModelTypeHere
});
collectionInstance.getModelsFromServer();
Then, to render each one, you'll want to make a View for each model, and a Collection View. There are a lot of resources though on learning basic Backbone.js and I feel that you might benefit from looking at a few of those.
Keep in mind that Backbone collections will by default merge models with the same id. 'id' usually references a model in the backend of an application, so make sure each id is actually what you want it to be. I work with an application that has a non-Restfull back end, and so ID's are never transferred to the front end.
There are some excellent resources available to begin starting with Backbone.js.
https://www.codeschool.com/courses/anatomy-of-backbonejs
(This is a free course up to a point, and a great starter.)
http://net.tutsplus.com/tutorials/javascript-ajax/getting-started-with-backbone-js/
http://javascriptissexy.com/learn-backbone-js-completely/

backbone.js cache collections and refresh

I have a collection that can potentially contain thousands of models. I have a view that displays a table with 50 rows for each page.
Now I want to be able to cache my data so that when a user loads page 1 of the table and then clicks page 2, the data for page 1 (rows #01-50) will be cached so that when the user clicks page 1 again, backbone won't have to fetch it again.
Also, I want my collection to be able to refresh updated data from the server without performing a RESET, since RESET will delete all the models in a collection, including references of existing model that may exist in my app. Is it possible to fetch data from the server and only update or add new models if necessary by comparing the existing data and the new arriving data?
In my app, I addressed the reset question by adding a new method called fetchNew:
app.Collection = Backbone.Collection.extend({
// fetch list without overwriting existing objects (copied from fetch())
fetchNew: function(options) {
options = options || {};
var collection = this,
success = options.success;
options.success = function(resp, status, xhr) {
_(collection.parse(resp, xhr)).each(function(item) {
// added this conditional block
if (!collection.get(item.id)) {
collection.add(item, {silent:true});
}
});
if (!options.silent) {
collection.trigger('reset', collection, options);
}
if (success) success(collection, resp);
};
return (this.sync || Backbone.sync).call(this, 'read', this, options);
}
});
This is pretty much identical to the standard fetch() method, except for the conditional statement checking for item existence, and using add() by default, rather than reset. Unlike simply passing {add: true} in the options argument, it allows you to retrieve sets of models that may overlap with what you already have loaded - using {add: true} will throw an error if you try to add the same model twice.
This should solve your caching problem, assuming your collection is set up so that you can pass some kind of page parameter in options to tell the server what page of options to send back. You'll probably want to add some sort of data structure within your collection to track which pages you've loaded, to avoid doing unnecessary requests, e.g.:
app.BigCollection = app.Collection.extend({
initialize: function() {
this.loadedPages = {};
},
loadPage: function(pageNumber) {
if (!this.loadedPages[pageNumber]) {
this.fetchNew({
page: pageNumber,
success: function(collection) {
collection.loadedPages[pageNumber] = true;
}
})
}
}
});
Backbone.Collection.fetch has an option {add:true} which will add models into a collection instead of replacing the contents.
myCollection.fetch({add:true})
So, in your first scenario, the items from page2 will get added to the collection.
As far as your 2nd scenario, there's currently no built in way to do that.
According to Jeremy that's something you're supposed to do in your App, and not part of Backbone.
Backbone seems to have a number of issues when being used for collaborative apps where another user might be updating models which you have client side. I get the feeling that Jeremy seems to focus on single-user applications, and the above ticket discussion exemplifies that.
In your case, the simplest way to handle your second scenario is to iterate over your collection and call fetch() on each model. But, that's not very good for performance.
For a better way to do it, I think you're going to have to override collection._add, and go down the line dalyons did on this pull request.
I managed to get update in Backbone 0.9.9 core. Check it out as it's exactly what you need http://backbonejs.org/#Collection-update.

Backbone.js web app - state, history & data management

I'm trying to build something similar to what is shown in the image below.
State & History management
I want to be able to serialize the current state at any time in the app and preferably be able to version these parameters.
1) First of all, what is a good way of overriding the defaultState parameters with any valid parameters given in the hash?
defaultState = {
mode : "2up", //Defines layout and how many widgets to init
models : [{year: 1980, selected: "SE"},
{year: 2010, selected: "NO"}] //Widget models
}
var AppRouter = Backbone.Router.extend({
routes: {
'': 'hashChangeHandler',
'v1?:params': 'hashChangeHandlerV1'
},
hashChangeHandlerV1: function(params) {
//here do something to override defaultState with params on init
//set models and layout etc.
}
});
//Handle history entries separately??
collection.bind("change", updateHistory)
function updateHistory () {
//each model toJSON(), then jQuery.param.querystring etc.
appRouter.navigate('v1' + state, false);
}
2) Could the above be a valid way of doing versioning? (Having a specific handler for the v1 route and another for v2 if the state parameters / API changes?) If you could give an example of the state/history solution that would work for this widgetized visualization app, I'd be very happy :)
3) I'd like to prevent a history entry to be set when dragging the timeslider and on certain other events. Can I remove the collection.change event listener (which tracks all instantiated WidgetModels) when dragging the slider, or what would you suggest?
Data management
Before the views are instantiated (and when their model's selection attribute changes) I'd like to make sure data (300-400 KB of numerical stat. data) is loaded.
4) What is a typical way of solving this in Backbone? (I.e. delaying the model.trigger events until attributes are stable.)
4 - wait for your models to load and only then instantiate the views.
Specifically, you should have someone (maybe an application view) that listens for the event that you data is loaded, and creates the view.
If your data is loaded in a model, than on the success callback of fetch you can trigger an event to let the rest of the application know that it was loaded.
If your data is loaded in a collection, you can listen for the collection's reset event.

Adapt my old work flow to Backbone

Im starting to build a new app and I would like to use Backbone as my framework. Below is a basic workflow that this (and most apps) follow.
What is the correct/best model to use with Backbone?
Old Way
User navigates to a page.
Selects "Create New widget"
User is presented with a form filled with inputs
At this point I would probably take the values entered (after passing basic validation), wrap them up and send them to the server via an ajax request
Request comes back as "OK" and the user is taken somewhere else (This step isn't entirely important)
Some basic pseudo-code
// Grab values
var userName = $('.UserName').val(),
dateOfBirth = $('.DateOfBirth').val();
...
...
...
$.ajax({
url: "/Webservices/ProcessStuff",
success: function(result){
if (result) {
// Render something or doing something else
} else {
// Error message
}
},
error: function () {
// Error message
}
});
Backbone way
Using the same example as above; I assume I'd have a model for the user information and a view to display the inputs. However, processing the actual call to the web service is one of the things I'm confused about. Where does this need to go? In the model or in the view click of some "Go" button?
Model.UserInformation = Backbone.Model.extend({ username: null, dateOfBirth: null });
Maybe also have a collection of these UserInformation models?
UserInformations = Backbone.Collection.extend({ model: Model.UserInformation' });
So bottom line what I'm asking is...
What is the best way to achieve this functionality?
What is the proper way to actually perform CRUD? Where to put the actual call to delete/update/create/etc?
You have the right idea and Backbone should make it easy for you to get things done using the same basic high level overview of your workflow. Note that you're still going to be using jQuery for this functionality - you'll just be doing it through the organizational aspects of Backbone's types.
There are a couple of key items that you'll want in place, most of which you already mentioned:
A backbone View to coordinate the HTML elements with your Javascript code
A backbone Model to store all of the data that the user input into the HTML elements
A back-end server that can handle RESTful JSON calls via AJAX requests from jQuery
I think the only thing you are missing is that the model has a save method on it, which wraps up all of the logic to call the create / update routes on your back-end server. The model also has a delete method to handle deletion from the server.
As a very simple example, here's a form that renders an HTML template to the screen, gathers the user input in to the model and then saves it to the server.
An HTML template:
<script id="myTemplate" type="text/x-jquery-tmpl">
First name: <input id="first_name"><br/>
Last Name: <input id="last_name"><br/>
<button id="save">Save!</button>
</script>
The code to run this:
MyModel = Backbone.Model.extend({
urlRoot: "/myModel"
});
MyView = Backbone.View.extend({
template: "#myTemplate",
events: {
"change #first_name": "setFirstName",
"change #last_name: "setLastName",
"click #save": "save"
},
initialize: function(){
_.bindAll(this, "saveSuccess", "saveError");
},
setFirstName: function(e){
var val = $(e.currentTarget).val();
this.model.set({first_name: val});
},
setLastName: function(e){
var val = $(e.currentTarget).val();
this.model.set({last_name: val});
},
save: function(e){
e.preventDefault(); // prevent the button click from posting back to the server
this.model.save(null, {success: this.saveSuccess, error: this.saveError);
},
saveSuccess: function(model, response){
// do things here after a successful save to the server
},
saveError: function(model, response){
// do things here after a failed save to the server
},
render: function(){
var html = $(this.template).tmpl();
$(el).html(html);
}
});
myModel = new MyModel();
myView = new MyView({model: myModel});
myView.render();
$("#someDivOnMyPage").html(myView.el);
This will give you a quick start for a form that saves a new model back to the server.
There are a couple of things your server needs to do:
Return a valid HTTP response code (200 or some other response that says everything was "ok")
Return the JSON that was sent to the server, including any data that the server assigned to the model such as an id field.
It's very important that your server do these things and include an id field in the response. Without an id field from the server, your model will never be able to update itself when you call save again. It will only try to create a new instance on the server again.
Backbone uses the id attribute of a model to determine if it should create or update a model when pushing data to the back end. The difference between creating a new model and saving one is only the id attribute. You call save on the model whether it's a new or an edited model.
A delete works the same way - you just call destroy on the model and it does a call back to the server to do the destroy. With some HTML that has a "delete" link or button, you would attach to the click event of that HTML element the same as I've shown for the "Save" button. Then in the callback method for the delete click, you would call this.model.destroy() and pass any parameters you want, such as success and error callbacks.
Note that I included a urlRoot on the model, as well. This, or a url function are needed on a model if the model is not part of a collection. If the model is part of a collection, the collection must specify the url.
I hope that helps.
If the "el" of the view is the form tag, then you could probably use the built in event object to bind a function to submit, but if the root of the view is something else, then you'll need to attach the click handler in the render function.

Fetching a single Backbone model from server

Say I have a route setup:
'photos/:id' : 'showPhoto'
and somebody shares the url: www.mysite.com/photos/12345 with a friend.
When their friend clicks on the shared link, showPhoto gets called back with 12345 passed as the id. I cant figure out how to fetch the model from the server, because even when setting its id property and calling fetch(), backbone thinks that the model isNew and so the ajax request url is just /photos instead of /photos/12345:
showPhoto: (id) ->
photo = new models.Photo _id:id
photo.fetch #does a GET to /photos, I would have expected it to request /photos/12345
success: () ->
render photo view etc...
Photo = Backbone.Model.extend
idAttribute: '_id'
urlRoot: '/photos'
The model Photo is usually part of a collection, but in this scenario someone is visiting the site directly and only expects to see data for one photo, so the collection is not instantiated in this state of the app.
Is the solution to load the entire collection of photos and then use collection.getById(id)? This just seems way too inefficient when I just want to load the properties for one model.
if you don't have the model as part of a collection, you have to tell the model the full url manually. it won't auto-append the id to the urlRoot that you've specified. you can specify a function as the urlRoot to do this:
Photo = Backbone.Model.extend({
urlRoot: function(){
if (this.isNew()){
return "/photos";
} else {
return "/photos/" + this.id;
}
}
});
Backbone uses the id of the model to determine if it's new or not, so once you set that, this code should work correctly. if it doesn't, you could always check for an id in the if-statement instead of checking isNew.
You do not need to tell backbone whether or not to append the id the url. Per the documentation: http://backbonejs.org/#Model-fetch, you may simply set the urlRoot to the equivalent of the url in a collection.
Backbone will automatically append the desired id to the url, provided you use one of the following methods:
model.set("id", 5); //After initialized
model = new Backbone.Model({id: 5}); //New model
If you manually set the id in the attributes hash or directly on the model, backbone won't be aware of it.
model.id = 5; //Don't do this!
there's already a similar question: "How do I fetch a single model in Backbone?"
my answer there should work for you (and it's in coffeescript)
also remember to check Backbone Model#url documentation, it's all explained there
I would bootstrap the collection (by rendering the following to the page) with just one model in it like this:
photos = new PhotoCollection();
photos.reset([ #Html.ToJson(Model) ]);
Note that the server side code that I use is ASP.Net MVC so use something specific to your server side architecture. Also note that the square brackets are important as they take your singular model and wrap it in an array.
Hope that's helpful.

Categories