I’m a bit new to Backbone.js, but am already impressed by everything it can do for me, and am trying to learn the patterns and best practices now.
I have two collections:
var CollA = Backbone.Collection.extend({
model: ModelA,
url: '/urlA'
});
var CollB = Backbone.Collection.extend({
model: ModelB,
url: '/urlB'
});
var collA = new CollA;
var collB = new CollB;
When loading my app, I need to fetch both of these collections from the server, and run some bootstrap code when it’s guaranteed that both fetches have completed.
Here’s how I did it for now:
collA.fetch({success: function() {
collB.fetch({success: function() {
// run the needed code here.
}});
}});
This works, the needed code is guaranteed to run only after both fetches complete successfully. It is clearly inefficient though, because the fetches run serially, one after another.
What would be a better pattern to do this, to run the fetches in parallel and then run some code once both fetches have completed successfully?
If you're using jQuery, use when:
$.when(collA.fetch(),collB.fetch()).done(function(){
//success code here.
});
Background:
http://api.jquery.com/jQuery.when/
http://documentcloud.github.com/backbone/#Collection-fetch
You pass in one or more deferreds to $.when. It so happens that the "jqXHR" that collections return implements the promise/Deferred interface that can be combined into a new promise using $.when. The requests will essentially be concurrent (well, as much as javascript allows) and the function passed to .done will execute only when both fetches are successful.
As #JayC said you could use $.when from jQuery, other option that I prefer is the after function (from Underscore), which execute one time just after expected calls are completed.
http://underscorejs.org/#after
function runMyApp(){
// run the needed code here.
console.log('This will run one time but until both fetch are being completed.');
}
//2 because you have two collections
var renderApp = _.after(2, runMyApp);
collA.fetch({success: renderApp});
collB.fetch({success: renderApp});
I like #JayC's answer if you have to fetch each collection individually. You could argue that a single fetch to the server would be better as opposed to multiple fetches. If you are fetching this data on application load, I would do a single call to the server and then pass the data to your relevant collections. It truly depends on how many collections you have to fetch on application load, but I honestly prefer making one call to the server then passing the data to my relevant collections.
$.ajax({
url: "path/to/app-load-data"
}).done(function(data) {
collA = new CollA(data.collA);
collB = new CollB(data.collB);
});
This obviously will depend if you can manipulate your api and the above does not follow REST. But then again you are making one call to the server as opposed to multiple calls.
Related
I am a bit confused. Components, controllers, routes, helpers and whatsoever. I simply want to grab a value from a JSON file and calculate it with a value on Ember.Helper. Which way should i use, i cannot know anymore, brain burned. Would someone please help me to grab the "sell" part of the "market_name" which equals to "BTC_USDT" on "https://stocks.exchange/api2/prices" and put that into helper?
Edited:
In fact i try to do something like that.
import Ember from 'ember';
export function formatBTC(value) {
var url = 'https://stocks.exchange/api2/prices';
var btc_price = Ember.$.getJSON(url).then(function(data) {
for (var i=0; i <= data.length-1; i += 1)
{
if (data[i].market_name == "BTC_USDT")
{
return data[i].sell;
console.log(data[i].sell+' - i got the value properly');
}
}
});
console.log(btc_price+' - shows nothing, i cannot pass the var btc_price to here, why not');
calculation = value * btc_price; //some syntax may apply, maybe Number(value) or whatsoever, but i cannot have my variable btc_price returns here.
return calculation.toFixed(8);
}
export default Ember.Helper.helper(formatBTC);
And from the index.hbs
{{format-btc 0.001}}
Still couldnt find a proper solution. I get the data[i].sell as btc_price, but couldnt pass it through to return part... what am i missing? or what am i doing wrong?
The issue you're encountering is because the ajax request executes. Execution of the function continues and returns the value before the promise returns.
While technically, you could fix this and use async/await in your helper function, you'll run into another issue - Every time your helper is called, you'll make a new ajax request that will fetch the current price and calulate the value.
My recommendation is that instead of a helper, you use a combination of a model and a controller. Because you're currently overwhelmed with the framework, I'll actually make a second suggestion of using a service + component
I recommend a service or a model because you want to persist the data that you've fetched from the pricing source. If you don't, every instance of the helper/component will make a new request to fetch data.
Service
A service is kind of a session collection in ember. It only gets instantiated once, after that data will persist.
ember g service pricing
In the init block, set your default values and make your ajax request.
# services/pricing.js
btcPrice:null,
init() {
this._super(...arguments);
Ember.$.getJSON(...).then(val=>{
# get correct value from response
# The way you were getting the value in your example was incorrect - you're dealing with an array.
# filter through the array and get the correct value first
this.set('btcPrice',val.btcPrice);
})
}
Component
You can then inject the service into the component and use a consistent price.
ember g component format-btc
Modify the controller for the component to inject the service and calculate the new value.
#components/format-btc.js
pricing: Ember.inject.service('pricing')
convertedPrice: Ember.computed('pricing',function(){
return pricing.btcPrice*this.get('bitcoins')
})
The template for the component will simple return the converted price.
#templates/components/format-btc.js
{{convertedPrice}}
And you'll call the component, passing in bitcoins as an argument
{{format-btc bitcoints='1234'}}
All of this is pseudo-code, and is probably not functional. However, you should still be able to take the guidance and piece the information together to get the results you want.
In my controller I have a property and a function observing that property. When the property changes once, the observer is being hit three times - twice with the old data and once with the new data. You can see it happen in the console window of the jsbin I just created:
jsbin
Usage: click one of the books (not the first one), and look in the console window at the results.
In my actual app, the work to be performed requires an asynchronous download. This observer is downloading the wrong content twice and the correct content once because of the three hits. Making this problem more obvious is that the asynchronous responses do not come back in sequence. :)
An interim solution has been to schedule the download code to run later.
I'm not sure why this is happening, but the guides give a way to fix it. They suggest something like this:
bookObserver: function() {
Ember.run.once(this, 'bookWasChanged');
}.observes('book'),
bookWasChanged: function() {
// This will only run once per run loop
}
Personally, I always make the assumption that observers could fire even when I don't want them to. For instance, with this code, I would do the following:
bookObserver: function() {
var book = this.get('book');
var lastBook = this.get('lastBook');
if (book !== lastBook) {
this.set('lastBook', book);
doSomethingWithBook(book);
}
}.observes('book')
This way, even if Ember calls your observer 1000 times, you're only going to do the work you want when the book actually changes.
Given Ember reached 1.0.0 recently, I wanted to start using it with tests. I'm using Yeoman 1.0 with Karma. I want to unit test models but I'm finding it very difficult to accomplish isolation.
The example I have now is:
describe("Expense", function() {
return it("has a computed property called `explained`", function() {
var expense = App.Expense.create({
name: "My first expense",
value: 34
});
return expect(expense.get("explained")).to.equal("My first expense -- 34");
});
});
As of 1.0.0, I get the following error:
Error: You should not call `create` on a model. Instead, call
`store.createRecord` with the attributes you would like to set.
How should I access store in order to create a model instance? More ideally, how can I simply spawn models like this without even resorting to the store, is that viable? There's no point in spawning an entire app just to test a model, IMO.
Thank you.
Here is the minimum code that I've used so far for unit testing models.
var container, store, expense;
container = new Ember.Container();
container.register('store:main', DS.Store.extend());
container.register('model:expense', App.Expense);
store = container.lookup('store:main');
Ember.run( function() {
expense = store.createRecord('expense', {
name: "My first expense",
value: 34
});
});
Based on the code of the store and the way models are tested inside Ember Data, I don't think that you can reduce the setup of the test.
According to #sly7_7's commentary, looking for the store inside the app via App.__container__.lookup('store:main') works.
You should use this.get('store').createRecord('expense')
It looks like you're using Ember-Data 1.0.0 beta, which changed the way things work. You should take a look at the migration.md file in the ember-data project.
(Sorry I should have read your question a bit better - I'm having the same issue at the moment and the suggested container solution doesn't work for me - causing browsers to crash) :(
My application has a nodes pool, and several sub-nodes (queues, services, ...). I need to constantly access the nodes pool, and I need to make sure that the data is up-to-date with the data in the backend. The data in the backend can change due to several reasons, for example:
side effects of working with other objects: the backend will modify not only the affected object, but related objects too. For example, an update of a service document could affect a queue. This is done in the backend, and the ember application is not aware of this.
Maybe another user has modified objects in the backend, and I want to get a fresh copy.
Usually the objects are loaded when accessing the route #/nodes/index, but sometimes I would like to force a refresh of the store, without hoping that the user performs an access to the right route. How can I trigger this programatically?
With ember data you can reload the data using. recordArray.update(), and using window.setInterval to schedule to your desired time. A RecordArray instance is get when the Model.find() is resolved.
Model.find().then(function(recordArray) {
});
The easy way to do this is in the ember way, is returning your data in the model hook, and getting the recordArray instance in afterModel hook. So use this instance to perform an update at some specific time using setInverval, by example:
App.NodesIndexRoute = Ember.Route.extend({
model: function() {
return App.Nodes.find();
},
afterModel: function(recordArray) {
setInterval(function() {
recordArray.update();
}, 1000); //each second
}
});
Just be aware with the transition to nodes index route, because every time will be created a new setInterval, and things will load n times more to each transition to that route.
To fix this, store the id returned of setInterval, and use the clearInterval to remove the previous task:
afterModel: function() {
if (this.get('jobId')) {
clearInterval(jobId);
}
var jobId = setInterval(function() {
record.update()
}, 1000); // each second
this.set('jobId', jobId);
}
I have created a sample here.
Using the latest revision of Ember-Data and the RESTAdapter, is there a way of doing the following?
I have a resource called App and a API that responds to /apps by returning the correct JSON (with { apps: [...] }etc.)
Since this gets served from a static json on our server, it is quiet inappropriate to create server-side resources for every app that can be fetched as /apps/:app_id. Instead, it would be good if the RESTAdapter allways loaded /apps, even if it then only uses one single app out of the fetched ones.
Do I have to write my own Adapter to achieve this? If yes, what would be a good point to "hook into"?
Supposing you have an app model App.App, it should be enough to call App.App.find() when your application loads. This will make the AJAX call to /apps. Even if you don't cache the result in a variable, your data store will be populated with the returned records. Now whenever you call App.App.find(id), Ember Data will check your store and return the record if it has it. If it doesn't have the record, then it will try to call /apps/:id, but this shouldn't happen if your application is designed to use only a static collection.
There are a few different places you could put the App.App.find() call. I would probably put it in App.ready:
App = Ember.Application.create({
ready: function() {
// pre-load apps
App.App.find();
}
});
App.App = DS.Model.extend({
//...
});
It seems a little hacky (and probably is), but it looks like one can achieve this by overwriting the DS.Adapter:find().
In my case, to block calls to /app/:app_id I wrote this:
find: function(store, type, id) {
// Terminate calls for single app
if (type === App.App) {
// instead, load all apps and drop this request
App.App.find();
return;
}
// or continue as usual
this._super(store, type, id);
}
This also works when you have a hierarchy of embedded: 'always' records and Ember thinks it has to load a middle level. Just make sure you load the parent for sure when dropping requests like this!