1

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.

1
  • What exactly do you need to store? All messages and just a count of sent / received, sent and received messages separately and just a count for total, or both? Commented Jun 16, 2013 at 23:10

3 Answers 3

2

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.

Sign up to request clarification or add additional context in comments.

2 Comments

For the line: this.[msg.source].push(msg); Is that a special kind of notation where the msg.source variable will get injected in before the code is run? What is this notation called?
Nice answer, but set the push function on the prototype.
1

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.

2 Comments

I like this very much. Could you please explain get count() {... part of your code? I don't recognize the syntax.
Thx. this syntax is an 1.8.5 feature. It is used to define a getter property within an object (Object.defineProperty being another way). You can find all on getters on MDN : developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… . Setters work in quite the same way.
0

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

4 Comments

You'll need to wrap that in a function for the _arr = [] part to work (as well as the this._arr part).
Unfortunately, I think I may need multiple instances.
@DanTao I meant _arr as a property, fixed now.
@ibopm Added a brief example on using a constructor for multiple instances

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.