Reputation: 1996
I want to define a Javascript object which manages messages. Within this object, I'll need an array that I can do a push() to:
MsgObjCollection.push(MsgObj)
Essentially I am trying to fill the MsgObjCollection object with a bunch of MsgObjs. Each MsgObj has the 3 variables messagesText, timeStamp, source (sent or received).
Also, I'll need to have some methods like:
MsgObjCollection.Sent.Count // Counts the number of sent messages
MsgObjCollection.Received.Count // Counts the number of received messages
MsgObjCollection.Count // Counts the total number of messages in the object
I'm not sure how to approach this in the simplest, cleanest manner.
NOTE: In case there's any confusion, these are not static methods. I'll be creating instances of these objects using the new operator. So I will need multiple instances.
Upvotes: 1
Views: 139
Reputation: 19294
You need push, count, you might want to have all arrays methods / accesssors / iterators. What's more, you 'll get some speed boost if you let your collection be an array.
So best solution is to inherit from array, and to have your objects be just real arrays : nothing should be defined on the object, everything on its prototype.
-->> You'll get the speed and all features of arrays for free.
The function looks like :
function MsgObjCollection() { /* nothing */ };
var SO_pr = ( MsgObjCollection.prototype = [] ) ;
And then, to define count, sent and received on the prototype, use Object.defineProperty not to pollute enumeration, and also to have getters/setters :
Object.defineProperty(SO_pr, 'sent', { get : function() {
var cnt = 0;
this.forEach( function(x) { if (x.source == 'Sent') cnt++; });
return cnt; } } );
Object.defineProperty(SO_pr, 'received', { get : function() {
var cnt = 0;
this.forEach( function(x) { if (x.source == 'Received') cnt++; });
return cnt; } } );
Object.defineProperty(SO_pr, 'count', { get : function() { return this.length } ,
set : function (x) { this.length = x } });
Notice that since the Msg collection's prototype is a new array, you do not pollute array's prototype when changing MsgObjCollection's prototype.
The Sent and Received property you wish are more complex : they act as a view on the underlying object.
One thing you can do is to have them return a new array built out of the right items of the original array.
I prefer, though, to build a wrapper around the original array 1) to allow modification through this view and 2) to avoid garbage creation.
The fiddle is here : http://jsfiddle.net/cjQFj/1/
Object.defineProperty(SO_pr, 'Sent',
{ get : function() { return getWrapper('Sent', this); } } ) ;
Object.defineProperty(SO_pr, 'Received',
{ get : function() { return getWrapper('Received', this); } } ) ;
function getWrapper(wrappedProp, wrappedThis) {
var indx = 0, wp=null;
// try to find a cached wrapper
while (wp = getWrapper.wrappers[indx++] ) {
if (wp.wthis === this && wp.wprop==wrappedProp) return wp.wrapper;
};
// no wrapper : now build, store, then return a new one
var newWrapper = {
get count() { return (wrappedProp=='Sent') ? wrappedThis.sent : wrappedThis.received },
unshift : function () { if (this.count == 0) return null;
var indx=0;
while (wrappedThis[indx].source != wrappedProp ) indx++;
var popped = wrappedThis[indx];
while (indx<wrappedThis.length-1) {wrappedThis[indx]=wrappedThis[indx+1]; indx++; }
wrappedThis.length--;
return popped;
}
};
getWrapper.wrappers.push({ wthis : wrappedThis, wprop : wrappedProp, wrapper : newWrapper });
return newWrapper;
};
getWrapper.wrappers = [];
Now just a little test :
var myColl = new MsgObjCollection();
myColl.push({ source : 'Sent', message : 'hello to Jhon' });
myColl.push({ source : 'Received' , message : 'hi from Kate' });
myColl.push({ source : 'Sent', message : 'hello to Kate' });
myColl.push({ source : 'Received' , message : 'Reply from Jhon' });
myColl.push({ source : 'Received' , message : 'Ho, i forgot from Jhon' });
console.log('total number of messages : ' + myColl.count);
console.log('sent : ' + myColl.sent + ' Sent.count ' + myColl.Sent.count);
console.log('received : ' + myColl.received + ' Received.count ' + myColl.Received.count);
console.log('removing oldest sent message ');
var myLastSent = myColl.Sent.unshift();
console.log ('oldest sent message content : ' + myLastSent.message);
console.log('total number of messages : ' + myColl.count);
console.log('sent : ' + myColl.sent + ' Sent.count ' + myColl.Sent.count);
console.log('received : ' + myColl.received + ' Received.count ' + myColl.Received.count);
Output : >>
total number of messages : 5
sent : 2 Sent.count 2
received : 3 Received.count 3
removing oldest sent message
oldest sent message content : hello to Jhon
total number of messages : 4
sent : 1 Sent.count 1
received : 3 Received.count 3
The annoying part is that those view properties are not arrays, but since you cannot overload [] operator, you cannot have a fully transparent view on the original array, (i.e. : myBox.Sent[i] that would be exactly the i-th sent message ) so at some point you might want to create arrays on the fly for some operations.
Upvotes: 1
Reputation: 128317
Here's a tweak on bfavaretto's answer that should get you closer to what you want:
function MsgObjCollection() {
this.sent = [];
this.received = [];
this.total = [];
this.push = function(msg) {
// Assuming msg.source is either 'sent' or 'received',
// this will push to the appropriate array.
this[msg.source].push(msg);
// Always push to the 'total' array.
this.total.push(msg);
};
};
You would use this as follows:
var coll = new MsgObjCollection();
coll.push(/* whatever */);
var sent = coll.sent.length;
var received = coll.received.length;
If you wanted, you could wrap the sent
and received
arrays with objects that expose a Count
function instead of a length
property; but that strikes me as unnecessary.
Upvotes: 2
Reputation: 71908
There are several ways to do that. One of the simplest, if you only need one instance, is an object literal:
var MsgObjCollection = {
_arr : [],
push : function(val) {
return this._arr.push(val);
},
Sent : {
Count : function() {
// ...
}
},
// etc.
};
If you need multiple instances, use a constructor, and add methods to its prototype
property:
function MsgObjCollection() {
this._arr = [];
}
MsgObjCollection.prototype.push = function(val) {
return this._arr.push(val);
}
MsgObjCollection.prototype.get = function(index) {
return this._arr[index];
}
// and so on...
// USAGE:
var collection = new MsgObjCollection();
collection.push('foo');
console.log(collection.get(0));
Upvotes: 0