Javascript dispatchEvent - javascript

I'm using Flash a lot and my classes uses EventDispatcher class which allows me to define custom events of a class. How can I do this in JavaScript.
I would like to do something like this:
var MyClass = function() {
};
MyClass.prototype = {
test : function() {
dispatchEvent('ON_TEST');
}
};
var mc = new MyClass();
mc.addEventListener('ON_TEST', handler);
function handler() { alert('working...') }
How is this possible with JavaScript?

Gotta roll your own. Here's just one way.
var MyClass = function() {
this._events = {};
};
MyClass.prototype = {
addListener: function(eventName, callback) {
var events = this._events,
callbacks = events[eventName] = events[eventName] || [];
callbacks.push(callback);
},
raiseEvent: function(eventName, args) {
var callbacks = this._events[eventName];
for (var i = 0, l = callbacks.length; i < l; i++) {
callbacks[i].apply(null, args);
}
},
test : function() {
this.raiseEvent('ON_TEST', [1,2,3]); // whatever args to pass to listeners
}
};
You should probably also add a 'removeListener', which would have to find the callback in the array and remove it from the array (or possibly, remove all listeners for an entire event if no callback given).

Related

javascript add Single Event Lister for multiple events like ajax function callbacks succss, fail etc

I want to crate javascript object that accepts setting and provide multiple events with single addListener function. for example addListener({success: callback,fail:callback}) etc. I hope my requirement is clear.
var myCustomObject=new MyCustomeObject({name:'anu', age:'30'});
myCustomObject.addListener({
success: function(e){ console.log(e)},
fail: function(ef){ console.log(ef)}
});
You can implement your own EventEmitter like this:
const EventEmitter = function () {
this.events = {};
};
Then you need to create function for subscribe:
EventEmitter.prototype.on = function (event, listener) {
if (typeof this.events[event] !== 'object') {
this.events[event] = [];
}
this.events[event].push(listener);
};
and of course function for emit:
EventEmitter.prototype.emit = function (event) {
let i, listeners, length, args = [].slice.call(arguments, 1);
if (typeof this.events[event] === 'object') {
listeners = this.events[event].slice();
length = listeners.length;
for (i = 0; i < length; i++) {
listeners[i].apply(this, args);
}
}
};

A prototype-less, new-less, one-instance-only javascript object?

This doesn't work.
var genericClickHandler = function () {
this.handlers = [];
if (console && console.log) {
console.log("this:", this);
console.log("event:", event);
}
};
genericClickHandler.addHandler = function (handlerSpec) {
this.handlers.push(handlerSpec);
return this;
};
genericClickHandler.executeHandler = function (handlerName) {
for (var i = 0; i < this.handlers.length; i++) {
if (handlerName === this.handlers[i][0]) {
this.handlers[i][1]();
}
}
return this;
};
It doesn't work because the addHandler can't see the this.handlers in genericClickHandler.
Anyway what I'm after is function that gets defined once, but has methods and properties. I want to be able to use the function with Google Maps like this:
heatmap.addListener("click", genericClickHandler)
circle.addListener("click", genericClickHandler)
polygons.addListener("click", genericClickHandler)
So in the first instance, it only reports the this and event object. However, I then want to write code which extends the genericClickHandler dynamically so that it can implement map-object-specific behaviour.
Here's an example of what I meant using an object rather than a function.
var genericClickHandler = {
handlers: []
};
genericClickHandler.addHandler = function (name, fn) {
this.handlers.push([name, fn]);
return this;
};
genericClickHandler.executeHandler = function (name) {
for (var i = 0, l = this.handlers.length; i < l; i++) {
if (this.handlers[i][0] === name) this.handlers[i][1]();
}
};
genericClickHandler.addHandler('click', function () {
console.log('hi');
});
genericClickHandler.addHandler('click', function () {
console.log('hallo again');
});
genericClickHandler.executeHandler('click'); // hi... hallo again
DEMO
if you want to create an object, here you can see 2 ways to do the same thing, javascript got multiple way to write the same things.
var genericClickHandler = function()
{
this.handlers = [];
this.addHandler = function (handlerSpec)
{
this.handlers.push(handlerSpec);
return this;
},
this.executeHandler = function (handlerName)
{
this.handlers[handlerName]();
return this;
}
};
//sample:
var tmp = new genericClickHandler();
console.log(tmp.handlers);
console.log(tmp.addHandler("TEST"));
Another way to write the same object, but more optimised : prototype will be stored once for each object
var genericClickHandler = function(){}
genericClickHandler.prototype =
{
handlers:[],
addHandler : function (handlerSpec)
{
this.handlers.push(handlerSpec);
return this;
},
executeHandler : function (handlerName)
{
this.handlers[handlerName]();
return this;
}
}
//sample:
var tmp = new genericClickHandler();
console.log(tmp.handlers);
console.log(tmp.addHandler("TEST"));

When I make a clone of the object shows me an error message

First the following is the code of my own javascript library.
(function() {
var lib = {
elems: [],
getElem: function() {
var tmpElem = [];
for (var i = 0; i < arguments.length; i++)
tmpElem.push(document.getElementById(arguments[i]));
this.elems = tmpElem;
tmpElem = null;
return this;
},
html: function(txt) {
for (var i = 0; i < this.elems.length; i++)
this.elems[i].innerHTML = txt;
return this;
},
style: function(prob, val) {
for (var i = 0; i < this.elems.length; i++)
this.elems[i].style[prob] = val;
return this;
},
addEvent: function(event, callback) {
if (this.elems[0].addEventListener) {
for (var i = 0; i < this.elems.length; i++)
this.elems[i].addEventListener(event, callback, false);
} else if (this.elems[0].attachEvent) {
for (var i = 0; i < this.elems.length; i++)
this.elems[i].attachEvent('on' + event, callback);
}
return this;
},
toggle: function() {
for (var i = 0; i < this.elems.length; i++)
this.elems[i].style.display = (this.elems[i].style.display === 'none' || '') ? 'block' : 'none';
return this;
},
domLoad: function(callback) {
var isLoaded = false;
var checkLoaded = setInterval(function() {
if (document.body && document.getElementById)
isLoaded = true;
}, 10);
var Loaded = setInterval(function() {
if (isLoaded) {
clearInterval(checkLoaded);
clearInterval(Loaded);
callback();
}
}, 10);
}
};
var fn = lib.getElem;
for(var i in lib)
fn[i] = lib[i];
window.lib = window.$ = fn;
})();
Previously, I have used this way to use my own library, and works fine .
$.getElem('box').html('Welcome to my computer.');
But when updated the code of my own library, and I added
var fn = lib.getElem;
for(var i in lib)
fn[i] = lib[i];
To be using the element selector like this way
$('box').html('Welcome to my computer.');
But the problem began appear when added the updated code to clone the lib object TypeError: $(...).html is not a function.
And now I want to use the element selector like that
$('box').html('Welcome to my computer.');
instead of
$.getElem('box').html('Welcome to my computer.');
You create a variable fn which has a reference to "getElem" but since fn is not a property on your lib object then it means that when getElem refers to "this" it will be you global object which is propbably window.
Remove all the following 3 lines
var fn = lib.getElem;
for(var i in lib)
fn[i] = lib[i];
and then do this
window.$ = function () { return lib.getElem.apply(lib, arguments); };
This will allow getElem to be called as $ but maintaining "lib" as context.
Although I don't know exactly what you are trying to achieve with those additional lines, just by reading the code, lib.getElem does not have a function called html
lib does.
Hence, just var fn = lib; should do just fine.
There more ways to achieve this but the root cause is in your getElem() function: return this;
$ is a reference to that function. If you call $() it is called as a function and not as a method. Therefore this refers to window and window has, of course, no html() function.
You could do return lib; to fix the problem.

Javascript has StackOverflow in IE but not Chrome/Firefox

I've got a class that is basically a native Javascript Array, but it raises events when items are added or removed.
hb.extend( {
Classes: {
Collection: hbClass.inherit({
init: function (arr) {
// get the functions we want to retain
var _on = this.on,
_trigger = this.trigger,
_push = this.push,
_remove = this.remove,
_reset = this.reset,
_from = this.fromArray,
_watch = this.watch;
// Set the object up as an Array
this.__proto__ = Array.prototype;
// get the Array functions we want to use
this.arrPush = this.push;
// reapply the old functions
this.push = _push;
this.remove = _remove;
this.reset = _reset;
this.fromArray = _from;
this.on = _on;
this.trigger = _trigger;
this.watch = _watch;
if (arr && (arr.length && typeof arr !== "string")) this.fromArray(arr, true);
},
fromArray: function (arr, stopEvent) {
this.reset();
for (var i = 0, len = arr.length; i < len; i++) {
this.arrPush(arr[i]);
}
if (!stopEvent) this.trigger('change', this);
},
push: function () {
this.arrPush.apply(this, arguments);
this.trigger('add', this);
this.trigger('change', this);
return this;
},
remove: function (from, to) {
var rest = this.slice((to || from) + 1 || this.length);
this.length = from < 0 ? this.length + from : from;
this.arrPush.apply(this, rest);
this.trigger('remove', this);
this.trigger('change', this);
return this;
},
reset: function () {
this.length = 0;
this.trigger('change', this);
this.trigger('remove', this);
}
})
}
});
There may be better ways to do it, but it works for me.......except in IE.
In IE at the line this.arrPush.appy(this, arguments); under the push method, it hits a Stack Overflow error.
Specifically:
SCRIPT28: Out of stack space
But this does NOT occur in Firefox or Chrome.
Anyone have any advice?
EDIT
Trigger code:
this.hbClass.prototype.trigger = function(type, data, context) {
var listeners, handlers, i, n, handler, scope;
if (!(listeners = this.listeners)) {
return;
}
if (!(handlers = listeners[type])){
return;
}
for (i = 0, n = handlers.length; i < n; i++){
handler = handlers[i];
if (handler.method.call(
handler.context, this, type, data
)===false) {
return false;
}
}
return true;
}
The issue is probably this line:
this.__proto__ = Array.prototype;
as __proto__ is not supported in some versions of IE. It has been codified in the ES6 specification, but that isn't implemented in some versions of IE. I don't understand exactly how your code works, but the safe way to set a prototype is like this:
Working demo: http://jsfiddle.net/jfriend00/ff99G/
function myClass() {
// add new methods to this instance in the constructor
this.fromArray = function() {};
};
// become an array and get all its methods
myClass.prototype = Array.prototype;
var x = new myClass();
Here's an example of the kind of thing you're doing using .prototype that works in IE:
function log(msg) {
var result = document.getElementById("result");
var div = document.createElement("div");
div.innerHTML = msg;
result.appendChild(div);
}
function myClass() {
var _push = this.push;
this.count = function() {
return this.length;
}
this.trigger = function(type, name) {
var str = type;
if (name) {
str += ", " + name;
}
log(str);
}
this.push = function() {
var retVal = _push.apply(this, arguments);
this.trigger("change", "push");
return retVal;
}
};
// become an array and get all its methods
myClass.prototype = Array.prototype;
var x = new myClass();
x.push("foo");
x.push("whatever");
log(x.count());

Super in Backbone

When I override the clone() method of a Backbone.Model, is there a way to call this overriden method from my implantation? Something like this:
var MyModel = Backbone.Model.extend({
clone: function(){
super.clone();//calling the original clone method
}
})
You'll want to use:
Backbone.Model.prototype.clone.call(this);
This will call the original clone() method from Backbone.Model with the context of this(The current model).
From Backbone docs:
Brief aside on super: JavaScript does not provide a simple way to call
super — the function of the same name defined higher on the prototype
chain. If you override a core function like set, or save, and you want
to invoke the parent object's implementation, you'll have to
explicitly call it.
var Note = Backbone.Model.extend({
set: function(attributes, options) {
Backbone.Model.prototype.set.apply(this, arguments);
...
}
});
You can also use the __super__ property which is a reference to the parent class prototype:
var MyModel = Backbone.Model.extend({
clone: function(){
MyModel.__super__.clone.call(this);
}
});
Josh Nielsen found an elegant solution for this, which hides a lot of the ugliness.
Just add this snippet to your app to extend Backbone's model:
Backbone.Model.prototype._super = function(funcName){
return this.constructor.prototype[funcName].apply(this, _.rest(arguments));
}
Then use it like this:
Model = Backbone.model.extend({
set: function(arg){
// your code here
// call the super class function
this._super('set', arg);
}
});
Working from the answers given by geek_dave and charlysisto, I wrote this to add this._super(funcName, ...) support in classes that have multiple levels of inheritance. It's worked well in my code.
Backbone.View.prototype._super = Backbone.Model.prototype._super = function(funcName) {
// Find the scope of the caller.
var scope = null;
var scan = this.__proto__;
search: while (scope == null && scan != null) {
var names = Object.getOwnPropertyNames(scan);
for (var i = 0; i < names.length; i++) {
if (scan[names[i]] === arguments.callee.caller) {
scope = scan;
break search;
}
}
scan = scan.constructor.__super__;
}
return scan.constructor.__super__[funcName].apply(this, _.rest(arguments));
};
A year later I've fixed some bugs and made things faster. Below is the code that I use now.
var superCache = {};
// Hack "super" functionality into backbone.
Backbone.View.prototype._superFn = Backbone.Model.prototype._superFn = function(funcName, _caller) {
var caller = _caller == null ? arguments.callee.caller : _caller;
// Find the scope of the caller.
var scope = null;
var scan = this.__proto__;
var className = scan.constructor.className;
if (className != null) {
var result = superCache[className + ":" + funcName];
if (result != null) {
for (var i = 0; i < result.length; i++) {
if (result[i].caller === caller) {
return result[i].fn;
}
}
}
}
search: while (scope == null && scan != null) {
var names = Object.getOwnPropertyNames(scan);
for (var i = 0; i < names.length; i++) {
if (scan[names[i]] === caller) {
scope = scan;
break search;
}
}
scan = scan.constructor.__super__;
}
var result = scan.constructor.__super__[funcName];
if (className != null) {
var entry = superCache[className + ":" + funcName];
if (entry == null) {
entry = [];
superCache[className + ":" + funcName] = entry;
}
entry.push({
caller: caller,
fn: result
});
}
return result;
};
Backbone.View.prototype._super = Backbone.Model.prototype._super = function(funcName) {
var args = new Array(arguments.length - 1);
for (var i = 0; i < args.length; i++) {
args[i] = arguments[i + 1];
}
return this._superFn(funcName, arguments.callee.caller).apply(this, args);
};
Then given this code:
var A = Backbone.Model.extend({
// className: "A",
go1: function() { console.log("A1"); },
go2: function() { console.log("A2"); },
});
var B = A.extend({
// className: "B",
go2: function() { this._super("go2"); console.log("B2"); },
});
var C = B.extend({
// className: "C",
go1: function() { this._super("go1"); console.log("C1"); },
go2: function() { this._super("go2"); console.log("C2"); }
});
var c = new C();
c.go1();
c.go2();
The output in the console is this:
A1
C1
A2
B2
C2
What's interesting is that class C's call to this._super("go1") scans the class hierarchy until it gets a hit in class A. Other solutions do not do this.
P.S. Uncomment the className entries of the class definitions to enable caching of the _super lookup. (The assumption is that these class names will be unique in the application.)
If you want just to call this._super(); without passing the function name as an argument
Backbone.Controller.prototype._super = function(){
var fn = Backbone.Controller.prototype._super.caller, funcName;
$.each(this, function (propName, prop) {
if (prop == fn) {
funcName = propName;
}
});
return this.constructor.__super__[funcName].apply(this, _.rest(arguments));
}
Better use this plugin:
https://github.com/lukasolson/Backbone-Super
I believe you can cache the original method (although not tested):
var MyModel = Backbone.Model.extend({
origclone: Backbone.Model.clone,
clone: function(){
origclone();//calling the original clone method
}
});
backbone._super.js, from my gists: https://gist.github.com/sarink/a3cf3f08c17691395edf
// Forked/modified from: https://gist.github.com/maxbrunsfeld/1542120
// This method gives you an easier way of calling super when you're using Backbone in plain javascript.
// It lets you avoid writing the constructor's name multiple times.
// You still have to specify the name of the method.
//
// So, instead of having to write:
//
// var Animal = Backbone.Model.extend({
// word: "",
// say: function() {
// return "I say " + this.word;
// }
// });
// var Cow = Animal.extend({
// word: "moo",
// say: function() {
// return Animal.prototype.say.apply(this, arguments) + "!!!"
// }
// });
//
//
// You get to write:
//
// var Animal = Backbone.Model.extend({
// word: "",
// say: function() {
// return "I say " + this.word;
// }
// });
// var Cow = Animal.extend({
// word: "moo",
// say: function() {
// return this._super("say", arguments) + "!!!"
// }
// });
(function(root, factory) {
if (typeof define === "function" && define.amd) {
define(["underscore", "backbone"], function(_, Backbone) {
return factory(_, Backbone);
});
}
else if (typeof exports !== "undefined") {
var _ = require("underscore");
var Backbone = require("backbone");
module.exports = factory(_, Backbone);
}
else {
factory(root._, root.Backbone);
}
}(this, function(_, Backbone) {
"use strict";
// Finds the next object up the prototype chain that has a different implementation of the method.
var findSuper = function(methodName, childObject) {
var object = childObject;
while (object[methodName] === childObject[methodName]) {
object = object.constructor.__super__;
}
return object;
};
var _super = function(methodName) {
// Keep track of how far up the prototype chain we have traversed, in order to handle nested calls to `_super`.
this.__superCallObjects__ || (this.__superCallObjects__ = {});
var currentObject = this.__superCallObjects__[methodName] || this;
var parentObject = findSuper(methodName, currentObject);
this.__superCallObjects__[methodName] = parentObject;
// If `methodName` is a function, call it with `this` as the context and `args` as the arguments, if it's an object, simply return it.
var args = _.tail(arguments);
var result = (_.isFunction(parentObject[methodName])) ? parentObject[methodName].apply(this, args) : parentObject[methodName];
delete this.__superCallObjects__[methodName];
return result;
};
// Mix in to Backbone classes
_.each(["Model", "Collection", "View", "Router"], function(klass) {
Backbone[klass].prototype._super = _super;
});
return Backbone;
}));
In the case that you don't know what the parent class is exactly (multiple inheritance or you want a helper function) then you can use the following:
var ChildModel = ParentModel.extend({
initialize: function() {
this.__proto__.constructor.__super__.initialize.apply(this, arguments);
// Do child model initialization.
}
});
With helper function:
function parent(instance) {
return instance.__proto__.constructor.__super__;
};
var ChildModel = ParentModel.extend({
initialize: function() {
parent(this).initialize.apply(this, arguments);
// Do child model initialization.
}
});
Pass the parent class as an option during instantiation:
BaseModel = Backbone.Model.extend({
initialize: function(attributes, options) {
var self = this;
this.myModel = new MyModel({parent: self});
}
});
Then in your MyModel you can call parent methods like this
this.options.parent.method();
Keep in mind this creates a retain cycle on the two objects. So to let the garbage collector do it's job you would need to manually destroy the retain on one of the objects when finished with it. If you're application is pretty large. I would encourage you to look more into hierarchal setups so events can travel up to the correct object.
2 functions below, one requires you pass in the function name, the other can "discover" which function we want the super version of
Discover.Model = Backbone.Model.extend({
_super:function(func) {
var proto = this.constructor.__super__;
if (_.isUndefined(proto[func])) {
throw "Invalid super method: " + func + " does not exist in prototype chain.";
}
return proto[func].apply(this, _.rest(arguments));
},
_superElegant:function() {
t = arguments;
var proto = this.constructor.__super__;
var name;
for (name in this) {
if (this[name] === arguments.callee.caller) {
console.log("FOUND IT " + name);
break;
} else {
console.log("NOT IT " + name);
}
}
if (_.isUndefined(proto[name])) {
throw "Super method for: " + name + " does not exist.";
} else {
console.log("Super method for: " + name + " does exist!");
}
return proto[name].apply(this, arguments);
},
});
Here is how I would do this:
ParentClassName.prototype.MethodToInvokeName.apply(this);
so for your example this is:
Model.prototype.clone.apply(this)

Categories