A simple event system | How to? - javascript

I want to implement this interface
addListener(name, callback);
removeListener(name, [callback]); // callback is optional
trigger(name);
All events will be triggered on an internal event bus
var bus = {}
that is not part of the interface.
This is the simplest interface I could imagine that is actually useful.
However I still don't how to implement these interfaces conceptually. I've perused the backbone event system, but can't quite understand how they implement this core functionality.
I just want to write a quick 10-100 line event system that is as simple as possible and based upon the more complex Backbone event system.

You could use Backbone.Events module for it.
Stolen shamelessly from lostechies
You could use an Application level event aggregator:
MyApp = {};
MyApp.vent = _.extend({}, Backbone.Events);
MyApp.vent.on("some:event", function(){
alert("some event was fired!");
});
MyApp.vent.trigger("some:event");

Check out the minimal event bus library minibus.js. It currently has different API but might be exactly what you are looking for.
var bus = Minibus.create();
bus.on(name, callback); // add listener
bus.emit(name); // trigger event
bus.off(name); // remove listener

Related

How to avoid memory leaks from jQuery?

jQuery holds references to DOM nodes in its internal cache until I explicitly call $.remove(). If I use a framework such as React which removes DOM nodes on its own (using native DOM element APIs), how do I clean up jQuery's mem cache?
I'm designing a fairly large app using React. For those unfamiliar, React will tear down the DOM and rebuild as needed based on its own "shadow" DOM representation. The part works great with no memory leaks.
Flash forward, we decided to use a jQuery plugin. After React runs through its render loop and builds the DOM, we initialize the plugin which causes jQuery to hold a reference to the corresponding DOM nodes. Later, the user changes tabs on the page and React removes those DOM elements. Unfortunately, because React doesn't use jQuery's $.remove() method, jQuery maintains the reference to those DOM elements and the garbage collector never clears them.
Is there a way I can tell jQuery to flush its cache, or better yet, to not cache at all? I would love to still be able to leverage jQuery for its plugins and cross-browser goodness.
jQuery keeps track of the events and other kind of data via the internal API jQuery._data() however due to this method is internal, it has no official support.
The internal method have the following signature:
jQuery._data( DOMElement, data)
Thus, for example we are going to retrieve all event handlers attached to an Element (via jQuery):
var allEvents = jQuery._data( document, 'events');
This returns and Object containing the event type as key, and an array of event handlers as the value.
Now if you want to get all event handlers of a specific type, we can write as follow:
var clickHandlers = (jQuery._data(document, 'events') || {}).click;
This returns an Array of the "click" event handlers or undefined if the specified event is not bound to the Element.
And why I speak about this method? Because it allow us tracking down the event delegation and the event listeners attached directly, so that we can find out if an event handler is bound several times to the same Element, resulting in memory leaks.
But if you also want a similar functionality without jQuery, you can achieve it with the method getEventHandlers
Take a look at this useful articles:
getEventHandlers
getEventListeners - chrome
getEventListeners - firebug
Debugging
We are going to write a simple function that prints the event handlers and its namespace (if it was specified)
function writeEventHandlers (dom, event) {
jQuery._data(dom, 'events')[event].forEach(function (item) {
console.info(new Array(40).join("-"));
console.log("%cnamespace: " + item.namespace, "color:orangered");
console.log(item.handler.toString());
});
}
Using this function is quite easy:
writeEventHandlers(window, "resize");
I wrote some utilities that allow us keep tracking of the events bound to DOM Elements
Gist: Get all event handlers of an Element
And if you care about performance, you will find useful the following links:
Leaking Memory in Single Page Apps
Writing Fast, Memory-Efficient JavaScript
JavaScript Memory Profiling
I encourage anybody who reads this post, to pay attention to memory allocation in our code, I learn the performance problems ocurrs because of three important things:
Memory
Memory
And yes, Memory.
Events: good practices
It is a good idea create named functions in order to bind and unbind event handlers from DOM elements.
If you are creating DOM elements dynamically, and for example, adding handlers to some events, you could consider using event delegation instead of keep bounding event listeners directly to each element, that way, a parent of dynamically added elements will handle the event. Also if you are using jQuery, you can namespace the events ;)
//the worse!
$(".my-elements").click(function(){});
//not good, anonymous function can not be unbinded
$(".my-element").on("click", function(){});
//better, named function can be unbinded
$(".my-element").on("click", onClickHandler);
$(".my-element").off("click", onClickHandler);
//delegate! it is bound just one time to a parent element
$("#wrapper").on("click.nsFeature", ".my-elements", onClickMyElement);
//ensure the event handler is not bound several times
$("#wrapper")
.off(".nsFeature1 .nsFeature2") //unbind event handlers by namespace
.on("click.nsFeature1", ".show-popup", onShowPopup)
.on("click.nsFeature2", ".show-tooltip", onShowTooltip);
Circular references
Although circular references are not a problem anymore for those browsers that implement the Mark-and-sweep algorithm in their Garbage Collector, it is not a wise practice using that kind of objects if we are interchanging data, because is not possible (for now) serialize to JSON, but in future releases, it will be possible due to a new algorithm that handles that kind of objects. Let's see an example:
var o1 = {};
o2 = {};
o1.a = o2; // o1 references o2
o2.a = o1; // o2 references o1
//now we try to serialize to JSON
var json = JSON.stringify(o1);
//we get:"Uncaught TypeError: Converting circular structure to JSON"
Now let's try with this other example
var freeman = {
name: "Gordon Freeman",
friends: ["Barney Calhoun"]
};
var david = {
name: "David Rivera",
friends: ["John Carmack"]
};
//we create a circular reference
freeman.friends.push(david); //freeman references david
david.friends.push(freeman); //david references freeman
//now we try to serialize to JSON
var json = JSON.stringify(freeman);
//we get:"Uncaught TypeError: Converting circular structure to JSON"
PD: This article is about Cloning Objects in JavaScript. Also this gist contain demos about cloning objects with circular references: clone.js
Reusing objects
Let's follow some of the programming principles, DRY (Don't Repeat Yourself) and instead of creating new objects with similar functionality, we can abstract them in a fancy way. In this example I will going to reuse an event handler (again with events)
//the usual way
function onShowContainer(e) {
$("#container").show();
}
function onHideContainer(e) {
$("#container").hide();
}
$("#btn1").on("click.btn1", onShowContainer);
$("#btn2").on("click.btn2", onHideContainer);
//the good way, passing data to events
function onToggleContainer(e) {
$("#container").toggle(e.data.show);
}
$("#btn1").on("click.btn1", { show: true }, onToggleContainer);
$("#btn2").on("click.btn2", { show: false }, onToggleContainer);
And there are a lot of ways to improve our code, having an impact on performance, and preventing memory leaks. In this post I spoke mainly about events, but there are other ways that can produce memory leaks. I suggest read the articles posted before.
Happy reading and happy coding!
If your plugin exposes a method to programatically destroy one of its instances (i.e. $(element).plugin('destroy')), you should be calling that in the componentWillUnmount lifecycle of your component.
componentWillUnmount is called right before your component is unmounted from the DOM, it's the right place to clean up all external references / event listeners / dom elements your component might have created during its lifetime.
var MyComponent = React.createClass({
componentDidMount() {
$(React.findDOMNode(this.refs.jqueryPluginContainer)).plugin();
},
componentWillUnmount() {
$(React.findDOMNode(this.refs.jqueryPluginContainer)).plugin('destroy');
},
render() {
return <div ref="jqueryPluginContainer" />;
},
});
If your plugin doesn't expose a way to clean up after itself, this article lists a few ways in which you can try to dereference a poorly thought out plugin.
However, if you are creating DOM elements with jQuery from within your React component, then you are doing something seriously wrong: you should almost never need jQuery when working with React, since it already abstracts away all the pain points of working with the DOM.
I'd also be wary of using refs. There are only few use cases where refs are really needed, and those usually involve integration with third-party libraries that manipulate/read from the DOM.
If your component conditionally renders the element affected by your jQuery plugin, you can use callback refs to listen to its mount/unmount events.
The previous code would become:
var MyComponent = React.createClass({
handlePluginContainerLifecycle(component) {
if (component) {
// plugin container mounted
this.pluginContainerNode = React.findDOMNode(component);
$(this.pluginContainerNode).plugin();
} else {
// plugin container unmounted
$(this.pluginContainerNode).plugin('destroy');
}
},
render() {
return (
<div>
{Math.random() > 0.5 &&
// conditionally render the element
<div ref={this.handlePluginContainerLifecycle} />
}
</div>
);
},
});
How about do this when the user exits the tab:
for (x in window) {
delete x;
}
This is much better to do, though:
for (i in $) {
delete i;
}

Forcing modules to have a specific function. eg implement an interface?

Simple in java, but how do I ensure the modules that are registering with my event bus for topic notifications have the required callback method on them?
All my modules are following the revealing module pattern and as such are defined like the following
namespace = (function() {
//Private stuff
return {
method1 : method1
}
})();
I just need to ensure the module has a notify method on it which takes a single argument. The module can be responsible for unmarshalling the payload data into the format it is expecting
Thanks
I really want to help but I'm not sure I understand your terminology. I'll answer based on my limited understanding and adjust as necessary
Sounds to me like you want to make sure the object returned has a property called notify on it, which is a function that accepts one function parameter only. If this is correct, there are two places you can ensure all modules meet your requirement.
Inside the Event Bus
If you wrote the event bus, you can write a few lines of code inside it to make sure that if a module doesn't come with the 'notify' property on it, the event bus will not subscribe the module to the event. Something like this:
if( typeof module.notify !== 'function' ){
// do error handling then ...
return false;
}
External Function for Registering Modules to Event Bus
If you didn't write the event bus, you can create an external function that is responsible for ensuring the standard. Think of it as a wrapper around #1.
function registerToEventBus( module, event ){
if( typeof module.notify !== 'function' ){
// do error handling then ...
return false;
}
// register event to event bus then ...
return true;
}
Personally I'd recommend using #2
That way you wouldn't need to know a thing about the event bus itself to implement this.
You get the added benefit of being able to keep the event bus strictly for events and not complicate it by adding the module format enforcement into it.
You will be able to take the event bus as is to another project, drop it in and edit your enforcer function to meet this project's specificiations with absolutely no risk of breaking the event bus accidentally.
I hope this helps. If there's any place I misunderstood you, let me know and I'll give it my best shot to fix.

Possible to extend JQueryEventObject in TypeScript

I've only just started with Typescript but I'm wondering if it is possible to extend the JQueryEventObject with a new method - I'd like to add a cancel method that sets the event to stop bubbling and propagating.
If my understanding is correct, there is no way to do this using TypeScript because the JQueryEventObject is merely an interface created to provide an intellisense of what is available on the event object provided to an event. However, I may be wrong and if someone has got a solution, then I'd appreciate someone sharing that with me.
You are correct that the typescript definition is an interface, however, you can extend it, and you can also extend the runtime to add the new function. There are two things that need to be done:
make typescript aware of the new function
make the new function available at runtime inside jQuery
Here is a code sample that does both:
// PatchJquery.ts
// Include the generated .js file for this, after jQuery, in some html file.
/// <reference path="typedef/jquery/jquery.d.ts" />
// Runtime patch: Assume jQuery is already defined in the global scope
// and patch its Event prototype.
jQuery.Event.prototype.cancel = function() {
console.log("Someone wants me to cancel this event!",this);
}
// Define the extended typescript interface
interface ExtJQueryEventObject extends JQueryEventObject {
cancel():void;
}
// Test
$(function() {
var body:JQuery = $("body");
body.on('click', (e:ExtJQueryEventObject) => {
e.cancel();
});
});
Note that if you wanted, say, an actual click event, you would need to define an extended interface for JQueryMouseEventObject. You would need to do this for all of the different JQuery event types. Alternatively, you could just modify jquery.d.ts to add your new function to BaseJQueryEventObject.
(BTW, jQuery has stopPropagation() and stopImmediatePropagation(), which might do what you want already)

Defining and triggering custom events on custom module in YUI3

This is how we can create a custom module in YUI3,
<script type="text/javascript">
YUI.add('my-module', function (Y) {
// Write your module code here, and make your module available on the Y
// object if desired.
Y.MyModule = {
sayHello: function () {
console.log('Hello!');
}
};
});
</script>
But now I would like to, on this module, define some custom events and later trigger them, I just couldn't find any information about this on the YUI3 official website.
How can we actually do this?
Custom events are actually pretty important throughout YUI. This documentation page describes them in detail: http://yuilibrary.com/yui/docs/event-custom/. Read this page and some of the examples in the sidebar.
The easiest and simplest way to fire a custom event is to fire it from the Y, as in Y.fire("myEvent"). However, if you want to fire an event from your object, you would need to give your object the EventTarget API and call this.fire("myEvent"). Most people do this by extending Y.Base, which includes Y.EventTarget. See http://yuilibrary.com/yui/docs/base/ -- if you extend Base, you get a fire() method, the ability to listen for events with on() or after(), plus lots of other goodies.

Update: How to find event listeners on a DOM node in prototype?

I'm looking for an updated answer to this question.
It seems that Event.observers is no longer used (perhaps to avoid memory leaks) in Prototype 1.6+, so how do I track down now what event listeners are attached to an element?
I know Firebug has a "break on next" button, but there are several mouse listeners on the body element that execute before I can get to the behavior that I want on another particular element, so is there some other way?
I've update the answer you linked to with more comprehensive Prototype coverage accounting for changes in versions 1.6.0 to 1.6.1.
It got very messy in between there, but 1.6.1 is somewhat clean:
var handler = function() { alert('clicked!') };
$(element).observe('click', handler);
// inspect
var clickEvents = element.getStorage().get('prototype_event_registry').get('click');
clickEvents.each(function(wrapper){
alert(wrapper.handler) // alerts "function() { alert('clicked!') }"
})
Things are now routed through Element storage : )
Element.getStorage(yourElement).get('prototype_event_registry') will give you an instance of Prototype's Hash, so you can do anything that you would do with hash.
// to see which event types are being observed
Element.getStorage(yourElement).get('prototype_event_registry').keys();
// to get array of handlers for particular event type
Element.getStorage(yourElement).get('prototype_event_registry').get('click');
// to get array of all handlers
Element.getStorage(yourElement).get('prototype_event_registry').values();
// etc.
Note that these are undocumented internal details which might be changed in the future, so I wouldn't rely on them except for, perhaps, debugging purposes.

Categories