find which event is triggred in backbone js event callback function - backbone.js

obj.on('evt_A evt_B evt_c', function(eventData){
console.log("Is it possible here to find which event is triggered. As this callback is registered for three events. This callback is like a central callback for all the events on this object.")
})
obj.trigger('evt_A evt_B evt_c', [{eventDataForevt_A}, {eventDataForevt_B, {eventDataForevt_C}}])
There is one way of doing it, which is having a property in eventDataForevt_<A||B||C> which says the name of the event. But is it possible to do this without modifying the eventDataForevt_<A||B||C> ?

Check out this fiddle: http://jsbin.com/ucevov/4/edit
It doesn't seem possible to detect the event name from your obj.on - note that evt_c in this._events is true even when evt_c is not called.
However there is a special "all" event which passes in the name of the calling event as the first parameter, so
obj.on("all", function(eventName, eventData) {
console.log(eventName);
});
should output the name of the event that was called.

Related

How do I call triggerHandler with a specific fake 'event' parameter?

I'm trying to test that on a link click 'preventDefault' is called. However, I have difficulties to replace a real 'event' object with one I can spy on:
Here is how I trigger the click event:
var e = jasmine.createSpyObj('e', [ 'preventDefault' ]);
$element.triggerHandler('click', [e]);
However, when the directive code is being run, the event element is not replaced with a fake one:
$element.on('click', function(event) {
console.log(event);
}
I tried different ways of adding a second parameter to triggerHandler - as an array, as an object, just some line, etc. Neither worked.. It is also not that many examples of triggerHandler together with additional parameters, so I feel a little bit lost...
Thanks in advance!
We also ran into this issue when we wanted to pass an event for testing that had a specific value for the which property.
As c0bra pointed out, the triggerHandler function creates its own dummy event object which it then passes on to the event handler. However, you can take advantage of the fact that the triggerHandler function will extend the dummy event with an event object passed in if there is a valued type property on the event:
https://github.com/angular/angular.js/blob/v1.6.5/src/jqLite.js#L1043
triggerHandler: function(element, event, extraParameters) {
...
// If a custom event was provided then extend our dummy event with it
if (event.type) {
dummyEvent = extend(dummyEvent, event);
}
...
}
After inspecting the Angular code, we found that when you call element.triggerHandler from your test, it will first call an anonymous function which then calls Angular's triggerHandler function. When Angular's triggerHandler function is called, it is passed the element as the first argument, then the first argument that you passed to triggerHandler in your test as the second argument, then your second argument that you passed to triggerHandler as the third etc. https://github.com/angular/angular.js/blob/v1.6.5/src/jqLite.js#L1068
What that then means is you can pass an object to triggerHandler in your test as the first argument, and that object's properties will be appended to the dummy event object (or overwritten if there was already a property on the dummy object with the same name), thus allowing you to pass specific values and add spies for testing.
Note that the event type will need to be passed to triggerHandler as the type property on your object so as to satisfy the if condition mentioned above. As an example, this is how we used triggerHandler in our test:
element.triggerHandler({
type: 'keydown',
which: 13
});
The docs say that triggerHandler() passes a dummy object to the handler: http://docs.angularjs.org/api/ng/function/angular.element
If you check the source, you can see that triggerHandler() creates its own event object, and then passes your second argument as the event data, not the actual event object:
https://github.com/angular/angular.js/blob/master/src/jqLite.js#L882
Relevant code:
var event = [{
preventDefault: noop,
stopPropagation: noop
}];
forEach(eventFns, function(fn) {
fn.apply(element, event.concat(eventData));
});
I've used jQuery's internal event simulator for creating my own events. That may work for you: http://wingkaiwan.com/2012/09/23/triggering-mouse-events-with-jquery-simulate-when-testing-in-javascript/

Stickit: How to trigger a change event after every model -> view change

I have many events bound to elements in my view, though when I use stickit js to change values in my view by altering the model it doesn't trigger an onChange event.
Is there a way that I can trigger an onchange event for the current model:element after the setting the value in the model without having to write a handler for every binding? This would be for all form elements, input/select/textarea.
I want to avoid the following for each form element on the page:
bindings: {
'#foo': {
observe: 'foo',
afterUpdate: 'forceChange'
},
'#bar': {
observe: 'bar',
afterUpdate: 'forceChange'
},
...
},
forceChange: function(el) { jQuery(el).change() }
One possible hack (with version 0.6.3 only) would be to define a global handler which matches all elements:
Backbone.Stickit.addHandler({
selector: '*',
afterUpdate: function($el) {
$el.trigger('change');
}
});
Since handlers are mixed in with other matching handlers and bindings configurations in the order that they are defined, you couldn't use afterUpdate in any of your bindings without overwriting this global, all-matching handler since the bindings configurations are the last to be mixed in. You can read more about it here.
Ahhh, that comment clarifies matters. So, in Javascript when you change an input's value "manually" (whether through jQuery or through someElement.value =) the browser won't, as you noticed, fire a change event. Change events (and most other events for that matter) are only fired in response to user actions, not to Javascript.
Luckily, just as you can "manually" change a value, you can also "manually" trigger an event. In jQuery the syntax for that is:
$(yourElement).trigger('change');
If you need to control things like e.target you can read up on the jQuery trigger documentation for the details, but that's the basic idea.
You can even chain the value-changing and event-triggering together if you want:
$(yourElement).val('newValue').trigger('change');

backbone model with an array/object property: infinite 'change' event triggered after sync()?

My backbone.js model has an array property. I bound the change event to save().
After sync() (triggered by save(), my app server returns an identical JSON, but backbone thinks the array has been changed (due to a different reference to the array I guess?), and trigger changes again. Then an infinite loop occurs.
save() -> sync() -> triggered `change` -> save()...
What shall I do?
Idea: I can bind the change event to a function that checks if the changed attributes are of type object/array, and do a deep comparison and call save only if the array/object really changed. If true then save()?
Thanks!
Try the Edge version of Backbone (master branch) this behavior changed after 0.9.9 - see https://github.com/documentcloud/backbone/pull/2004
Backbone has a special option on many methods to prevent just this sort of issue: silent:true. If you pass that option to your save method, the resulting sync won't trigger a change event.
So, if you want to set your change event handler to save silently, something like:
changeHandler: function() {
this.save({silent:true});
}
should do the trick.

AngularJS - Detecting, stalling, and cancelling route changes

I can see the event $routeChangeStart in my controller, but I don't see how to tell Angular to stay. I need to popup something like "Do you want to SAVE, DELETE, or CANCEL?" and stay on the current "page" if the user selects cancel. I don't see any events that allow listeners to cancel a route change.
You are listening to the wrong event, I did a bit of googling but couldn't find anything in the docs. A quick test of this:
$scope.$on("$locationChangeStart", function(event){
event.preventDefault();
})
In a global controller prevented the location from changing.
The documented way of doing this is to use the resolve property of the routes.
The '$route' service documentation says that a '$routeChangeError' event is fired if any of the 'resolve' promises are rejected.1 That means you can use the '$routeProvider' to specify a function which returns a promise that later gets rejected if you would like to prevent the route from changing.
One advantage of this method is that the promise can be resolved or rejected based on the results of asynchronous tasks.
$scope.$watch("$locationChangeStart", function(event){
event.preventDefault();
});
You can do like this as well. Benefit of doing this way is that it
doesn't trigger through if() statement with $on ...as you well see
that below code will trigger no matter what the condition is:
if(condition){ //it doesn't matter what the condition is true or false
$scope.$on("$locationChangeStart", function(event){ //this gets triggered
event.preventDefault();
});
}

How do you "preview" user actions like resize or editing in GoDiagrams?

The GoDiagram object model has a GoDocument.
GoViews have a reference to a GoDocument.
If the user does any modification on the diagramming surface, a GoDocument.Changed event is raised with the relevant information in the event arguments.
I would like to be notified when some user-actions happen, so that I can confer with my Controller (disallow/cancel it if need be) and then issue view-update orders from there that actually modify the Northwoods GoDiagram third party component.
The Changed event is a notification that something just happened (past tense) - Doing all of the above in the event handler results in a .... (wait for it)... StackOverflowException. (GoDocument.Changed handler > Updates GoDocument > Firing new Changed events.. )
So question, how do I get a BeforeEditing or BeforeResizing kind of notification model in GoDiagrams? Has anyone who's been there lived to tell a tale?
JFYI...
The component-vendor recommendation is to subclass and override appropriate methods for this. Override the bool CanXXX() method, raise a cancelable custom event. If the subscriber returns false, bail out (return false to abort the user action) of CanXXX.
No built-in mechanism for this in GoDiagrams.
For example, you could define a
CustomView.ObjectResizing cancelable
event. In your override of
GoToolResizing.CanStart, you can raise
that event. If the
CancelEventArgs.Cancel property
becomes true, you would have
CanStart() return false.
Source http://www.nwoods.com/forum/forum_posts.asp?TID=2745
The event arguments (GoChangedEventArgs) for the change event has a property IsBeforeChanging which indicates whether the change event was raised from the "RaiseChanging" method (true), or the RaiseChanged (false). That should tell you whether the change has occurred yet, but I know of no way to cancel it.
The best I can suggest is instead of checking if the change is allowed and performing it, check if the change is not allowed, and if it isn't call the "Undo" method on the arguments in the change event. So essentially:
OnChanged(GoChangedEventArgs e)
{
if(NotAllowed)
{
e.Undo();
}
}

Resources