jQuery prevent all event bubbling - events

I'm using jQuery with it's widget factory and using custom events to handle events in my application.
This means that all my event binding looks a lot like:
//...in the widget factory code
$(this.element).addClass('customEventClass');
$(this.element).bind('mysite.loadNextPage', $.proxy(this, 'loadNextPage');
and the events are triggered by doing:
$('.customEventClass').trigger('mysite.loadNextPage');
Because the events are directly bound to the elements that need to receive them, I don't need to have these custom events to bubble up through the DOM. I know I can check whether the even has bubbled up or not by doing this in the event handler code:
if (event.target != event.currentTarget) {
event.stopPropagation();
event.preventDefault();
return;
}
But at the moment because most of the elements that are listening for the custom events don't have a handler registered for 'mysite.loadNextPage' there are 51 events generated where only 1 actually does anything. Is there a way to either:
tell jQuery not to bubble these events at all or
Add a default 'stop propagation' handler to all DOM objects that have class 'customEventClass' to stop them from bubbling up an event that they don't have a specific handler for.
Or are there any other good practices for only triggering events on elements that are interesting in those events, rather than having lots of events be triggered for elements that aren't interested in those events.

You can also return false from your event handler function to stop propagation, that's what I normally use:
Returning false from an event handler will automatically call event.stopPropagation() and event.preventDefault(). A false value can also be passed for the handler as a shorthand for function(){ return false; }. So, $( "a.disabled" ).on( "click", false ); attaches an event handler to all links with class "disabled" that prevents them from being followed when they are clicked and also stops the event from bubbling.
See http://api.jquery.com/on/

It looks like there's no good way to do it with jQuery as it is, but it's very easy to write a new function to allow this.
First I wrote a new function to stop the event from bubbling(, and also log the event why not).
function eventWrapper(event){
var logString = 'Event called: ' + event.type + ":" + event.namespace;
if (jQuery.isFunction(this.log) == true) {
this.log(logString);
}
else if (jQuery.isFunction(Logger.log) == true) {
Logger.log(logString);
}
else{
console.log(logString);
}
event.stopPropagation();
}
And now a new function that is added to jQuery.
// A global GUID counter for objects
guidWrapper: 1,
proxyWrapper: function(wrappedFn, fn, context, wrapFn ) {
var args, proxy, tmp;
if ( typeof context === "string" ) {
tmp = fn[ context ];
context = fn;
fn = tmp;
}
// Quick check to determine if target is callable, in the spec
// this throws a TypeError, but we will just return undefined.
if ( !jQuery.isFunction( fn ) ) {
return undefined;
}
// Simulated bind
args = core_slice.call( arguments, 3 );
proxy = function() {
wrappedFn.apply( context || this, args.concat( core_slice.call( arguments ) ) );
return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) );
};
// Set the guid of unique handler to the same of original handler, so it can be removed
proxy.guid = fn.guid = fn.guid || jQuery.guid++;
return proxy;
},
And then instead of binding the function like this:
$(this.element).bind('click', $.proxy(this.click, this));
Instead bind it like this.
$(this.element).bind('click', $.proxyWrapper(eventWrapper, this.click, this));
This means that when the event is triggered, the first element that is listening for that event will call event.stopPropagation on the event, and so it won't bubble up to other elements that may also be listening for that event.

Related

How do I do this via RxJs?

I have a autocomplete control that triggers a onAutoCompleteSearch() after a debounce where I retrieve results from the server. However, if the user enters text and hits enter (key code 13) then a signal should be raised that will cancel the next invocation of an autocomplete. Since this is a 3rd party control I don't have control over the invocation of onAutoCompleteSearch() that occurs after a set debounce time.
I am using a Subject to do the signalling:
private cancelAutoComplete$ = new Subject<boolean>();
If user hits enter key:
onKeyUp(e) {
if (e.keyCode === 13) {
this.cancelAutoComplete$.next(true);
this.fireExecuteSearch(); // fire full search
} else {
this.fireSearchChange(); // trigger user input change
}
}
When an autocomplete is to be executed:
onAutoCompleteSearch(e) {
console.log('starting autocomplete!');
this.cancelAutoComplete$
.first()
.defaultIfEmpty(false)
.subscribe(c => {
if (c) {
console.log('autocomplete cancelled!');
} else {
console.log('execute the autocomplete!');
this.executeAutoComplete.next(e.query);
}
});
}
the above does not quite work... what I wish to do is check the cancelAutoComplete stream for an element, if one exists then take it off the stream, if the cancel flag is true then abort the autocomplete. If there isn't an element then return a default element of false so I can continue with the autocomplete.
How can I accomplish this? Basically if there is an cancel signal pending from the onKeyUp -> keycode 13 event I want to abort the call, if not continue.
I know I can use a simple boolean to track this but wanted to know how to do it via RxJs Subjects.
Firstly I'd make cancelAutoComplete$ a BehaviorSubject initialised to false. Send it false whenever the keyCode is not 13:
private cancelAutoComplete$ = new BehaviorSubject<boolean>(false);
onKeyUp(e) {
if (e.keyCode === 13) {
this.cancelAutoComplete$.next(true); // prevent autocomplete
this.fireExecuteSearch();
} else {
this.cancelAutoComplete$.next(false); // allow autocomplete
this.fireSearchChange();
}
}
Then I'd use the takeUntil operator as part of your executeAutoComplete stream as follows:
onAutoCompleteSearch(e) {
this.executeAutoComplete.next(e.query);
}
this.executeAutoComplete
.switchMap(query => this.backend.fetchAutoCompleteResults(query).takeUntil(
this.cancelAutoComplete$.filter(c => c === true)
))
.subscribe(...);
I've assumed your backend api is named this.backend.fetchAutoCompleteResults - the takeUntil will abort it if cancelAutoComplete$ is initially true or becomes true while the call is in flight.

Event each time component becomes visible

Is there a way in Angular2 to have an event fired when my component becomes visible?
It is placed in a tabcontrol and I want to be notified when the user switches. I'd like my component to fire an event.
What I finally did (which is not very beautiful but works while I don't have a better way to do it...) is to use the ngAfterContentChecked() callback and handle the change myself.
#ViewChild('map') m;
private isVisible: boolean = false;
ngAfterContentChecked(): void
{
if (this.isVisible == false && this.m.nativeElement.offsetParent != null)
{
console.log('isVisible switched from false to true');
this.isVisible = true;
this.Refresh();
}
else if (this.isVisible == true && this.m.nativeElement.offsetParent == null)
{
console.log('isVisible switched from true to false');
this.isVisible = false;
}
}
There is no such event, but if you're using a tab control, the proper way to do this would be to create a tab change #Output for your tab control if it's custom, otherwise, most tab controls (like ng-bootstrap) have some tab change event as well.
If your component has to be aware of this, you can use this tab change event to detect which tab is visible, and if you know which tab is visible, you also know if your component is visible or not. So you can do something like this:
onTabChange(event) {
this.currentTab = /** Get current tab */;
}
And then you can send it to your component itself if you have an input:
#Input() activated: boolean = false;
And then you can apply it with:
<my-component [activated]="currentTab == 'tabWithComponent'"></my-component>
Now you can listen to OnChanges to see if the model value activated changed to true.
You can also refactor this to use a service with an Observable like this:
#Injectable()
export class TabService {
observable: Observable<any>;
observer;
constructor() {
this.observable = Observable.create(function(observer) {
this.observer = observer;
});
}
}
When a component wishes to listen to these changes, it can subscribe to tabService.observable. When your tab changes, you can push new items to it with tabService.observer.next().
You can use the ngAfterViewInit() callback
https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html
Update
The new Intersection Observer API can be used for that
https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
See also https://stackoverflow.com/a/44670818/217408
For those watching at home, you can now use ngAfterContentInit() for this, at least on Ionic anyway.
https://angular.io/guide/lifecycle-hooks
Best way to work around this limitation of Angular is to use a shared service that provides a Subject your component can subscribe to. That way new values could be pushed onto the Observable and the components which subscribe get the newest data and can act accordingly.
Fyi: The difference between a normal Observable and a Subject is that a Subject is multicast whereas an Observable could only be subscribed to by one Subscriber.
As a small example I show you a possible implementation of a shared-service and following the subscription inside the component that needs this new data.
Shared-service:
// ...
private actualNumberSubject = new Subject<number>()
public actualNumber$ = this.actualNumberSubject.asObservable()
/**
* #info CONSTRUCTOR
*/
constructor() {}
/**
* #info Set actual number
*/
setActualNumber(number: number) {
this.actualNumberSubject.next(internalNumber)
}
// ...
Push new value onto the subject from anywhere where shared.service is imported:
// ...
this.sharedService.setActualNumber(1)
Subscribe to sharedService.actualNumber$ in component to process/display that new data:
// ...
this.sharedService.actualNumber$.subscribe(number => {
console.log(number)
// e.g. load data freshly, etc.
})
// ...
I have the same purpose and cannot get a satisfy approach to it. The first answer will call so many times.
There is a compromised way I used, of course, not elegant either.
In parent component, I set a method:
parentClick() {
setTimeout(() => {
// TO-DO
This.commonService.childMethod();
}, time);
}
Maybe the method not accurate in time, but in some way, you reach the destiny.

RxJS: How would I "manually" update an Observable?

I think I must be misunderstanding something fundamental, because in my mind this should be the most basic case for an observable, but for the life of my I can't figure out how to do it from the docs.
Basically, I want to be able to do this:
// create a dummy observable, which I would update manually
var eventObservable = rx.Observable.create(function(observer){});
var observer = eventObservable.subscribe(
function(x){
console.log('next: ' + x);
}
...
var my_function = function(){
eventObservable.push('foo');
//'push' adds an event to the datastream, the observer gets it and prints
// next: foo
}
But I have not been able to find a method like push. I'm using this for a click handler, and I know they have Observable.fromEvent for that, but I'm trying to use it with React and I'd rather be able to simply update the datastream in a callback, instead of using a completely different event handling system. So basically I want this:
$( "#target" ).click(function(e) {
eventObservable.push(e.target.text());
});
The closest I got was using observer.onNext('foo'), but that didn't seem to actually work and that's called on the observer, which doesn't seem right. The observer should be the thing reacting to the data stream, not changing it, right?
Do I just not understand the observer/observable relationship?
In RX, Observer and Observable are distinct entities. An observer subscribes to an Observable. An Observable emits items to its observers by calling the observers' methods. If you need to call the observer methods outside the scope of Observable.create() you can use a Subject, which is a proxy that acts as an observer and Observable at the same time.
You can do like this:
var eventStream = new Rx.Subject();
var subscription = eventStream.subscribe(
function (x) {
console.log('Next: ' + x);
},
function (err) {
console.log('Error: ' + err);
},
function () {
console.log('Completed');
});
var my_function = function() {
eventStream.next('foo');
}
You can find more information about subjects here:
https://github.com/ReactiveX/rxjs/blob/master/docs_app/content/guide/subject.md
http://reactivex.io/documentation/subject.html
I believe Observable.create() does not take an observer as callback param but an emitter. So if you want to add a new value to your Observable try this instead:
var emitter;
var observable = Rx.Observable.create(e => emitter = e);
var observer = {
next: function(next) {
console.log(next);
},
error: function(error) {
console.log(error);
},
complete: function() {
console.log("done");
}
}
observable.subscribe(observer);
emitter.next('foo');
emitter.next('bar');
emitter.next('baz');
emitter.complete();
//console output
//"foo"
//"bar"
//"baz"
//"done"
Yes Subject makes it easier, providing Observable and Observer in the same object, but it's not exactly the same, as Subject allows you to subscribe multiple observers to the same observable when an observable only send data to the last subscribed observer, so use it consciously.
Here's a JsBin if you want to tinker with it.
var observer = Observable.subscribe(
function(x){
console.log('next: ' +
var my_function = function(){
Observable.push('hello')
One of the way to update an observable.

Backbone pass object with event

Reading up on tutorials of Backbone, it seems that when the add event is fired from a collection, the item added is sent along with the event (same goes for remove). I can't find any documentation on this feature on the backbonejs.org site and was curious if there was a way I could send an object along with my custom events. Secondly, is something like this possible in Marionette?
Each object defined by Backbone mixes in Backbone.Events which means you can trigger events with object.trigger. It is defined as
trigger object.trigger(event, [*args])
Trigger callbacks for the given event, or space-delimited list of events. Subsequent arguments
to trigger will be passed along to the event callbacks.
You just have to pass additional arguments to get them in your callbacks.
For example,
var m = new Backbone.Model();
m.on('custom', function(more) {
console.log(more);
});
m.trigger('custom', 'more info');
will log more info
See http://jsfiddle.net/nikoshr/HpwXe/ for a demo
You would trigger an event with a reference to the object to emulate the behavior of backbone :
var m = new Backbone.Model();
m.on('custom', function(model, more) {
console.log(arguments);
});
m.trigger('custom', m, 'more info');
http://jsfiddle.net/nikoshr/HpwXe/1/
And in a derived model:
var M = Backbone.Model.extend({
custom: function() {
this.trigger('custom', this);
}
});
var m = new M();
m.on('custom', function(model, more) {
console.log(model);
});
m.custom();
http://jsfiddle.net/nikoshr/HpwXe/2/
Yes of course, you can use Backbone.Event
var collection = Backbone.Collection.extend();
collection = new collection();
collection.on("message", function(message){
console.log(message);
});
var model = new Backbone.Model();
collection.add(model);
model.trigger("message", "This is message");
About what types of events you can see to backbone documentation.
This is demo
Also you can use Event Aggregator from Marionette.js
An event aggregator implementation. It extends from Backbone.Events to provide the core event handling code in an object that can itself be extended and instantiated as needed.
var vent = new Backbone.Wreqr.EventAggregator();
vent.on("foo", function(){
console.log("foo event");
});
vent.trigger("foo");

Can arguments be passed just by reference to function in javascript

Look at this:
addEventListener("mouseover", function(e){..code..},false);
can be written as
function mouseover(e){ ... }
addEventListener('mouseover', mouseover, false);
so here mouseover function receives event object even though it was just referenced and not passed any parameters. I'd think that maybe addeventlistener function executes all functions referenced/anonymous like this:
....
suppose like this
function addeventlistner (a,b,c){
b(e);
}
Why I am asking this?
I can't understand where e come from in addeventlistner function function (e) {...code...}
.
As I understand this second parameter of addeventlistner can be either target object or function. Which is passed an event object. It makes sense that function can do something with object passed to it, what does the object would do with this passed event object.
the reason for this is that I was trying to understand in some example codes that function took e from addeventlistner, similar to one above, and used properties like e.msg, e.data, and e.cmd.....which I have no clue where they are coming from.
Are they properties of Event object? I can't find them!
When you add an event listener the function you add gets passed an event object depending on what type of event it is (click, scroll, etc).
http://www.w3schools.com/jsref/dom_obj_event.asp
window.addEventListener("click", function(event) {
alert(event.pageX + ", " + event.pageY);
}, false);
// same as
window.addEventListener("click", function(e) {
alert(e.pageX + ", " + e.pageY);
}, false);
The parameter name can be anything it still represents the event object.
EDIT List all of the properties and functions of an event object
window.addEventListener("click", function(event) {
var all = "";
for(var prop in event) {
all += prop + " : " + event[prop] + "\n";
}
alert(all);
}, false);

Resources