I'm working with Node.JS and have two objects that I move between via callbacks. I came up with a solution to maintain scope references to the correct object. I'm trying to figure out if there's a better way to do this, or if this is good practice.
function Worker () {}
Worker.prototype.receiveJob = function(callback, bossReference) {
this.doJob(callback, bossReference);
};
Worker.prototype.doJob = function(callback, bossReference) {
callback.call(bossReference);
// callback(); // this will not work
};
function Boss () {
this.worker = new Worker();
}
Boss.prototype.delegateJob = function() {
this.worker.receiveJob(this.whenJobCompleted, this);
};
Boss.prototype.whenJobCompleted = function() {
this.sayGoodJob();
};
Boss.prototype.sayGoodJob = function() {
console.log('Good job');
};
var boss = new Boss();
boss.delegateJob();
use Function.prototype.bind()
function Worker () {}
Worker.prototype.receiveJob = function(callback) {
this.doJob(callback);
};
Worker.prototype.doJob = function(callback) {
callback()
};
function Boss () {
this.worker = new Worker();
}
Boss.prototype.delegateJob = function() {
this.worker.receiveJob(this.whenJobCompleted.bind(this));
};
Boss.prototype.whenJobCompleted = function() {
this.sayGoodJob();
};
Boss.prototype.sayGoodJob = function() {
console.log('Good job');
};
var boss = new Boss();
boss.delegateJob();
you won't need those silly bossReferences aftewards unless you need it in your function
Related
Problem:
I have a piece of code that throws an error: this.isEmpty is not a function,
and I cannot figure out why. Following is the fragment (jsfiddle):
function addAlbumGrid(){
const MainGrid = new AlbumGrid ()
return MainGrid
}
function AlbumGrid() {
MainGrid.call(this)
}
var parentPrototype = Object.create(AlbumGrid.prototype)
parentPrototype.constructor = MainGrid
MainGrid.prototype = parentPrototype
AlbumGrid.prototype.addPhotoBox = function () {
MainGrid.prototype.add.call(this)
}
function MainGrid(){
}
MainGrid.prototype = {
isEmpty: function() {
{
return false
}
},
add:function() {
if(!this.isEmpty())
{
return false
}
}
}
var AlbumGrid=addAlbumGrid()
AlbumGrid.addPhotoBox()
Following is code that works (jsfiddle):
function animal() {
}
animal.prototype = {
canWalk: function () {
return true
},
move: function () {
if(this.canWalk()) {alert ('moving')}
}
}
function bird() {
animal.call(this)
}
var animalProto = Object.create(animal.prototype)
animalProto.constructor = bird
bird.prototype = animalProto
bird.prototype.fly = function () {
animal.prototype.move.call(this)
}
let fluffy = new bird ()
fluffy.fly()
Searching for help I landed at this page, can it be that I am somewhere loosing the context of this, and it is pointing to something I don't want?
In that case, would a solution using composition be an option (object.assign(..))?
Or can it be anything else?
I hope somebody can shed a light.
Thank you...
Ps edit: I have now updated the code, the first 3 comments reflected an older version of this post.
How can I implement sub functions in JavaScript
callMethod(); // Works
callMethod.doThisWay(); // Still works
Sure you can :-)
Just write
const callMethod = function () {
// ...
};
callMethod.doThisWay = function () {
// ...
};
and you're done :-)
This also works:
var callMethod = function() {
this.doThisWay = function () {
alert('doThisWay');
}
alert('callMethod');
return this;
};
var a = new callMethod();
a.doThisWay();
there is some other ways to do this also.
I need to launch custom events from CLASS. I know to do this with DOM objects and jquery, using triggerHandler, like $(object)..triggerHandler("inputChange", {param:X});
The problem is when i try this with a Class, like this:
var MyClass = (function(){
var static_var = 1;
var MyClass = function () {
var privateVar;
var privateFn = function(){ alert('Im private!'); };
this.someProperty = 5;
this.someFunction = function () {
alert('Im public!');
};
this.say = function() {
alert('Num ' + this.someProperty);
$(this).triggerHandler("eventCustom");
}
this.alter = function() {
this.someProperty ++;
}
};
return MyClass;
})();
TheClass = new MyClass();
$(TheClass).on('eventCustom', function() {
alert('Event!');
});
TheClass.say();
This doesn't launch warnings or errors, but the events listener is not working (or event is not dispatched). I think the jQuery event system doesn't work with not DOM object, correct?
Any other way (I need events, not callbacks for my specific case) to launch the events?
Thanks a lot!
I wrote an ES6 event class for nowadays in under 100 lines of code without using JQuery. If you don't want to use DOM-events you can extend your class, which should deal with Events.
For listening to events, you can use on, once, onReady, onceReady. On is execute the callbackfunction every time the label is trigger. Once only one time. The "ready"-functions execute the callback, if the label had been already triggerd before.
For triggering an event, use a trigger. To remove an eventhandler, use off.
I hope the example makes it clear:
class ClassEventsES6 {
constructor() {
this.listeners = new Map();
this.onceListeners = new Map();
this.triggerdLabels = new Map();
}
// help-function for onReady and onceReady
// the callbackfunction will execute,
// if the label has already been triggerd with the last called parameters
_fCheckPast(label, callback) {
if (this.triggerdLabels.has(label)) {
callback(this.triggerdLabels.get(label));
return true;
} else {
return false;
}
}
// execute the callback everytime the label is trigger
on(label, callback, checkPast = false) {
this.listeners.has(label) || this.listeners.set(label, []);
this.listeners.get(label).push(callback);
if (checkPast)
this._fCheckPast(label, callback);
}
// execute the callback everytime the label is trigger
// check if the label had been already called
// and if so excute the callback immediately
onReady(label, callback) {
this.on(label, callback, true);
}
// execute the callback onetime the label is trigger
once(label, callback, checkPast = false) {
this.onceListeners.has(label) || this.onceListeners.set(label, []);
if (!(checkPast && this._fCheckPast(label, callback))) {
// label wurde nocht nicht aufgerufen und
// der callback in _fCheckPast nicht ausgeführt
this.onceListeners.get(label).push(callback);
}
}
// execute the callback onetime the label is trigger
// or execute the callback if the label had been called already
onceReady(label, callback) {
this.once(label, callback, true);
}
// remove the callback for a label
off(label, callback = true) {
if (callback === true) {
// remove listeners for all callbackfunctions
this.listeners.delete(label);
this.onceListeners.delete(label);
} else {
// remove listeners only with match callbackfunctions
let _off = (inListener) => {
let listeners = inListener.get(label);
if (listeners) {
inListener.set(label, listeners.filter((value) => !(value === callback)));
}
};
_off(this.listeners);
_off(this.onceListeners);
}
}
// trigger the event with the label
trigger(label, ...args) {
let res = false;
this.triggerdLabels.set(label, ...args); // save all triggerd labels for onready and onceready
let _trigger = (inListener, label, ...args) => {
let listeners = inListener.get(label);
if (listeners && listeners.length) {
listeners.forEach((listener) => {
listener(...args);
});
res = true;
}
};
_trigger(this.onceListeners, label, ...args);
_trigger(this.listeners, label, ...args);
this.onceListeners.delete(label); // callback for once executed, so delete it.
return res;
}
}
// +++ here starts the example +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
class TestClassEvents extends ClassEventsES6 {
constructor() {
super();
this.once('sayHallo', this.fStartToTalk);
this.on('sayHallo', this.fSayHallo);
}
fStartToTalk() {
console.log('I start to talk... ');
}
fSayHallo(name = 'Nobody') {
console.log('Hallo ' + name);
}
}
let testClassEvents = new TestClassEvents();
testClassEvents.trigger('sayHallo', 'Tony');
testClassEvents.trigger('sayHallo', 'Tim');
testClassEvents.onReady('sayHallo', e => console.log('I already said hello to ' + e));
testClassEvents.trigger('sayHallo', 'Angie');
testClassEvents.off('sayHallo');
testClassEvents.trigger('sayHallo', 'Peter');
console.log('I dont say hallo to Peter, because the event is off!')
Your understanding of how javascript works is limited since you are approaching it from a traditional OOP point of view. Take a look at this fiddle http://jsfiddle.net/9pCmh/ & you will see that you can actually pass functions as variables to other functions. There are no classes in javascript, only functions which can be closures which can be made to emulate traditional classes:
var MyClass = (function(){
var static_var = 1;
var MyClass = function ( callback ) {
var privateVar;
var privateFn = function(){ alert('Im private!'); };
this.someProperty = 5;
this.someFunction = function () {
alert('Im public!');
};
this.say = function() {
alert('Num ' + this.someProperty);
callback();
}
this.alter = function() {
this.someProperty ++;
}
};
return MyClass;
})();
TheClass = new MyClass(function() {
alert('Event!');
});
TheClass.say();
Alternatively you could create a function in your "class" to configure the callback/trigger instead of passing it into the constructor.
Have a look at this as a start for your further reading on this concept... How do JavaScript closures work?
Edit
To appease those critics looking for an eventQueue here is an updated jsfiddle :)
http://jsfiddle.net/Qxtnd/9/
var events = new function() {
var _triggers = {};
this.on = function(event,callback) {
if(!_triggers[event])
_triggers[event] = [];
_triggers[event].push( callback );
}
this.triggerHandler = function(event,params) {
if( _triggers[event] ) {
for( i in _triggers[event] )
_triggers[event][i](params);
}
}
};
var MyClass = (function(){
var MyClass = function () {
this.say = function() {
alert('Num ' + this.someProperty);
events.triggerHandler('eventCustom');
}
};
return MyClass;
})();
TheClass = new MyClass();
events.on('eventCustom', function() {
alert('Event!');
});
events.on('eventCustom', function() {
alert('Another Event!');
});
TheClass.say();
Same as this question, but Prototype library specific:
I've got a Browser class, and I want to fire and observe custom events for this class. Prototype's custom event system only lets me bind to and fire events on DOM elements. Here's my first idea as to an alternative:
function Browser() {
this.event = new Element('span');
}
Browser.prototype.render = function() {
this.event.fire('browser:render');
}
var browser = new Browser();
browser.event.observe('browser:render', function() { ... });
Is there a better way to do this?
Thanks in part to Frits van Campen's advice, I made my own that serves my needs, a little more sophisticated than Frits' sample.
function EventManager(target) {
var target = target || window,
events = {};
this.observe = function(eventName, cb) {
if (events[eventName]) events[eventName].push(cb);
else events[eventName] = new Array(cb);
return target;
};
this.stopObserving = function(eventName, cb) {
if (events[eventName]) {
var i = events[eventName].indexOf(cb);
if (i > -1) events[eventName].splice(i, 1);
else return false;
return true;
}
else return false;
};
this.fire = function(eventName) {
if (!events[eventName]) return false;
for (var i = 0; i < events[eventName].length; i++) {
events[eventName][i].apply(target, Array.prototype.slice.call(arguments, 1));
}
};
}
Then I can do:
Function Browser() {
this.event = new EventManager(this);
}
Browser.prototype.render = function() {
this.event.fire("render");
}
browser = new Browser();
browser.event.observe("render", function() { alert("Rendered"); });
Why not just build your own event handling system? It really doesn't take much.
function MyClass() {
this.handlers = {};
}
MyClass.prototype.registerHandler = function(event, callback) {
this.handlers[event] = callback;
};
MyClass.prototype.fire = function(event) {
this.handlers[event]();
};
var instance = new MyClass();
instance.registerHandler('an event', function () {
alert('hi!');
});
instance.fire('an event');
I use the document to fire all my custom events. Works great.
document.on("customEvent:blah", this.doCustomEvent.bind(this));
document.fire("customEvent:blah");
I've confused myself nicely here. My scenario is as follows:
function DesignPad() {
function EditBar() {
...
this.removeHandler = function() {
**// how do I call Dragger.removeAsset**
}
}
function Dragger(){
...
this.removeAsset = function() {}
}
this.init = function() {
this.editBar = new EditBar();
this.dragger = new Dragger();
}
}
var dp = new DesignPad();
...
I can't seem to call Dragger.RemoveAsset. I understand the why, my question is how do I call it?
I'm trying to keep like-things separated (e.g. Dragger / EditBar) but I seem to get all sorts of mixed up in my event handlers. Any suggestions, good reading materials, etc. on this stuff?
I found Douglas Crockford's Javascript to be the best introduction to JavaScript. Especialy videos for Yahoo, like: The JavaScript Programming Language where you can learn how exactly are objects created and inherited in JS.
Solution to you problem is:
function DesignPad() {
var that = this;
function EditBar() {
this.removeHandler = function() {
print("RemoveHandler");
that.dragger.removeAsset();
}
}
function Dragger() {
this.removeAsset = function() {
print("RemoveAsset");
}
}
this.init = function() {
this.editBar = new EditBar();
this.dragger = new Dragger();
}
}
var dp = new DesignPad();
dp.init();
dp.editBar.removeHandler();
But as others noticed you could refactor some things :).
To me it just looks like you should refactor that code to make it simpler.
I think that your issue comes from the fact that a nested function is private, so you can't access it from outside.
Is an instance of Dragger a 'property' of your DesignPad object? If so, you could pass a reference to that object into your removeHandler() method.
Try this:
function DesignPad() {
function EditBar(s) {
super = s;
this.removeHandler = function() {
alert('call 1');
super.dragger.removeAsset();
}
}
function Dragger(s){
super = s;
this.removeAsset = function() {
alert('call 2');
}
}
this.init = function() {
this.editBar = new EditBar(this);
this.dragger = new Dragger(this);
}
}
var dp = new DesignPad();
dp.init()
dp.editBar.removeHandler();
alert('end');