Event constructor in worker on IE11? - javascript

new Event does not work in IE11, and unfortunately neither does the polyfill from MDN and this question: Internet Explorer 9, 10 & 11 Event constructor doesn't work, since they rely on document.createEvent.
Are there any workarounds for creating custom events in the worker context?

Use a method which aliases an implementation of an existing event system. For example:
sinon.Event = function Event(type, bubbles, cancelable, target) {
this.initEvent(type, bubbles, cancelable, target);
};
sinon.Event.prototype = {
initEvent: function (type, bubbles, cancelable, target) {
this.type = type;
this.bubbles = bubbles;
this.cancelable = cancelable;
this.target = target;
},
stopPropagation: function () {},
preventDefault: function () {
this.defaultPrevented = true;
}
};
sinon.CustomEvent = function CustomEvent(type, customData, target) {
this.initEvent(type, false, false, target);
this.detail = customData.detail || null;
};
sinon.CustomEvent.prototype = new sinon.Event();
sinon.CustomEvent.prototype.constructor = sinon.CustomEvent;
sinon.EventTarget = {
addEventListener: function addEventListener(event, listener) {
this.eventListeners = this.eventListeners || {};
this.eventListeners[event] = this.eventListeners[event] || [];
push.call(this.eventListeners[event], listener);
},
removeEventListener: function removeEventListener(event, listener) {
var listeners = this.eventListeners && this.eventListeners[event] || [];
for (var i = 0, l = listeners.length; i < l; ++i) {
if (listeners[i] === listener) {
return listeners.splice(i, 1);
}
}
},
dispatchEvent: function dispatchEvent(event) {
var type = event.type;
var listeners = this.eventListeners && this.eventListeners[type] || [];
for (var i = 0; i < listeners.length; i++) {
if (typeof listeners[i] === "function") {
listeners[i].call(this, event);
} else {
listeners[i].handleEvent(event);
}
}
return !!event.defaultPrevented;
}
};
}
/**
* Used to bind event listeners to the worker. Internally it uses the jQuery `.on` method.
* #method on
* #param {Mixed} args* Lookup the jQuery `.on` API for argument list.
* #chainable
*/
var worker = new WebWorker('./worker-script.js');
worker.on('my-custom-event', function () {
console.log('custom event triggered!');
});
References
jQuery github repo: sinon.js
web-worker/web-worker.js at master · tanzeelkazi/web-worker
Improving Web Worker usage in PWA's (and an implementation in PureScript)
peter.michaux.ca - Anonymous Events - Extremely Loose Coupling
John Resig - Flexible Javascript Events
... and the winner is ... - QuirksBlog

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);
}
}
};

Backbone.js source code - eventsApi

The following is a snippet from the Backbone.js annotated source code:
var eventsApi = function(iteratee, events, name, callback, opts) {
var i = 0, names;
if (name && typeof name === 'object') {
if (callback !== void 0 && 'context' in opts && opts.context === void 0) opts.context = callback;
for (names = _.key(names); i < names.length; i++) {
events = eventsApi(iteratee, events, names[i], name[names[i]], opts);
}
} else if (name && eventSplitter.test(name)) {
for (names = name.split(eventSplitter); i < names.length; i++) {
events = iteratee(events, names[i], callback, opts);
}
} else {
events = iteratee(events, name, callback, opts);
}
return events;
};
The eventsApi function checks for events as objects, in order to handle jQuery-style event maps.
But why does it call eventsApi recursively as it iterates through all the events?
The iteratee in the above is the following:
var onApi = function(events, name, callback, options) {
if (callback) {
var handlers = events[name] || (events[name] = []);
var context = options.context, ctx = options.ctx, listening = options.listening;
if (listening) listening.count++;
handlers.push({callback: callback, context: context, ctx: context || ctx, listening: listening});
}
return events;
};
it registers an event for single attribute (name).
eventsApi checks if the second parameter is an object, in that case it calls itself to register events for all the properties of the object, for example the attributes object of a model. This is a common use of recursion.

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());

Detect if an element has been resized via javascript?

Is it possible to simply add event listeners to certain elements to detect if their height or width have been modified? I'd like do this without using something intensive like:
$(window).resize(function() { ... });
Ideally, I'd like to bind to specific elements:
$("#primaryContent p").resize(function() { ... });
It seems like using a resize handler on the window is the only solution, but this feels like overkill. It also doesn't account for situations where an element's dimensions are modified programatically.
I just came up with a purely event-based way to detect element resize for any element that can contain children, I've pasted the code from the solution below.
See also the original blog post, which has some historical details. Previous versions of this answer were based on a previous version of the blog post.
The following is the JavaScript you’ll need to enable resize event listening.
(function(){
var attachEvent = document.attachEvent;
var isIE = navigator.userAgent.match(/Trident/);
var requestFrame = (function(){
var raf = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame ||
function(fn){ return window.setTimeout(fn, 20); };
return function(fn){ return raf(fn); };
})();
var cancelFrame = (function(){
var cancel = window.cancelAnimationFrame || window.mozCancelAnimationFrame || window.webkitCancelAnimationFrame ||
window.clearTimeout;
return function(id){ return cancel(id); };
})();
function resizeListener(e){
var win = e.target || e.srcElement;
if (win.__resizeRAF__) cancelFrame(win.__resizeRAF__);
win.__resizeRAF__ = requestFrame(function(){
var trigger = win.__resizeTrigger__;
trigger.__resizeListeners__.forEach(function(fn){
fn.call(trigger, e);
});
});
}
function objectLoad(e){
this.contentDocument.defaultView.__resizeTrigger__ = this.__resizeElement__;
this.contentDocument.defaultView.addEventListener('resize', resizeListener);
}
window.addResizeListener = function(element, fn){
if (!element.__resizeListeners__) {
element.__resizeListeners__ = [];
if (attachEvent) {
element.__resizeTrigger__ = element;
element.attachEvent('onresize', resizeListener);
}
else {
if (getComputedStyle(element).position == 'static') element.style.position = 'relative';
var obj = element.__resizeTrigger__ = document.createElement('object');
obj.setAttribute('style', 'display: block; position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden; pointer-events: none; z-index: -1;');
obj.__resizeElement__ = element;
obj.onload = objectLoad;
obj.type = 'text/html';
if (isIE) element.appendChild(obj);
obj.data = 'about:blank';
if (!isIE) element.appendChild(obj);
}
}
element.__resizeListeners__.push(fn);
};
window.removeResizeListener = function(element, fn){
element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1);
if (!element.__resizeListeners__.length) {
if (attachEvent) element.detachEvent('onresize', resizeListener);
else {
element.__resizeTrigger__.contentDocument.defaultView.removeEventListener('resize', resizeListener);
element.__resizeTrigger__ = !element.removeChild(element.__resizeTrigger__);
}
}
}
})();
Usage
Here’s a pseudo code usage of this solution:
var myElement = document.getElementById('my_element'),
myResizeFn = function(){
/* do something on resize */
};
addResizeListener(myElement, myResizeFn);
removeResizeListener(myElement, myResizeFn);
Demo
http://www.backalleycoder.com/resize-demo.html
Here is a jQuery plugin with watch and unwatch methods that can watch particular properties of an element. It is invoked as a method of a jQuery object. It uses built-in functionality in browsers that return events when the DOM changes, and uses setTimeout() for browsers that do not support these events.
The general syntax of the watch function is below:
$("selector here").watch(props, func, interval, id);
props is a comma-separated string of the properties you wish to
watch (such as "width,height").
func is a callback function, passed the parameters watchData, index, where watchData refers to an object of the form { id: itId, props: [], func: func, vals: [] }, and index is the index of the changed property. this refers to the changed element.
interval is the interval, in milliseconds, for setInterval() in browsers that do not support property watching in the DOM.
id is an optional id that identifies this watcher, and is used to remove a particular watcher from a jQuery object.
The general syntax of the unwatch function is below:
$("selector here").unwatch(id);
id is an optional id that identifies this watcher to be removed. If id is not specified, all watchers from the object will be removed.
For those who are curious, the code of the plugin is reproduced below:
$.fn.watch = function(props, func, interval, id) {
/// <summary>
/// Allows you to monitor changes in a specific
/// CSS property of an element by polling the value.
/// when the value changes a function is called.
/// The function called is called in the context
/// of the selected element (ie. this)
/// </summary>
/// <param name="prop" type="String">CSS Property to watch. If not specified (null) code is called on interval</param>
/// <param name="func" type="Function">
/// Function called when the value has changed.
/// </param>
/// <param name="func" type="Function">
/// optional id that identifies this watch instance. Use if
/// if you have multiple properties you're watching.
/// </param>
/// <param name="id" type="String">A unique ID that identifies this watch instance on this element</param>
/// <returns type="jQuery" />
if (!interval)
interval = 200;
if (!id)
id = "_watcher";
return this.each(function() {
var _t = this;
var el = $(this);
var fnc = function() { __watcher.call(_t, id) };
var itId = null;
if (typeof (this.onpropertychange) == "object")
el.bind("propertychange." + id, fnc);
else if ($.browser.mozilla)
el.bind("DOMAttrModified." + id, fnc);
else
itId = setInterval(fnc, interval);
var data = { id: itId,
props: props.split(","),
func: func,
vals: []
};
$.each(data.props, function(i) { data.vals[i] = el.css(data.props[i]); });
el.data(id, data);
});
function __watcher(id) {
var el = $(this);
var w = el.data(id);
var changed = false;
var i = 0;
for (i; i < w.props.length; i++) {
var newVal = el.css(w.props[i]);
if (w.vals[i] != newVal) {
w.vals[i] = newVal;
changed = true;
break;
}
}
if (changed && w.func) {
var _t = this;
w.func.call(_t, w, i)
}
}
}
$.fn.unwatch = function(id) {
this.each(function() {
var w = $(this).data(id);
var el = $(this);
el.removeData();
if (typeof (this.onpropertychange) == "object")
el.unbind("propertychange." + id, fnc);
else if ($.browser.mozilla)
el.unbind("DOMAttrModified." + id, fnc);
else
clearInterval(w.id);
});
return this;
}
Yes it is possible. You will have to track all of the elements on load and store it. You can try out the demo here. In it, you don't have to use any libraries, but I used jQuery just to be faster.
First thing first - Store their initial size
You can do that by using this method:
var state = []; //Create an public (not necessary) array to store sizes.
$(window).load(function() {
$("*").each(function() {
var arr = [];
arr[0] = this
arr[1] = this.offsetWidth;
arr[2] = this.offsetHeight;
state[state.length] = arr; //Store all elements' initial size
});
});
Again, I used jQuery just to be fast.
Second - Check!
Of course you will need to check if it has been changed:
function checksize(ele) {
for (var i = 0; i < state.length; i++) { //Search through your "database"
if (state[i][0] == ele) {
if (state[i][1] == ele.offsetWidth && state[i][2] == ele.offsetHeight) {
return false
} else {
return true
}
}
}
}
Simply it will return false if it has not been change, true if it has been change.
Hope this helps you out!
Demo: http://jsfiddle.net/DerekL/6Evk6/

Javascript dispatchEvent

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).

Categories