I'm trying to get my head around custom events. I want some entities to be listeners, and then be able to fire an event without knowing who is listening.
I have this afterrender handler in the main controller, confirmed it is being called and a tab control is "on" for the event userTimeOut:
// Register components
registerComponent: function (component) {
console.log('registering\tid\t' + component['id']);
if (component['id'].indexOf('tab-') > -1) {
console.log("tab getting user timeout event handler\t" + component['id']);
component.on('userTimeOut', this.onUserTimeOut);
}
return true;
},
This is the onUserTimeOut handler:
onUserTimeOut: function (caller) {
console.log('user-time-out\tid' + this['id'] + '\tcaller-id\t' + caller['id']);
}
And I have this code in the main controller called by a button click. Confirmed it does get called as well:
fireUserTimeOut: function(button) {
console.log('fireUserTimeOut\t' + button['id']);
Ext.GlobalEvents.fireEvent('userTimeOut', button);
},
Searching the web, I have tried numerous approaches instead of Ext.GlobalEvents, but no luck. Eventually, I want to be able to have any entity: component, data store, application, view, etc. act as a listener and be able to broadcast the event and have it acted upon.
I can get it to work with built-in events like click or focus, but not these custom events.
Any assistance would be appreciated!
Instead of:
component.on('userTimeOut', this.onUserTimeOut);
Use:
Ext.GlobalEvents.on('userTimeOut', this.onUserTimeOut, component);
Then you can call fireEvent as you are doing. In your onUserTimeOut you can access the component acting on the event via this, e.g. this.id.
Related
I need to attach an Event called render to a panel element, that does nothing but being dispatched to warn all the listeners whenever panel is rendering.
Following the The old-fashioned way section of this link, I came up with this code:
/**
* **Static** Re-draw the layer panel to represent the current state of the layers.
* #param {Element} panel The DOM Element into which the layer tree will be rendered
*/
static renderPanel(panel) {
// Create the event.
var render_event = document.createEvent('Event');
// Define that the event name is 'render'.
render_event.initEvent('render', true, true);
// Listen for the event.
panel.addEventListener('render', function (e) {
// e.target matches panel
}, false);
panel.dispatchEvent(render_event);
This seems to have worked but as this is my first time doing this, I am not quite sure how to check the correctness of this method.
Looking inside the console I can see my panel element dispatching the render Event, but I'd like to ask if there's something I am missing or to be worried about before moving on.
To debug the result, I tried add an event listener to the document element like document.addEventListener("render",console.log("ciao")), which in turn printed ciao once in the console, but only just once.
I thought I would be able to see as many "ciao" in the console as the times the render Event was triggered, but this does not seem the case.
If you're trying to check everytime your event is fired, the second argument of addEventListener (taking into account what you're willing to achieve) should be a function callback using an event object as argument, like this for example:
document.addEventListener("render", function(e) { console.log("ciao"); });
In your example you're executing console.log("ciao"), not passing a function reference (anonymous or not), this is why it executes only one time: when the page loads/evaluates your script.
mdn guide on creating and dispatching custom events (same as your link)
The old fashioned method seems to still be working fine when I tried it, I saw the document event listener console log each time I triggered the event.
The updated way is:
panel.dispatchEvent(new CustomEvent('render'));
let div = document.querySelector('div');
div.addEventListener('old-event', () => {console.log('Old-fashinoed event caught')});
div.addEventListener('new-event', () => {console.log('New-fashioned event caught')});
let oldEvent = document.createEvent('Event');
oldEvent.initEvent('old-event', true, true);
let newEvent = new CustomEvent('new-event');
setInterval(() => {
div.dispatchEvent(oldEvent);
div.dispatchEvent(newEvent);
}, 1000);
<div>I emit an old-fashioned and a new-fashioned event every 1 second</div>
I'm trying to use the function window.addEventListener('beforeunload', function (event) {...}
In the function, I want to know which action triggered the listener.
I tried to use this method with no success:
window.addEventListener('beforeunload', function (event) {
if (performance.navigation.type == 1) {
document.write('Window was refreshed!');
}
else {
document.write('Window was closed!');
}
});
Any action (refresh/close/'go back') triggers the else part... what am I doing wrong?
Alternative method would be appreciated as well.
Well, you always trigger the else handler, because of the way peformance.navigation works.
Even if you write it in the beforeunload handler, it still runs on your current document, not the one you are loading, so it will output the PerformanceNavigation object that contains info on how you opened your current page, and not the one you are trying to navigate to.
As to the part in your question about deteciting whatever method user used to leave the page, i don't think thats possible at the moment, not without usage of 'dirty' hacks/workarounds (detecting which button has user clicked, detecting keyboard events, etc.)
use router events You can subscribe in your app.component like
ngOnInit() {
this.router.events
.subscribe((event) => {
// example: NavigationStart, RoutesRecognized, NavigationEnd
console.log(event);
});
}
I use last version free-jqGrig by Oleg.
I know that in versions, free-jqGrid, many other events are added in difference from jqGrid.
http://www.trirand.com/jqgridwiki/doku.php?id=wiki:events#list_of_events
Has re-read many similar answers, but events don't work for me.
jqGrid 'clearToolbar' without grid reload
Here something similar, but in an example an event when pressing the custom button.
It is necessary for me that when pressing on to ClearToolbar to add the custom check on event "jqGridToolbarBeforeClear" or "jqGridToolbarAfterClear".
The main reason of your problem is the usage of wrong event. The event jqGridToolbarBeforeClear will be triggered inside of the method clearToolbar, but you want to prevent processing of reloading of the grid inside of triggerToolbar. Thus you should use jqGridToolbarBeforeSearch event instead.
The mostly correct implementation of event handler jqGridToolbarBeforeSearch looks like the following:
$("#grid").on("jqGridToolbarBeforeSearch", function (e) {
var filters = $(this).jqGrid("getGridParam", "postData").filters;
if (typeof filters === "string") {
filters = $.parseJSON(filters);
}
if (filters) {
/* add here you custom tests */
return "stop";
}
return e.result; // forward the result of the last event handler
});
The main advantage of the usage events comparing to callback is the following: one can define multiple event handlers, but only one callback. If one event returns "stop" to prevent processing then the next event could overwrite the value with another value. To allow to stop processing in case of any event handler return "stop" one should use event.result in every event handler.
I have a project in React which should be able to be placed on any website. The idea is that I host a javascript file, people place a div with a specific ID, and React renders in that div.
So far this works, except click-events. These evens are handled at the top level. This is all good, but one of the sites where the app should be placed, has stopPropagation() implemented for a off-canvas menu. Because of this the events aren't working properly.
I tried catching all events at the root-element, and dispatching them manually:
this.refs.wrapper.addEventListener('click', (event) => {
console.log(event);
console.log(event.type);
const evt = event || window.event;
evt.preventDefault();
evt.stopImmediatePropagation();
if (evt.stopPropagation) evt.stopPropagation();
if (evt.cancelBubble !== null) evt.cancelBubble = true;
document.dispatchEvent(event);
});
This doesn't work, because the event is already being dispatched:
Uncaught InvalidStateError: Failed to execute 'dispatchEvent' on 'EventTarget': The event is already being dispatched.
What would be the right way to fix this problem? Not using the synthetic events from React doesn't seem the right way to go for me..
Argument 'event h'as already been dispatched.
You should clone a new eventobject with old event.
var newevent = new event.constructor(event.type, event)
Ther is no solution yet. React, as you say, listen events on the root of DOM, and filter events if their event.target not inside react's mounted node.
You can try:
1. Redispatch new event in the Reract component, but it will be stopped at outside handler too.
2. Dispatch new event outside Reract component, higher (closest to BODY) then node with stopPropagation callback. But event.target will point to node, which not inside React's component and you can not change it, beacause it is readonly.
Maybe in next versions they will fix it.
But you can listen for events in the document, no?
Let say your root component for the whole app is named app. Then, inside it's componentDidMount you can have:
// when the main App component mounts - we'll add the event handlers ..
componentDidMount() {
var appComponent = this;
document.addEventListener('click', function(e) {
var clickedElement = e.target;
// Do something with the clickedElement - by ID or class ..
// You'll have reference to the top level component in `appComponent` ..
});
};
As you said - React handles all events at the top-level node (document), and to determine which react component relates to some event react uses event.target property. So to make everything work you should manually dispatch stopped event on document node and set proper "target" property to this event.
There is 2 problems to solve:
You can't trigger event that is already dispatched. To solve this you have to create a fresh copy of this event.
After you do dispatchEvent() on some node browser automatically set "target" property of this event to be the node on which event is fired. To solve this you should set proper target before dispatchEvent(), and make this property read-only using property descriptors.
General solution:
Solution tested in all modern browsers and IE9+
Here is a source code of solution:
https://jsbin.com/mezosac/1/edit?html,css,js,output . (Sometimes it hangs, so if you don't see UI elements in preview area - click on "run win js" button on top right corner)
It is well commented, so I will not describe here all of that stuff, but I will quickly explain main points:
Event should be redispatched immediately after it was stopped, to achieve this I extended native stopPropagation and stopImmediatePropagation methods of event to call my redispatchEventForReact function before stopping propagation:
if (event.stopPropagation) {
const nativeStopPropagation = event.stopPropagation;
event.stopPropagation = function fakeStopPropagation() {
redispatchEventForReact();
nativeStopPropagation.call(this);
};
}
if (event.stopImmediatePropagation) {
const nativeStopImmediatePropagation = event.stopImmediatePropagation;
event.stopImmediatePropagation = function fakeStopImmediatePropagation() {
redispatchEventForReact();
nativeStopImmediatePropagation.call(this);
};
}
And there is another one possibility to stop event - setting "cancelBubble" property to "true". If you take a look at cancalBubble property descriptor - you will see that this property indeed is a pair of getters/setters, so it's easy to inject "redispatchEventForReact" call inside setter using Object.defineProperty:
if ('cancelBubble' in event) {
const initialCancelBubbleDescriptor = getPropertyDescriptor(event, 'cancelBubble');
Object.defineProperty(event, 'cancelBubble', {
...initialCancelBubbleDescriptor,
set(value) {
redispatchEventForReact();
initialCancelBubbleDescriptor.set.call(this, value);
}
});
}
redispatchEventForReact function:
2.1 Before we dispatch event for react we should remove our customized stopPropagation and stopImmediatePropagation methods (because in react code some component in theory can invoke e.stopPropagation, which will trigger redispatchEventForReact again, and this will lead to infinite loop):
delete event.stopPropagation;
delete event.stopImmediatePropagation;
delete event.cancelBubble;
2.2 Then we should make a copy of this event. It's easy to do in modern browsers, but take a looot of code for IE11-, so I moved this logic in separate function (see attached source code on jsbin for details):
const newEvent = cloneDOMEvent(event);
2.3 Because browser set "target" property of event automatically when event is dispatched we should make it read-only. Important bit here - setting value and writeable=false will not work in IE11-, so we have to use getter and empty setter:
Object.defineProperty(newEvent, 'target', {
enumerable: true,
configurable: false,
get() { return event.target; },
set(val) {}
});
2.4 And finally we can dispatch event for react:
document.dispatchEvent(newEvent);
To guarantee that hacks for react will be injected in event before something stopped this event we should listen to this event on root node in capturing phase and do injections:
const EVENTS_TO_REDISPATCH = ['click'];
EVENTS_TO_REDISPATCH.forEach(eventToRedispatch => {
document.addEventListener(eventToRedispatch, prepareEventToBeRedispatched, true);
});
There are many contentcontrols in a document and I need to find out a way that the cursor is in which content control so that I will select that control and do the operation accordingly. I think by implementing onEnter and onExit events for contentcontrols , I can achieve it. But I don't know how to declare and invoke those eventhandlers in JavaScript API. Any help is really appreciated.
You would need to use a combination of APIs to implement that functionality with the current API set:
First add an event handler for the Document.selectionChanged event.
Every time the event fires, get the Range object corresponding to the selection in the document, using the Document.getSelection() API.
Check the range to see if there's a content control in it, using the Range.contentControls relationship.
-Michael (PM for add-ins)
Good question! We do have an onEnter event for content controls (we call it binding.selectionChanged. We also have a binding.dataChanged event who gets triggered if the user changes the content and exits the content control
so an alternative solution to what Michael proposed is to create bindings for each content control in the document and then register for such events.
you can achieve this by:
1. traversing the content control collection.(use body.contentControls collection)
2. for each content control, grab or set the title and use it to create a binding by named item. check the bindings.addFromNamedItem method.
3. on the callBack make sure to subscribe to the selectionChanged (or DataChanged) for the binding.
the create binding code and register to the events will look like this:
function CreateCCSelectionChangedEvent() {
Office.context.document.bindings.addFromNamedItemAsync("TitleOfTheContentControl", { id: 'Binding01' }, function (result) {
if (result.status == 'succeeded') {
result.value.addHandlerAsync(Office.EventType.BindingSelectionChanged, handler);
}
});
}
function handler() {
console.log("Event Triggered!");
}
Hope this helps!
Michael,
My company tried this approach a few years ago in a VSTO addin. It ended badly. The problem is the number of events you have to handle is horrific. The performance penalty is drastic and grows with the document size.