Backbone.js collection initialization query - backbone.js

I am learing backbone.js and came across interesting scenario.
var EmployeeList = Backbone.Collection.extend({
url: 'http://localhost:80/employee/employee/employeelist'
});
alert('Collection created');
var employeeList = new EmployeeList();
employeeList.on("reset", function(){
alert(employeeList.length + " item(s) found");
});
employeeList.fetch();
alert(employeeList.length);
I am not able to understand why the second alert doesn't show length properly.

The alert alert(employeeList.length + " item(s) found") should display the correct length, because it is run after the reset -event is triggered following the populating of the collection.
The alert alert(employeeList.length); right after fetch shouldn't give you consistently the correct result, because collections are fetched asynchronously by default, so the alert is processed right after the AJAX-call for the fetch has been fired and hasn't possibly returned yet.
Timeline is something like this
Fetch triggers ajax call to 'http://localhost:80/employee/employee/employeelist'
alert(employeeList.length); is called, but collection is empty
Fetch returns eventually triggering a reset
alert(employeeList.length + " item(s) found"); is called

Related

In AngularFire how to keep track of data is "In flight"?

I'm working on chatting application in firebase and angular. When I send message using $scope.send(message) function it's directly upend to $scope.messages array because of three-way binding of angularfire. It's initially stored in locally and sync with server in background. I want to keep track message's status if it's sync with server or not. AngularFire's $push method has callback when it's sync with server. But I'm getting no clue of how to keep track of data is "In flight" ( Not synched with server but added in angular scope variable.)
var Ref = new Firebase('FIREBASE_APP_URL');
var ref = Ref.child('messages');
$scope.messages= $firebaseArray(ref).orderByChild('createdAt').limitToLast(10));
$scope.send = function(message) {
$scope.messages.$add(message).then(function(ref) {
var id = ref.key();
console.log("added record with id " + id);
console.log($scope.messages.$indexFor(id)); // returns location in the array
});
}
The $firebaseArray object is not involved in any three-way data binding. That is limited to $firebaseObjects .bindTo() method.
What happens when you call $add() is that a local child_added event is immediately raised and the $firebaseArray updates its internal data based on that. This is how all Firebase clients work, not just AngularFire, and it is part of why Firebase appears to work instantaneous.
If you want to know when the item was added on the server, you can use the promise that $add() returns. When that promise resolves, the data has been committed on the server.
From the AngularFire documentation for $add():
This method returns a promise which is resolved after data has been saved to the server.
var list = $firebaseArray(ref);
list.$add({ foo: "bar" }).then(function(ref) {
var id = ref.key();
console.log("added record with id " + id);
list.$indexFor(id); // returns location in the array
});

$scope not updating after passing to new view with $location.path

I have a simple CRUD I put together with Angularjs. From a product list display I pass the user to a new view template for the "Create New" form.
The form processes fine and updates the database. I then pass the user back to the list display using "$location.path(url)".
For some reason when the list page displays, the changes do not appear in the $scope and you have to refresh the page to see the changes.
Sample code:
$scope.acns = acnFactory.query()
.$promise.then(function (data) {
$scope.acns = data;
});
the above displays the list of items.
$scope.createAcn = function () {
$scope.acn.isActive = true;
acnFactory.create($scope.acn);
$location.path('/acn');
}
The above POSTs the new product then redirects to the list page (/acn)
My assumption is that the list page will reprocess or watch the changes to the $scope but the view does not update.
The problem is most probably here:
$scope.createAcn = function () {
$scope.acn.isActive = true;
acnFactory.create($scope.acn);
$location.path('/acn');
}
creating a product most certainly consists in sending an HTTP request to the server. This is asynchronous. This means that acnFactory.create() returns immediately after the request has been sent. Not after the response has been received.
So, this code sends the request to create the product and immediately goes to the page which lists the products. The GET request sent to get the product list is thus sent almost at the same instant as the one used to create the new product. The two requests are handled concurrently by the server, and the returned list contains the list without the new product, which is being created in a separate transaction.
You need to wait for the response to come back, and make sure it's successful, before going to the product list. Assuming the service returns a promise, as it should do:
$scope.createAcn = function () {
$scope.acn.isActive = true;
acnFactory.create($scope.acn).then(function() {
$location.path('/acn');
});
};

Backbone model's fetch doesn't work when the page initially loads but on second attempt works fine

In my backbone project I am trying to fetch a model based on some search criteria submitted by the users from a form. In submit handler, I am trying to fetch the model by passing search criteria's via data option (following is the code).
var productType=$("#productType").val();
var customerId=$("#customerId").val();
var stateSelected=$("#selectedState").val();
var srbStatus=$("#stateReportingStatus").val();
var dateType=$("#dateType").val();
var fromDate=$("#fromDate").val();
var toDate=$("#toDate").val();
var billTypeInd=$("#billTypeIndicator").val();
var dataElement=$("#dataElement").val();
var ediFileName=$("#ediFileName").val();
var ediBillName=$("#ediBillNumber").val();
var billId=$("#billId").val();
var claimantFirstName=$("#claimantFirstName").val();
var claimantLastName=$("#claimantLastName").val();
var insurerName=$("#insurerName").val();
var insurerFEIN=$("#insurerFEIN").val();
var insurerZip=$("#insurerZIP").val();
var dashboardSearchResultModel= new dashboardSearchResult();
var dashboardSearchResultModel= new dashboardSearchResult();
dashboardSearchResultModel.fetch({
data:{
productType: productType,
customerId: customerId,
state:stateSelected,
srbStatus:srbStatus,
dateType:dateType,
fromDate:fromDate,
toDate:toDate,
billTypeInd:billTypeInd,
dataElement:dataElement,
ediFileName:ediFileName,
ediBillName:ediBillName,
billId:billId,
claimantFirstName:claimantFirstName,
claimantLastName:claimantLastName,
insurerName:insurerName,
insurerFEIN:insurerFEIN,
insurerZip:insurerZip
},
wait:true,
success: function(dashboardSearchResultModel)
{
alert("This is what we get for result"+JSON.stringify(dashboardSearchResultModel));
$('#dashboardResultArea').html(self.dashboardResultTemplate({results:dashboardSearchResultModel.get("results")}));
},
error: function (model, xhr, options) {
alert("Error: An error occurred while trying to fetch the dashboardSearchResultModel");
alert("Error got model"+JSON.stringify(model));
alert("options:"+JSON.stringify(options));
alert("xhr:"+JSON.stringify(xhr));
}
});
Initially when the page loads after providing the search criteria's if I click submit the fetch doesn't work and goes to the error handler. After that when I submit the from second time the fetch works and retrieves data from the backend server. Any idea what is wrong? Thanks in advance.
When error callback is called, it is because your XHTMLRequest to the server returned a error (HTTP status code). So, there is where your problem resides.
Who starts this code? As the erros does not occur on a second attempt, I would suggest that you area callind $('#id').val() when the DOM is not ready. This way you are sending null values to the server, and that's causing the error you are receiving.
To solve your problem, assure you DOM is ready when executing this script.
See if your request is leaving the browser and reaching the server (i.e., cross-domain request fail with status 0, not reaching the server).
And, if it is, debug your server-side, as it does not seem to be an client-side problem.
So after trying many things I finally decided to try $.ajax call rather the fetch method. This is what I came up with
$.ajax({
type: "GET",
url: "rest/dashboardResult",
dataTyp: 'json',
data: {
productType: productType,
customerId: customerId,
state:stateSelected,
srbStatus:srbStatus,
dateType:dateType,
fromDate:fromDate,
toDate:toDate,
billTypeInd:billTypeInd,
dataElement:dataElement,
ediFileName:ediFileName,
ediBillName:ediBillName,
billId:billId,
claimantFirstName:claimantFirstName,
claimantLastName:claimantLastName,
insurerName:insurerName,
insurerFEIN:insurerFEIN,
insurerZip:insurerZip
}
})
.done(function(response) {
alert( "Result is: " + response);
});
This works without any problem from the get go. Now my question is how to bind the response to the backbone model?
Finally I figured out what was wrong. The call was inside a submit click handler, and $.ajax call or fetch is asynchronous. So by the time the call got reply from the server default action of submit click already took place (which is to reload the page). So by the time success or .done got called the whole page was reloaded. So I put event.preventDefault() at the beginning of handler method and let the handler receive the call back from the server and display it at the template. Thanks everyone for your time.

how backbone.js model fetch method works

i am very confuse about using backbone.js model fetch method. See the following example
backbone router:
profile: function(id) {
var model = new Account({id:id});
console.log("<---------profile router-------->");
this.changeView(new ProfileView({model:model}));
model.fetch();
}
the first step, the model account will be instantiated, the account model looks like this.
define(['models/StatusCollection'], function(StatusCollection) {
var Account = Backbone.Model.extend({
urlRoot: '/accounts',
initialize: function() {
this.status = new StatusCollection();
this.status.url = '/accounts/' + this.id + '/status';
this.activity = new StatusCollection();
this.activity.url = '/accounts/' + this.id + '/activity';
}
});
return Account;
});
urlRoot property for what is it? After model object created, the profileview will be rendered with this this.changeView(new ProfileView({model:model}));, the changeview function looks like this.
changeView: function(view) {
if ( null != this.currentView ) {
this.currentView.undelegateEvents();
}
this.currentView = view;
this.currentView.render();
},
after render view, profile information will not display yet, but after model.fetch(); statement execute, data from model will be displayed, why? I really don't know how fetch works, i try to find out, but no chance.
I'm not entirely sure what your question is here, but I will do my best to explain what I can.
The concept behind the urlRoot is that would be the base URL and child elements would be fetched below it with the id added to that urlRoot.
For example, the following code:
var Account = Backbone.Model.extend({
urlRoot: '/accounts'
});
will set the base url. Then if you were to instantiate this and call fetch():
var anAccount = new Account({id: 'abcd1234'});
anAccount.fetch();
it would make the following request:
GET /accounts/abcd1234
In your case there, you are setting the urlRoot and then explicitly setting a url so the urlRoot you provided would be ignored.
I encourage you to look into the Backbone source (it's surprisingly succinct) to see how the url is derived: http://backbonejs.org/docs/backbone.html#section-65
To answer your other question, the reason your profile information will not display immediately is that fetch() goes out to the network, goes to your server, and has to wait for a reply before it can be displayed.
This is not instant.
It is done in a non-blocking fashion, meaning it will make the request, continue on doing what it's doing, and when the request comes back from the server, it fires an event which Backbone uses to make sure anything else that had to be done, now that you have the model's data, is done.
I've put some comments in your snippet to explain what's going on here:
profile: function(id) {
// You are instantiating a model, giving it the id passed to it as an argument
var model = new Account({id:id});
console.log("<---------profile router-------->");
// You are instantiating a new view with a fresh model, but its data has
// not yet been fetched so the view will not display properly
this.changeView(new ProfileView({model:model}));
// You are fetching the data here. It will be a little while while the request goes
// from your browser, over the network, hits the server, gets the response. After
// getting the response, this will fire a 'sync' event which your view can use to
// re-render now that your model has its data.
model.fetch();
}
So if you want to ensure your view is updated after the model has been fetched there are a few ways you can do that: (1) pass a success callback to model.fetch() (2) register a handler on your view watches for the 'sync' event, re-renders the view when it returns (3) put the code for instantiating your view in a success callback, that way the view won't be created until after the network request returns and your model has its data.

Wait for the collection to fetch everything in backbone

I have two set of collections. One is for the categories and the other is for the Items. I ned to wait for the categories to finish fetching everything for me to set the category for the Items to be fetched.
Also i everytime i click a category i must re-fetch a new Items Collection because i have a pagination going on everytime i click on a category it doesn't refresh or re-fetch the collection so the pagination code is messing with the wrong collection. Any ideas?
this.categoryCollection = new CategoryCollection();
this.categoryCollection.fetch();
this.itemCollection = new ItemCollection();
this.itemCollection.fetch();
Just ran into a similar situation. I ended up passing jquery.ajax parameters to the fetch() call. You can make the first fetch synchronous. From the backbone docs:
jQuery.ajax options can also be passed directly as fetch options
Your code could be simplified to something like:
this.categoryCollection.fetch({async:false});
this.itemCollection.fetch();
One quick way would be to just pass a callback into the first fetch() call that invokes the second. fetch() takes an options object that supports a success (and error) callback.
var self = this;
this.categoryCollection = new CategoryCollection();
this.categoryCollection.fetch({
success: function () {
self.itemCollection = new ItemCollection();
self.itemCollection.fetch();
}
});
Not the most elegant, but it works. You could probably do some creative stuff with deferreds since fetch() returns the jQuery deferred that gets created by the $.ajax call that happens.
For the pagination issue, it's difficult to tell without seeing what your pagination code is doing. You're going to have to roll the pagination stuff yourself since Backbone doesn't support it natively. What I'd probably do is create a new Collection for the page criteria that are being queried and probably create a server action I could hit that would support the pagination (mapping the Collection's url to the paginated server action). I haven't put a ton of thought into that, though.
I had to react to this thread because of the answers there.
This is ONLY WAY OF DOING THIS RIGHT!!!
this.categoryCollection = new CategoryCollection();
this.itemCollection = new ItemCollection();
var d1 = this.categoryCollection.fetch();
var d2 = this.itemCollection.fetch();
jQuery.when(d1, d2).done(function () {
// moment when both collections are populated
alert('YOUR COLLECTIONS ARE LOADED :)');
});
By doing that you are fetching both collections at same time and you can have event when both are ready. So you don't wait to finish loading first collections in order to fetch other, you are not making ajax calls sync etc that you can see in other answers!
Here is a doc on Deferred objects.
Note: in this example case when one or more deferred object fails it's not covered. If you want to cover that case also beside .done you will have to add .fail callback on .when and also add error handler that will mark failed d1 or d2 in this example.
I am using RelationalModel and I created a queued fetch, that only calls the 'change' event when done loading:
var MySuperClass = Backbone.RelationalModel.extend({
//...
_fetchQueue : [],
fetchQueueingChange : function(name){
//Add property to the queue
this._fetchQueue.push(name);
var me = this;
//On fetch finished, remove it
var oncomplete = function(model, response){
//From _fetchQueue remove 'name'
var i = me._fetchQueue.indexOf(name);
me._fetchQueue.splice(i, 1);
//If done, trigger change
if (me._fetchQueue.length == 0){
me.trigger('change');
}
};
this.get(name).fetch({
success: oncomplete,
error : oncomplete
});
},
//...
});
The class would call:
this.fetchQueueingChange('categories');
this.fetchQueueingChange('items');
I hope you can improve on this, it worked well for me.
I ended up with the same problem today and figured out a solution to this:
var self = this;
this.collection = new WineCollection();
this.collection.url = ApiConfig.winetards.getWineList;
this.collection.on("reset", function(){self.render()});
this.collection.fetch({reset: true});
Now when the fetch on the collection is complete a "reset" is triggered and upon "reset" call the render() method for the view.
Using {async: false} is not the ideal way to deal with Backbone's fetch().
just set jQuery to become synchronous
$.ajaxSetup({
async: false
});
this.categoryCollection.fetch();
this.itemCollection.fetch();
$.ajaxSetup({
async: true
});
This is the simplest solution, I guess. Of course, starting new requests while these fetches run will be started as synchronous too, which might be something you don't like.

Resources