Undo view change after backbone sync fails - backbone.js

I'm quite confused with backbone's view rendering. I need your help regarding this.
For example, I've a Album view. It's render method renders each Track view. All the track specific events are binding in the Track view.
Now, Track view is listening to it's model. For example:
this.model.on('destroy', this.destroy, this);
destroy: function(model){
this.close();
},
so when a track is destroyed, the view is removed from UI which is fine. But problem is with sync. The view is removed from the UI not from server. If sync is successful, nothing to do with the view.
However, if the sync is not successful, I want to undo this view change (restore the track markup where it was before I destroyed it).
Can anyone please tell how can I do this?
FYI,
earlier I've altered the UI after getting success from server. But in more than 98% cases we get success, we decided to change the UI immediately and restore when there is an error.

There's a great plugin for dealing with this: https://github.com/derickbailey/backbone.memento

I call view.remove() in the success callback of model.destroy, I think its easier to read the code and understand what happens that way.
But if you really want to restore the view on sync.error I guess you could call a restore method in an error callback from your destroy call. But it feels like more work to restore it than to wait for the ack.

Related

How to trigger a function when a MongoDB Cursor property is changed? (Angular-Meteor)

I don't know if i titled my question correctly but here is what i want to achieve and what i have so far (simplified as much as possible):
What I have so far:
For each Visitor I create a "Visitor" Object:
{
name:"Bob",
mouseX:"31", // The mouseposition is constantly updated
mouseY:"400",
messages:[
{text:"Message Text",prop:"other property"},
{text:"Another Message"}
]
}
After that I bind the Object to the scope:
$scope.helpers({
visitor: () => Visitors.findOne({"_id":id})
});
The Messages are shown inside a ng-repeat over the visitor.messages model:
<div ng-repeat="msg in visitor.messages">
<span class="text {{msg.class}}"> {{msg.text}} </span>
</div>
Another Visitor can send a new Message with:
Visitors.update(
{_id: visitor._id},
{$push: {messages: {"text":"New Message"}}}
);
What I want to do
When Bob receives a new message i want to trigger the function newMessageArrived() and to animate the new Message in.
The Problem and what i tried so far
First i tried to use angular:angular-animate package and animate new messages with css-classes but ng-enter triggered for alle messages each time visitor.mouseX was updated and even if nothing other than the messages in the Visitors-Object where pushed still ng-enter triggered for all messages instead only for the new ones.
Thats ok because i guess that the whole DOM is rebuild when the Visitor-Object changes. Now i tried to simply watch the messages prop of the Visitors-Object like:
scope.$watch("visitor.messages", function ( newValue, oldValue ) {
newMessageArrived()
});
Again newMessageArrived() is triggering with each update to the Visitors-Object. Of course i could catch this stupidly with something like checking the length of the messages array but in my understanding even using $watch without this is wrong already.
The Question
So what is the Angular-Meteor way of reacting on changes in a property of a MongoDB Cursor (Object)?
Please consider my examples are extremly simplified to focus on my problem but also i am new to Meteor and not even very experienced in Angular. Also this is for a "proof of concept" project and not for any commercial or professional context, so security, performance and maintainability are not as crucial as usual.
Thanks for your time and hopefully for any help
I found a solution I guess.
Uringo from Angular-Meteor explained in another similar question on Github:
#gooor this is how Meteor's autorun works. it executes on every change
on any reactive thing inside it and a Meteor cursor is a reactive
object. If you want to re-run something only when field is changing
you can use Angular's $watch:
$scope.$watch('field', function(){ console.log('calling');
this.foos = Foos.find({field: this.field}); });
In my particular case I needed to add true as the third parameter to the $watch function. Therefore newMessageArrived() is only called when the property messages is changed.
So my initial concerns where wrong but when someone has a better, more meteorish solution i would appreciate.

Backbone best practices where to fetch models/collections

I've inherited a codebase that follows the format of: a router sets a controller, the controller fetches the collection/model needed, then the controller set/passes the view the collection/model.
The current View I'm working on loads a collection, and now I need to build in a feature where I fetch a single model after the view has rendered, based on an id clicked (note the model is from a different collection).
I only want to load the model when/if they click a button. So my question is, can I setup the model/fetch in the View, or should I be doing that in the controller? Is there a backbone best practice when adopting a controller/view setup like this?
I primarily ask because it seems easier for me to add this new feature right in the View. But is that a bad practice? I thought so, so I started down the path of triggering an event in the View for the controller to the fetch the model, and then somehow pass that back to the View (but I'm not sure really how to even do that)...it seems like a lot of unnecessary hoop jumping?
Its OK to fetch collection via views. As 'plain' backbone does not Controller, View in charge of it responsibilities.
But imho fetch collections via controller is better practice, its easier to scale and support and test.
The only difficulty is to set communication between Controller and View context event. One of the approach is trigger Message Bus event on context event like
events: {
'click .some': function() {
this.trigger('someHappend', { some: data })
}
}
and listen to this event in Controller
this.on(someViewInstance, 'someHappend', function() {
// do something in controller
})
If you already inherited code with structure you described you'd better follow it. Also you might be interested in MarionetteJS as significant improvement. Also highly recommend you to checkout BackboneRails, screencasts not free but very usefull, especially in large scale app maintain

Preventing backbone zombie views

Note: we are using backbone 1.0.0
I am relatively new to Backbone, and was going to through some of the code a ex co-worker wrote. Rather than copy pasting stuff blindly, I wanted to understand how he did things, and that's when I started wondering about the best way to handle zombie views.
var view = new editItemView({ model: this.model });
this.ui.editItemPopup.html(view.render().el).modal({ modalOverflow: true });
This creates an instance of view and pops it up in a boostrap modal. The model has Save Changes, Cancel & Delete buttons. We will look at the clean work that is done on Save changes and delete.
onDelete: function() {
this.stopListening(this.model);
this.$el.parent().modal('hide');
this.$el.remove();
},
onApplyChangesClick: function () {
this.stopListening(this.model);
this.close();
},
close: function () {
this.$el.parent().modal('hide');
}
As far as I can tell, this code won't discard the view. And if I were to add another listener to the aforementioned view
this.listenTo(this.model.AnotherItem, 'change', this.doSomething);
and then trigger the change event on this.model.AnotherItem, this.doSomething will still fire. Correct?
I did some reading on Zombie views prior to posting this question. http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/
And based on that article wouldn't I be better off if I just did
onDelete: function() {
this.close()
},
onApplyChangesClick: function () {
this.close();
},
close: function () {
this.$el.parent().modal('hide');
this.remove();
}
his.remove() will automatically call stopListening and also remove the dom element(Same as this.$el.remove)
The article that I posted also uses this.unbind()
this.unbind()will unbind any events that our view triggers directly – that is, anytime we may have calledthis.trigger(…)` from within our view, in order to have our view raise an event.
Is that still necessary in Backbone 1.0.0 (or latest version)? The article is 3 years old, so I was wondering and I couldn't find any mention of view.unbind in backbone documentation. The documentation mentions that unbind is an alias of off. So should I be doing
this.remove();
this.off();
Ok, first off let me state the obvious: a few zombie views here or there are not going to cause you any problems. All any given zombie view will do is eat up a small amount of memory and then go away when the user hits refresh or navigates away. So, if you're a little sloppy about cleaning up your references in general things will still work fine. Where you will run in to problems is when you have a lot of zombie views, say because you rendered a 20x100 table where every cell has its own View.
Now, to truly understand how to avoid zombie views you have to understand how memory works in Javascript. I encourage you to read more about that elsewhere, but here's the cliff notes version: anything you "stop using" will get cleaned up by the browser's garbage collector, and since the garbage collector can't tell exactly when you "stop using" something it actually goes by whether or not that thing has any references to it on other objects.
This is where event bindings come in to play, because they can create references that prevent a view from being garbage collected. One of the features of Backbone is that it handles cleaning up these bindings if they are made as part of a Backbone.View's initialization (ie. the events you put in an events property of the View class). So if you remove a View's element from the page, it will get garbage collected ...
... unless it has some other reference to it, like another object that uses it, or an event binding that you created using jQuery. So, as long as your Views don't have any other references you are correct: simply removing the element will be enough. But if you do have any other references, you will need to clean them up or else the View won't get garbage collected and will become a zombie view.

angular event doesn't update page

I'm converting a page in a mvc application with a lot of inline jquery javascript to angular using typescript.
The first calls works fine but I have a problem: based on a selection in a dropdown, the event updates several controls in the view, make a few ajax calls and finally update the view with the data from the calls.
Seems the conversion is working fine, but at the end of the call the page isn't updated.
I tried to remove all the old jquery code to avoid problems.
batarang and java console reports no errors.
the final ajax call is done and the result shown in a debug.
All seems to work fine, but the page isn't updated.
How can I find the problem?
thanks
Without seeing any code, it's difficult to answer but if you bind an event to an element and want to update something in the callback, you will have to use $apply
scope.$apply(function () {
// your code
});
$apply will trigger a $digest cycle, and should be used when you want to update something while being outside angular's context.
Most likely you are not handling your asynchronous calls correctly. It's impossible to tell from your question but it is a very common mistake with symptoms as you describe.
Make sure you are updating your model within the .then() method of a promise returned from an $http request. For example
someFnDoingHttpRequest().then(function(data){
// updated the model with the data
})
Also (another common mistake) make sure someFnDoingHttpRequest() returns a promise.
If you want to "find the problem" then you can use the following option.
Go to Internet Explorer (10 or 11).
Select "Internet Options" from the settings menu.
Go to the "Advanced" tab (the last tab)
Settings are listed and select "Display a notification about every script error"
Deselect the "Disable Script debugging (Internet Explorer)" and "Disable script debugging (Other)"
Run the program again, you will get notification about the real issue that happens while displaying actual result.

Does a backbone view linger on after deletion of the corresponding DOM node?

We have some legacy non-backbone code in our web application. Although we attach views to existing DOM elements there is still some yet-to-be-refactored code that deletes certain DOM elements i.e. the delete call doesn't go through the view but is more like a jQuery call $('#domID').remove();
I have a nagging feeling that the backbone view probably hangs around as a zombie, but I don't have a way to see it? Is this harmful? Should we make it priority to refactor and have all the deletes go via the view and call view.remove() and view.unbind() for proper deletion?
Would the view be garbage collected if the DOM node is deleted independently? I guess if not if it's bound to some event, but what if it isn't?
The view will only linger on if there is a reference to it somewhere. There are four sources of stray references to consider:
Bindings to models and collections, i.e. this.collection.on('reset', this.render) and such.
Bindings to DOM objects through the view's events.
Bindings to DOM objects through direct $(...).on(...) calls.
Plain old variable references such as this.current_view = new V(...).
(1) is normally handled by the view's remove method and you have to call remove yourself, there's nothing in Backbone or jQuery that can do this for you. For example: http://jsfiddle.net/ambiguous/e574Z/
(2) is easy. Backbone views use a single delegate call to bind the view's events to the view's el. So, if you remove the view's el through a simple $(x).remove() then the event reference goes away. However, if you're attaching different views to the same el, you'll need to call undelegateEvents to detach the delegate; this would normally be done in a remove method:
remove: function() {
this.undelegateEvents();
return this;
}
but, again, you have to call remove yourself somewhere.
(3) is rare but sometimes necessary in the case of window scroll events, body click events for dialogs, and things like that. Of course, you have to clean these up yourself as Backbone can't know what you're doing behind its back and the elements that you're binding to would be outside of the view's el (or you'd be in (2)). Where would you clean these up? The remove method of course.
(4) is, as always, up to you. Usually, this sort of thing is handled like this:
if(this.current_view)
this.current_view.remove();
this.current_view = null;
Yes, there's remove again.
So, if all you have are things like (2), then $('#domID').remove(); will be fine and shouldn't leave any zombies; in fact, the default remove implementation is just this.$el.remove() and the documentation says as much:
remove view.remove()
Convenience function for removing the view from the DOM. Equivalent to calling $(view.el).remove();
However, you probably have some things like (1) involved as well so adding/updating all your remove methods and calling view.remove() to remove views would be a good idea.

Resources