Detach event handlers selectively - javascript

I have a html richText editor. My code structure is like this:
function richTextEditor(div)
{
var self=this;
self.instanceIdentifier=Math.floor(Date.now());
//Richtext editor creation logic
$(document).on('click.'+self.instanceIdentifier,function()
{
//some logic
})
self.destroy=function()
{
//delete all properties of self
// detach all listeners
$(document).off('click.'+self.instanceIdentifier) ;
}
}
Our app is single page application, and there are multiple richtexteditor instances opened in different panes. I need to destroy the instance when the node corresponding to this has been removed. Destroy should remove all the event handlers attached by that instance.
So far Date.now() for uniquely identifying the handler is working but I think there must be some elegant way to do that.
var div1=$('#notes')[0];
var editorInstance1=new richTextEditor(div1);
//remove is not a valid jquery event, its just for illustration
// I am getting remove event from another library
$(div1).on('remove',function(){
editorInstance1.destroy();
})
Please suggest if this is the correct way to go.

What you want is a GUID or UUID. There is a great answer to this question here.

Related

How to Get Event Listener of an Element

Is there any way to get the list of all event listeners of an element on the HTML page using JavaScript on that page.
Note: I know we can see them with Chrome dev tools event listeners but I want to log/access see list using the JavaScript of the page.
Also, I know we can get them through jQuery but for that, we also have to apply the events using jQuery, but I want something that would be generic so I could also access the event listeners applied to other elements such as web components or react components.
If you really had to, a general way to do this would be to patch EventTarget.prototype.addEventListener:
const listeners = [];
const orig = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function(...args) {
if (this instanceof HTMLElement) {
listeners.push({
type: args[0],
fn: args[1],
target: this,
});
}
return orig.apply(this, args);
};
document.body.addEventListener('click', () => console.log('body clicked'));
console.log(listeners[0].fn);
click this body
To find listeners attached to an element, iterate through the listeners array and look for targets which match the element you're looking for.
To be complete, also patch removeEventListener so that items can be removed from the array when removed.
If you need to watch for listeners attached via on, then you'll have to do something similar to the above to patch the HTMLElement.prototype.onclick getter/setter, and for each listener you want to be able to detect.
That said, although you said you want a generic solution, rather than patching built-in prototypes, it'd be better to add the listeners through jQuery or through your own function.
What I did when I had a similar problem is add a data attribute when the listener was set, so I could identify it later.
At the end of the function that adds the listener:
elm.setAttribute('data-has_mask', true);
At the beginning of that same function:
if("true" == elm.getAttribute('data-has_mask')) {
return;
}
Maybe not exactly what the OP is looking for, but I was having a lot of trouble with this, and this is an obvious solution for a particular use case, and I guess it might help someone out.

How to implement OnEnter and OnExit event on ContentControl using Javascript API for Word 2016

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.

Event based editor like in StarCraft 2 Editor (algorithm)

I'm trying to create an algorithm for an event based editor like in StarCraft 2 Editor that can support:
Create UI
Play sounds
Handle keyboard/mouse inputs
Display messages
Button(or some referenced UI object) is pressed etc.
Pretty much the same thing as in StarCraft 2 Editor (of course not the 3D stuff too)
So far I'm thinking to use JSON , add every event in an object and then loop through them and create an event using the addEventListener() method.
The JSON Events Object(of course it will be created by the user in the editor with no programming):
var Events={
//your event's names here
onReady:{ //on page ready to manipulate
displayMessage:{//just a simple popup
text:"Hello user!",
title:"Welcome!",
type:"normal",
},
createButton:{ //creates a buton on the screen
text:"Click me!",
id:"myButton"
}
},
onClick:{
id:"myButton" ,//the id of the button we just created
actions:{ //the actions applied after we click the button
displayMessage:{//just a simple popup
text:"You pressed me!",
title:"Button",
type:"error",//show the message as an error
}
}
}
}
I found some softwares (GameMaker,Construct 2,GameDevelop) that have an event based editor if you would like to get an idea about what I'm talking about (if you don't already know about StarCraft 2 Editor)
My question is:
What is the best algorithm that I can use to achieve this?
Sounds like a job for jQuery UI.
When the user creates a custom area in your editor all it's attributes are stored inside an object (that you can save as JSON) that would then be applied to a div as param when loading the map (using html-attributes.
function create_areas(areas){
var map = $('#map_area');
for(var i=0;i<areas.length;i++){
map.append($('<div>', area[i].params));
}
}
whereas params would look something like this:
params = {
width: 100,
height: 200,
....
mousedown: function(){ play_music('hello'); },
keydown: function(e){ alert('you pressed ' + e.keyCode; }
}
also the jQuery UI tools like draggable and resizeable should ease up building your editor.
I'd model this more after backbone's event system:
events: {
'click selector': handler,
'mouseover selector': handler2,
...
}
Handlers can be any javascript function, this would allow you to create a bunch of pre-defined functions like displayMessage.
Then you could curry your own handlers, which would allow your users to specify configuration if they need it.
Example:
var events = {
'click element': displayMessage({
text:"Hello user!",
title:"Welcome!",
type:"normal",
}),
'mouseover pizza': createButton({...})
}
function displayMessage(options) {
var options = options;
return function() {
//display message logic
}
}
Then you can supply a compose function among other helpers (look up promises perhaps?) to combine your functions together:
var events = {
'click element': compose(
displayMessage({
text:"Hello user!",
title:"Welcome!",
type:"normal",
}),
createButton({})
),
'mouseover pizza': createButton({...})
}
This could work out?
Caveat: it might be better if events was an array that contained objects. That way you can have multiple click handlers on some selector without collisions.
The way I see this there are really severall choices you need to make. I would, although I prefer JSON as a data construct not limit myself to this subset of an actuall programming language. And engener this the other way around.
You have events, handlers and options. Where a option, or better a option list is the user inputed data, the handlers are the actual action, and the events are triggers to set some action off.
If you read this carefully you will notice this is the exact description of the basic structure of most jQuery-Scripts or Event-Driven Software in generall. Only the users options in jQuery are (since it is a DOM Framework) most often the context of a single DOM-Element. So, here we are and I would suggest to simply borrow the theorie behind this and make use of promisses wich make a very clear and great way to generate code!
So my call to any event chain would look like this.
...when(chainObject['event'])
.then(function(event) {
//call handler
handlers[chainObject[selectedHandler]].call(event.context, chainObject['options']);
//apply next element(s) in chain, this is the current promise
appendNextElement(chainObject['followingHandlers'], this);
})...
Notice how apply makes it easy for you to change the environement and in turn behaviour of any hanlder based on what the user and event did. And promisses make error handling very easy!
This of course applies to only one node in your chain. So what should a data structure look like to let you generate this kind of code?
One node in your structure would look like this:
{
event: 'click',
selectedHandler: 'sohwText',
options: {
'text': 'helloWorld'
},
followingChain: {...OTHER HANDLERS....}
}
The important thing to notice is that like a good structured functional programm you are looking at a tree and not at a simple list of events. So every actual DOM Element holds many of these
var eventTree = {
'.someButton': [..Handlers of this button...],'
'.someOtherButton': [..Handlers of the other button...],
}
And there we go. You have a context (the button), a event, user input and a handler.
The resulting app should not only work, but will be styled for any experienced JavaScript-Programmer to expand or mod.

JavaScript - how to check if event already added

I want to add an listener exactly once for beforeunload. This is my pseudocode:
if(window.hasEventListener('beforeunload') === false) {
window.addEventListener('beforeunload', function() { ... }, false);
}
But hasEventListener does not exist obviously. How can I achieve this? Thanks.
In fact there is no need to check if an listener was added to a target:
If multiple identical EventListeners are registered on the same EventTarget with the same parameters, the duplicate instances are discarded. They do not cause the EventListener to be called twice, and since the duplicates are discarded, they do not need to be removed manually with the removeEventListener method.
Source:https://developer.mozilla.org/en-US/docs/Web/API/EventTarget.addEventListener#Multiple_identical_event_listeners
Using jquery you can do use data("events") on any object (here the window) :
var hasbeforeunload = $(window).data("events") && $(window).data("events").['beforeunload'];
But this works only for jquery added events.
In a more general case, you should simply store the information that you add a listener somewhere :
var addedListeners = {};
function addWindowListenerIfNone(eventType, fun) {
if (addedListeners[eventType]) return;
addedListeners[eventType] = fun;
window.addEventListener(eventType, fun);
}
I think there is no standard way in javascript to get the existing event handlers. At best you could surcharge the addEventListener function of Node to intercept and store the listeners but I don't recommend it...
EDIT :
From jQuery 1.8, event data are available in $._data(element, "events"). The change log has a warning that should be taken into account :
Note that this is not a supported public interface; the actual data
structures may change incompatibly from version to version.
In Chrome Dev tool, you can check all events attached to an element (For debugging)-
// print all events attached to document
var eventObjectAttachedToDocument = getEventListeners(document);
for (var event in eventObjectAttachedToDocument) {
console.log(event);
}

How do I dispose of a prototype in JS?

I've got a thorny issue and although the answer may be obvious, I cannot see how to do what I'm trying to do.
I have created a script library for my application that uses JS prototypes and templating to dynamically instantiate DOM elements AND to wire those elements up with handlers. An example is the following:
var ppane = new AWP.iuiPanel(theObject, { title: 'Select filter(s)', idDisplay: 'block', idString: params.sender._options['title'] });
AWP.iuiPanel is a class defined as a function prototype, e.g:
AWP.iuiPanel = function() { <i'm a constructor> }
AWP.iuiPanel.prototype = { <a bunch of methods here> }
The methods inside the instance create a DOM element (in this case a floating panel) and establish event bindings for it, wire up its control elements, etc.
The advantage of going down this path is that through a single call to create a new instance of a class I can also build the associated DOM element, and once instantiated, the class methods that have been wired up will execute against the element to do things like position it relative to a target object, respond to relevant browser events, etc.
The problem I have is when I want to dispose of this construct. I can dispose the DOM element easily. But I then still have the class instance in memory with methods wired to browser events looking for the DOM element that has been disposed. I need to be able to dispose not only of the DOM element, but also of the class instance, and I cannot figure out how to do that.
How can one dispose of a function prototype once declared? This seems like it ought to be simple, but I'm finding it to be decidedly not so.
For background info, here is an example of a class as I am defining it:
This is necessarily pseudo-code(ish)...
AWP.trivialExample = function(someDomRef, someOptionSet) {
this._id = someOptionSet['name'];
this._width = someOptionSet['width'];
this._width = someOptionSet['height'];
this._domRef = someDomRef;
this._object = '';
this.constructDOM();
this.wireEvents();
}
AWP.trivialExample.prototype = {
constructDOM: function() {
// build a complex DOM element relative to a provided DOM ref using the
// desired and height. This uses a template and I won't give a precise example
// of such a template.
jQuery("#aTemplate").tmpl(someJSONData).appendTo("body");
},
positionRelative: function() {
// this function would get the location of a specific DOM ref and always maintain
// a relative position for the DOM element we just constructed
},
wireEvents: function() {
// hook up to events using JQuery (example)
jjQuery(window).resize(this.positionRelative);
}
}
The above is a trivial example that would take in a DOM object reference, and then it would dynamically construct a new DOM element and it would wire up to browser events to always maintain relative position between these two objects when the page is sized.
When I dispose of the new object, I also need to dispose of the class instance and I cannot find a simple way to do that.
All help appreciated.
Thanks;
A suggestion on the event listeners referencing a deleted DOM node:
just as you have a 'wireEvents', you should have a corresponding 'unwireEvents' in case you decide to stop using the object. addEventListener() needs to be used in conjuction with removeEventListener() in this case. You should modify your prototype to remove Event listeners when the corresponding DOM Node is 'disposed', as you say.
AWP.iuiPanel.prototype = null; // ?

Categories