I'm using backbone and bootstrapping some data according to practice (http://documentcloud.github.com/backbone/#FAQ-bootstrap).
However, now when I save any bootstrapped model, the Backbone treats the model as non-existent on the server (model.isNew === true, http://backbonejs.org/#Model-isNew) which in turn calls Backbone.sync with method 'create' instead of 'update'. Thus, the POST instead of the PUT HTTP method is called.
how can I have bootstrapped models with isNew set to false, i.e. backbone treating them as existent on the server?
Apparently, model.isNew is determined by whether the id is set as explained here: http://backbonejs.org/#Model-isNew. That means bootstrapping data with the id set solves my problem
I encountered this problem recently, I overcame by overriding the parse(), initialize() and isNew() functions in my Backbone model.
The initialize() function initialises a 'loadedFromServer' property to false.
initialize: function () {
this.loadedFromServer = false;
},
The parse() function sets the 'loadedFromServer' property to true and returns the response:
parse: function (response) {
this.loadedFromServer = true;
return response;
},
The isNew() function then checks the id (for blank models) or whether the id has been set, but not synced to server:
isNew: function () {
return !this.id || !this.loadedFromServer;
}
This satisfies the following tests:
isNew() reports false when fetched
isNew() reports true when no id has been set
isNew() reports true when an id has been set (for PUT), but was never fetched
Related
I need to transform objects coming from $http call to an api. My code adds some fields (functions) to the object coming from the api, here the constructor of this object :
(function () {
window.TransformedObject = function (obj) {
var self = this;
self = {};
if (obj) {
self = angular.copy(obj);
}
self.hasChanged = function () {
// return true or false if the object has changed
}
return self;
}
}());
The $http transform code looks like this :
$http({
url: 'api/...',
method: 'GET',
transformResponse: function(value) {
return new TransformedObject(JSON.parse(value));
})
}).success(function(data){
vm.obj = angular.copy(data);
});
Note that the value in the transformResponse callback is stringified, and need to be parsed to get the object
All this is working fine, suppose the object coming from the api contains a key called title, doing obj.title = 'some title' will update the object.
The problem :
Binding the title field with an input tag will not update the object if the change is coming from the view.
I use a regular ng-model to do it:
<input type="text" placeholder="Title" ng-model="vm.obj.title"/>
even using $rootScope.$watch will never be triggered if the change is coming from the view aka the input tag.
$rootScope.$watch(function () {
return vm.obj;
}, function () {
console.log('watch');
// this log will never appear in the console
});
Am I doing something wrong, why transforming the object coming from the api is breaking angulars binding ???
Thanks.
http://www.bennadel.com/blog/2605-scope-evalasync-vs-timeout-in-angularjs.htm
Sometimes, in an AngularJS application, you have to explicitly tell
AngularJS when to initiate it's $digest() lifecycle (for dirty-data
checking). This requirement is typically contained within a Directive;
but, it may also be in an asynchronous Service. Most of the time, this
can be easily accomplished with the $scope.$apply() method. However,
some of the time, you have to defer the $apply() invocation because it
may or may not conflict with an already-running $digest phase. In
those cases, you can use the $timeout() service; but, I'm starting to
think that the $scope.$evalAsync() method is a better option.
...
Up until now, my approach to deferred-$digest-invocation was to
replace the $scope.$apply() call with the $timeout() service (which
implicitly calls $apply() after a delay). But, yesterday, I discovered
the $scope.$evalAsync() method. Both of these accomplish the same
thing - they defer expression-evaluation until a later point in time.
But, the $scope.$evalAsync() is likely to execute in the same tick of
the JavaScript event loop.
I have the following code in a controller:
$scope.chart = $resource('/api/chart/01234').get();
// { name: 'Foobar', id: '01234' }
$scope.send = function() {
$scope.chart.$save();
}
But after the user triggers send(), the only properties remaining in $scope.chart are those from $resource (e.g. $promise, $save, toJSON, etc…), the others are gone (no name or id).
I still don't understand why the instance's $save() wipes the instance, but using the class' save() works:
var Chart = $resource('/api/chart/01234');
$scope.chart = Chart.get();
$scope.send = function() {
Chart.save({}, $scope.chart);
}
Could be this:
It is important to realize that invoking a $resource object method immediately returns an empty reference (object or array depending on isArray). Once the data is returned from the server the existing reference is populated with the actual data.
From angular docs under the Returns section.
I have a resource factory with a POST method called update:
PnrApp.factory('Feed', function ($resource, $cacheFactory, $q, $rootScope) {
var Feed = $resource('api/feeds/:post', { post: 'post' }, {
get: { method:'GET' },
update: { method: 'POST' }
});
return Feed;
});
When I call the method it POSTs the data to the server as expected:
$rootScope.toggleStar = function (post, feedname) {
var updated = Feed.update(post);
this.child.StarId = updated.StarId;
}
And the server returns the correct values (notice the StarId in this json):
{"Name":"13 Ways to Act Like A Business Owner","ItemDate":"June 6, 2013","Url":"/post/13-Ways-to-Act-Like-A-Business-Owner-Stop-Acting-Like-an-Advisor-All-the-Time-(6-min-03-sec).aspx","StarImg":"bulletstar-on.png","StarId":1324,"StarDate":"0001-01-01T00:00:00","FeedCount":0,"FeedId":19,"SourceIcon":null,"IsBroken":false,"ItemId":"01"}
However, if you look at var updated's return value for StarId, notice how it's "0":
Can someone explain why this is, and how I can get at the return values in this situation?
Your var updated = Feed.update(post); makes an async call to the server and returns immedaitly and the updated object gets updated as soon as the server returns the data. So I guess you try to access the updated.StarId too early. From the angular doc:
It is important to realize that invoking a $resource object method immediately returns an empty reference (object or array depending on isArray). Once the data is returned from the server the existing reference is populated with the actual data. This is a useful trick since usually the resource is assigned to a model which is then rendered by the view. Having an empty object results in no rendering, once the data arrives from the server then the object is populated with the data and the view automatically re-renders itself showing the new data. This means ththeat in most case one never has to write a callback function for the action methods.
Try something like this:
$rootScope.toggleStar = function (post, feedname) {
var updated = Feed.update(post, function(f) {
this.child.StarId = f.StarId;
});
}
There's a new behaviour in the latest version of Backbone (1.0.0 in which the reset event is no longer triggered by default after fetching a Collection.
http://backbonejs.org/#changelog
Renamed Collection's "update" to set, for parallelism with the similar
model.set(), and contrast with reset. It's now the default updating
mechanism after a fetch. If you'd like to continue using "reset", pass
{reset: true}.
The problem is that I want to capture the event when the collection has been finally fetched (pretty common case, indeed!)
I could listen to add, remove and change event, but if the collection is empty I don't know how to catch the event.
So, what would be the new, recommended way to catch when the collection request has finalized, or is it passing a { reset = true } the only way to achieve it???
ps: here's the original question, BTW can't catch Backbone Collection reset event
From Backbone.sync doc,
Whenever a model or collection begins a sync with the server, a
"request" event is emitted. If the request completes successfully
you'll get a "sync" event, and an "error" event if not.
For example,
var C = Backbone.Collection.extend({
url: '/echo/json/'
});
var c = new C();
c.on('sync', function() {
console.log('sync');
});
c.fetch();
And a demo http://jsfiddle.net/nikoshr/GLATm/
We can pass a method as a success handler when we call fetch on collection and as you said you just want to do something when everything[add,remove,update or reset] has happened, you can do inside this success handler.
collection.fetch({
success: function() {
// Do Something
// This is called when all add, remove and update operations have been done
}
});
Note: success handler is always executed irrespective of you have passed reset:true or not. Irrespective of your collection gets empty or not and It will be called at the last step when all the add,remove and update events have occurred.
Let me know if it does not solve your problem.
My own solution is indeed pretty simple. I already have a BaseCollection with added features, so in there I just set as default { reset: true }. The code should be something like this (my own BaseCollection has a lot of stuff that is not pertinent here):
var BaseCollection = Backbone.Collection.extend({
fetch: function(options) {
options = options || {};
options.reset = (options.reset === undefined ? true : options.reset);
// just call super.fetch
return Backbone.Collection.fetch.call(this, options);
};
});
Using promises...
// you could use promises as well
// P.S.: pardon jquery promises :)
var C = Backbone.Collection.extend({
url: '/echo/json/'
});
var c = new C();
// c.fetch() returns jqXHR object that you can listen too
$.when( c.fetch() )
.done(successFn)
.fail(failFn)
.always(alwaysFn);
So, within one of my views, I've got this function:
delete_model: function() {
var answer = confirm("Are you sure you want to delete this element?");
if (answer) {
this.model.destroy({
success: function() {
console.log("delete was a success");
}
});
}
});
When I ping that, the Ajax call goes out, the backend properly deletes the model and returns a 200 header with "OK" as the body...but the success event never fires. Am I missing something? What should I have the backend serve to fire that event?
I just had the same problem. The solution that worked for me was to make sure I return a json model from the server that corresponds to the deleted one.
edit: returning an empty json response will suffice.
Did not work:
delete(model) {
// deleted model from db
return "Model was deleted";
}
This did work:
delete(model) {
// deleted model from db
return model;
}
or:
delete(id) {
// deleted model from db with this id
return new Model {id: id};
}
Had the same problem using Backbone 1.5.3 with Rails. I tried rudena's solution, and it works!
Here's what my controller's delete function looked like initially:
def destroy
#cell = current_user.cells.find(params[:id])
#cell.destroy
render :json => "success"
end
And here's what worked:
def destroy
#cell = current_user.cells.find(params[:id])
#cell.destroy
render :json => #cell
end
That looks good to me, exactly what I have everywhere (except I have function(model) but that shouldn't matter at all) I do know that older versions of backbone didn't use the destroy(options) but instead had destroy(success, failure). Can you make sure you have the latest version.
Had this problem come up with my UI as well. Upon DELETE, the API came back with an empty 200 response.
What's happening is, jQuery expects a json response body, but when the response comes back empty, json parsing fails and the error callback is triggered.
My solution was to override the Model's sync method:
var MyModel = Backbone.Model.extend({
// Fix for empty DELETE response
sync: function(method, model, options) {
if (method === 'delete') {
options.dataType = 'html';
}
Backbone.sync.call(this, method, model, options);
}
});
This works because options is passed to jQuery's ajax call and we're instructing jQuery not to expect json.