I have 3 classes:
function A(data){
this.data = data || [];
}
A.prototype.remove = function(index){
// remove index from this.data;
};
function B(data){
this.data = data || [];
}
B.prototype.add = function(x){
this.data.push(x);
};
B.prototype.update = function(index, x){
this.data[index] = x;
};
function C(data){
this.data = data || [];
}
C.prototype.add = function(x){
this.data.push(x);
};
var a = new A(xx);
var b = new B(xx);
var c = new C(xx);
There relations are:
if I call a.remove(i) to remove an element, b will add it to its data.
if I call b.update(i, x), c will add x to its data.
How could I design this with smallest coupling?
ps: this is just an example to demonstrate the situation, please don't care about the initialization etc.
I would use events for loose coupling at its best. As base I recommend using Backbone.js (see Backbone.Events section) it has very small footprint and provide you with great custom events support. Brief description from docs:
Events is a module that can be mixed in to any object, giving the object the ability to bind and trigger custom named events.
It could look like this:
function A(data){
this.data = data || [];
}
A.prototype.remove = function(index){
// remove index from this.data;
// dispatch A_Remove event
this.trigger("A_Remove", this.data);
};
function B(data){
this.data = data || [];
}
B.prototype.add = function(x){
this.data.push(x);
};
B.prototype.update = function(index, x){
this.data[index] = x;
this.trigger("B_Update", x);
};
function C(data){
this.data = data || [];
}
C.prototype.add = function(x){
this.data.push(x);
};
var a = new A(xx);
var b = new B(xx);
var c = new C(xx);
// mixin event support to your objects
_.extend(a, Backbone.Events);
_.extend(b, Backbone.Events);
_.extend(c, Backbone.Events);
// event listener for A_Remove event
a.bind("A_Remove", function(data) {
b.add(data);
});
// event listener for B_Update event
b.bind("B_Update", function(data) {
c.add(data);
});
With this event-based approach, your classes are as loose coupled as they can be, there's no need for A, B or C to know about each other. At the same time you get highest flexibility - at any point you can add more listeners or event types.
I feel that's the best solution.
I would add callbacks to class A and class B
function A(data, callback) {
this.data = data || [];
this.callback = callback || function() {};
}
A.prototype.remove = function(index) {
// remove index from this.data;
this.callback(this.data[index]);
}
...
var b = new B(xx);
var a = new A(xx, function(e) {b.add(e);});
Related
I understand that in D3, dispatch can be used to fire events to multiple visualisations according to this example.
I also understand that if I want to call a dispatch from an object and pass in the context, I can use apply as shown here.
However, I'm having a hard time combining the arguments from a D3 dispatch and the context that I want.
// create my dispatcher
var probeDispatch = d3.dispatch("probeLoad");
var line_count = 0;
// load a file with a bunch of JSON and send one entry every 50 ms
var lines = [[0,1],[1,2],[2,0]];
var parse_timer = window.setInterval(
function () {
parse_dispatch();
}, 50
);
function parse_dispatch(){
// send two arguments with my dispatch
probeDispatch.probeLoad(lines[line_count][0], lines[line_count][1]);
line_count += 1;
if(line_count >= lines.length){
//line_count = 0
window.clearInterval(parse_timer);
}
}
// my chart object
var genChart = function(label){
this.label = label;
// assume I've drawn my chart somewhere here
probeDispatch.on(("probeLoad."+this.label), this.probeParse);
// this next line isn't working, since the
// console.log in probeLoad still returns undefined
probeDispatch.probeLoad.apply(this);
};
genChart.prototype = {
probeParse: function(probeData, simTime) {
// How do I get the context from the object that's calling probeParse
// into the probeParse scope?
var self = this;
console.log(self.label);
}
};
new genChart("pants");
new genChart("shirt");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
It does set the context properly when you see "pants" in the console.
But then there are 3 undefined's logged, because you also call
// send two arguments with my dispatch
probeDispatch.probeLoad(lines[line_count][0], lines[line_count][1]);
without supplying context.
You need
probeDispatch.probeLoad.apply(instanceOfGenChart, [lines[line_count][0], lines[line_count][1]]);
But enabling that also requires moveing parse_dispatch down the page.
// create my dispatcher
var probeDispatch = d3.dispatch("probeLoad");
var line_count = 0;
// load a file with a bunch of JSON and send one entry every 50 ms
var lines = [[0,1],[1,2],[2,0]];
var parse_timer = window.setInterval(
function () {
parse_dispatch();
}, 50
);
// my chart object
var genChart = function(label){
this.label = label;
// assume I've drawn my chart somewhere here
probeDispatch.on(("probeLoad."+this.label), this.probeParse);
// this next line isn't working, but I don't know what to do
probeDispatch.probeLoad.apply(this);
};
genChart.prototype = {
probeParse: function(probeData, simTime) {
// How do I get the context from the object that's calling probeParse
// into the probeParse scope?
var self = this;
console.log(self.label);
}
};
var instanceOfGenChart = new genChart("pants");
function parse_dispatch(){
// send two arguments with my dispatch
probeDispatch.probeLoad.apply(instanceOfGenChart, [lines[line_count][0], lines[line_count][1]]);
line_count += 1;
if(line_count >= lines.length){
//line_count = 0
window.clearInterval(parse_timer);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
So it turns out to bring the context into the function, I have to bind() it for reasons I'm not too clear on.
// create my dispatcher
var probeDispatch = d3.dispatch("probeLoad");
var line_count = 0;
// load a file with a bunch of JSON and send one entry every 50 ms
var lines = [[0,1],[1,2],[2,0]];
var parse_timer = window.setInterval(
function () {
parse_dispatch();
}, 50
);
function parse_dispatch(){
// send two arguments with my dispatch
probeDispatch.probeLoad(lines[line_count][0], lines[line_count][1]);
line_count += 1;
if(line_count >= lines.length){
//line_count = 0
window.clearInterval(parse_timer);
}
}
// my chart object
var genChart = function(label){
this.label = label;
// assume I've drawn my chart somewhere here
probeDispatch.on(("probeLoad."+this.label), this.probeParse.bind(this));
};
genChart.prototype = {
probeParse: function(probeData, simTime) {
// How do I get the context from the object that's calling probeParse
// into the probeParse scope?
var self = this;
console.log(self.label);
}
};
new genChart("pants");
new genChart("shirt");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Added by meetamit
Bind is the solution here, because it locks a scope to an "instance" of genChart.prototype. probeParse. This way parse_dispatch (the invoker) doesn't need to know anything about scope. It's equivalent to this:
// my chart object
var genChart = function(label){
this.label = label;
var self = this;
var probeParseBound = function() { self.probeParse(); };
probeDispatch.on(("probeLoad."+this.label), probeParseBound);
};
Can you please help answering this. Please not the contraints.
var myLib = {
var callback_one = function (result_from_web_service) {
console.log('callback_one');
};
var callback_one = function (result_from_web_service) {
console.log('callback_two');
};
var init = function () {
console.log('initializing...');
async_call_one(callback_one);
async_call_two(callback_two);
};
var doStuff = function () {
console.log('doStuff is called');
};
};
// User of my library
myLib.init();
myLib.doStuff();
// output
initializing...
doStuff is called
callback_one
callback_two
// What i need:
initializing...
callback_one
callback_two
doStuff is called
Constraint:
calling myLib.init shall not end up calling myLib.doStuff. i.e. myLib.init should be independent of myLib.doStuff
myLib.doStuff() should be called after myLib.init() and its callbacks are returned.
Thanks,
//You must change your API so init is async
//There is no way to have it wait until all initialization is done before it retuns
var init = function (initDone) {
console.log('initializing...');
var n = 0;
function serviceDone(){
n++;
if(n >= 2){ initDone() }
}
async_call_one(function(x){ callback_one(x); serviceDone() });
async_call_two(function(x){ callback_two(x); serviceDone() });
};
// User of my library
myLib.init(function(){
myLib.doStuff();
})
The way I parallelized those calls is very ad-hoc s not the most maintainable (there I need to keep the calls to serviceDone and the value of N in sync).. In the long run I would recommend using one of the many JS async programming libs out there.
hugomg has a good answer.
Yet I think it is really specific and could benefit a sort of workflow implementation, like this (approximately...):
function void() {}
var myLib = {
var g_flow = [];
g_flow[this.init] = [];
g_flow[this.init]["whendone"] = this.callback_one;
g_flow[this.init]["done"] = false;
g_flow[this.callback_one] = [];
g_flow[this.callback_one]["whendone"] = this.callback_two;
g_flow[this.callback_one]["done"] = false;
g_flow[this.callback_two] = [];
g_flow[this.callback_two]["whendone"] = this.doStuff;
g_flow[this.callback_two]["done"] = false;
g_flow[this.doStuff] = [];
g_flow[this.doStuff]["whendone"] = void;
g_flow[this.doStuff]["done"] = false;
var callback_one = function (result_from_web_service) {
console.log('callback_one');
};
var callback_one = function (result_from_web_service) {
console.log('callback_two');
};
var init = function () {
console.log('initializing...');
};
var doStuff = function () {
console.log('doStuff is called');
};
var flow_onward(hwnd) {
async_call(function(){ hwnd(); myLib.flow_onward(g_flow[hwnd]["whendone"]); });
}
flow_onward(this.init);
};
// User of my library
myLib.init();
myLib.doStuff();
Doing this way you can ensure the sequentiality and expand the numbers of callback as much as you want.
ps: this code has not been tested
I have created a dynamic Bar graph using visualize function and visualize.css the value for graphs is given through HTML table now I want to perform an event on that graph.
Create a custom event:
(function() {
/**
* Small event class, can be instanciated as an object or using .call used to decorate other
* Objects with events funcitonality.
*/
MyEvent.Event = function() {
this.events = {};
this.bind = function(eventName, func) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(func);
};
this.trigger = function(eventName, args, scope) {
if (this.events[eventName]) {
var len = this.events[eventName].length,
funcArgs = (jQuery.isArray(args)) ? args : [args];
// This will go from back to front through the array not sure if this really matters?
while (len--) {
this.events[eventName][len].apply(scope || window, funcArgs);
}
}
}
this.removeAllEvents = function() {
this.events = {};
}
};
// Page level events class
MyEvent.pageEvents = new MyEvent.Event();
})();
trigger it:
MyEvent.pageEvents.trigger("Method-name", "data"));
and Finally listen it:
MyEvent.pageEvents.bind("Method-name", function(Data) {
//Code goes here
});
USING JQUERY?
Take a look at trigger and bind.
Here is the code:
http://jsfiddle.net/GKBfL/
I am trying to get collection.prototype.add to return a reference such that the final alert will display testing, testing, 123, testing. Is there a way to accomplish what I'm trying to do here?
HTML:
<span id="spantest">testing, testing, 123, testing</span>
JavaScript:
var collection = function () {
this.items = {};
}
collection.prototype.add = function(sElmtId) {
this.items[sElmtId] = {};
return this.items[sElmtId];
}
collection.prototype.bind = function() {
for (var sElmtId in this.items) {
this.items[sElmtId] = document.getElementById(sElmtId);
}
}
var col = new collection();
var obj = {};
obj = col.add('spantest');
col.bind();
alert(obj.innerHTML);
You problem is this line:
this.items[sElmtId] = document.getElementById(sElmtId);
This overwrites the object currently assigned to this.items[sElmtId] with the DOM node. Instead, you should assign the node to a property of that object:
this.items[sElmtId].node = document.getElementById(sElmtId);
That way, obj.node will always refer to the current node:
alert(obj.node.innerHTML);
DEMO
Side note: The problem with your fiddle is also that you execute the code when the DOM is not built yet (no wrap (head)), so it cannot find #spantest. You have to run the code once the DOM is ready, either no wrap (body), onDomRead or onLoad.
Creating a reference like you need is impossible in JavaScript. The closest thing you can get is either a nested or closed object, or just copying it over, like so:
var collection = function() {
this.items = {};
};
collection.prototype.add = function(sElmtId) {
return this.items[sElmtId] = {};
};
collection.prototype.bind = function() {
for(var sElmtId in this.items) {
var element = document.getElementById(sElmtId);
for(var x in element) {
this.items[sElmtId][x] = element[x];
}
}
};
var col = new collection();
var obj = {};
obj = col.add('spantest');
col.bind();
alert(obj.innerHTML);
But it won't be truly "bound". You'll have to use nested objects if you need that kind of functionality, and it will probably defeat the point of your syntactic sugar.
http://jsfiddle.net/GKBfL/7/
Is there a way to know when a user has pushed (via push()) an item onto an array?
Basically I have an asynchronous script that allows the user to push commands onto an array. Once my script loads, it execute the commands. The problems is, the user may push additional commands onto the array after my script has already run and I need to be notified when this happens. Keep in mind this is just a regular array that the user creates themselves. Google Analytics does something similar to this.
I also found this which is where I think Google does it, but I don't quite understand the code:
Aa = function (k) {
return Object.prototype[ha].call(Object(k)) == "[object Array]"
I also found a great example which seems to cover the bases, but I can't get my added push method to work correctly:
http://jsbin.com/ixovi4/4/edit
You could use an 'eventify' function that overrides push in the passed array.
var eventify = function(arr, callback) {
arr.push = function(e) {
Array.prototype.push.call(arr, e);
callback(arr);
};
};
In the following example, 3 alerts should be raised as that is what the event handler (callback) does after eventify has been called.
var testArr = [1, 2];
testArr.push(3);
eventify(testArr, function(updatedArr) {
alert(updatedArr.length);
});
testArr.push(4);
testArr.push(5);
testArr.push(6);
The only sensible way to do this is to write a class that wraps around an array:
function EventedArray(handler) {
this.stack = [];
this.mutationHandler = handler || function() {};
this.setHandler = function(f) {
this.mutationHandler = f;
};
this.callHandler = function() {
if(typeof this.mutationHandler === 'function') {
this.mutationHandler();
}
};
this.push = function(obj) {
this.stack.push(obj);
this.callHandler();
};
this.pop = function() {
this.callHandler();
return this.stack.pop();
};
this.getArray = function() {
return this.stack;
}
}
var handler = function() {
console.log('something changed');
};
var arr = new EventedArray(handler);
//or
var arr = new EventedArray();
arr.setHandler(handler);
arr.push('something interesting'); //logs 'something changed'
try this:
var MyArray = function() { };
MyArray.prototype = Array.prototype;
MyArray.prototype.push = function() {
console.log('push now!');
for(var i = 0; i < arguments.length; i++ ) {
Array.prototype.push.call(this, arguments[i]);
}
};
var arr = new MyArray();
arr.push(2,3,'test',1);
you can add functions at after pushing or before pushing
Why not just do something like this?
Array.prototype.eventPush = function(item, callback) {
this.push(item);
callback(this);
}
Then define a handler.
handler = function(array) {
console.log(array.length)
}
Then use the eventPush in the place that you want a specific event to happen passing in the handler like so:
a = []
a.eventPush(1, handler);
a.eventPush(2, handler);
I'd wrap the original array around a simple observer interface like so.
function EventedList(list){
this.listbase = list;
this.events = [];
}
EventedList.prototype.on = function(name, callback){
this.events.push({
name:name,
callback:callback
});
}
//push to listbase and emit added event
EventedList.prototype.push = function(item){
this.listbase.push(item);
this._emit("added", item)
}
EventedList.prototype._emit = function(evtName, data){
this.events.forEach(function(event){
if(evtName === event.name){
event.callback.call(null, data, this.listbase);
}
}.bind(this));
}
Then i'd instantiate it with a base array
//returns an object interface that lets you observe the array
var evtList = new EventedList([]);
//attach a listener to the added event
evtList.on('added', function(item, list){
console.log("added event called: item = "+ item +", baseArray = "+ list);
})
evtList.push(1) //added event called: item = 1, baseArray = 1
evtList.push(2) //added event called: item = 2, baseArray = 1,2
evtList.push(3) //added event called: item = 3, baseArray = 1,2,3
you can also extend the observer to observe other things like prePush or postPush or whatever events you'd like to emit as you interact with the internal base array.
This will add a function called onPush to all arrays, by default it shouldn't do anything so it doesn't interfere with normal functioning arrays.
just override onPush on an individual array.
Array.prototype.oldPush = Array.prototype.push;
Array.prototype.push = function(obj){
this.onPush(obj);
this.oldPush(obj);
};
//Override this method, be default this shouldnt do anything. As an example it will.
Array.prototype.onPush = function(obj){
alert(obj + 'got pushed');
};
//Example
var someArray = [];
//Overriding
someArray.onPush = function(obj){
alert('somearray now has a ' + obj + ' in it');
};
//Testing
someArray.push('swag');
This alerts 'somearray now has a swag in it'
If you want to do it on a single array :
var a = [];
a.push = function(item) {
Array.prototype.push.call(this, item);
this.onPush(item);
};
a.onPush = function(obj) {
// Do your stuff here (ex: alert(this.length);)
};
Sometimes you need to queue things up before a callback is available. This solves that issue. Push any item(s) to an array. Once you want to start consuming these items, pass the array and a callback to QueuedCallback(). QueuedCallback will overload array.push as your callback and then cycle through any queued up items. Continue to push items to that array and they will be forwarded directly to your callback. The array will remain empty.
Compatible with all browsers and IE 5.5+.
var QueuedCallback = function(arr, callback) {
arr.push = callback;
while (arr.length) callback(arr.shift());
};
Sample usage here.
Untested, but I am assuming something like this could work:
Array.prototype.push = function(e) {
this.push(e);
callbackFunction(e);
}
A lot better way is to use the fact that those methods modify array length.
The way to take advantage of that is quite simple (CoffeeScript):
class app.ArrayModelWrapper extends Array
constructor: (arr,chName,path)->
vl = arr.length
#.push.apply(#,arr)
Object.defineProperty(#,"length",{
get: ->vl
set: (newValue)=>
console.log("Hello there ;)")
vl = newValue
vl
enumerable: false
})
for debugging purpose you can try. And track the calling function from the call stack.
yourArray.push = function(){debugger;}
We can prototype Array to add a MyPush function that does push the rec to the array and then dispatches the event.
Array.prototype.MyPush = (rec) =>
{
var onArrayPush = new Event("onArrayPush",{bubbles:true,cancelable:true});
this.push(rec);
window.dispatchEvent(onArrayPush);
};
and then we need an eventhandler to capture the event, here I am capturing the event to log the event and then indexing the record for example:
addEventListener("onArrayPush",(e)=> {
this.#Log(e);
this.#IndexRecords();
});
But in 2022 you may also go with callback as:
Array.prototype.MyPush = function(rec,cb){
this.push(rec);
cb(rec);
};
here cb is the callback that is invoked after rec is pushed to the Array. This works at least in the console.