6

Imagine the following code:

$.get( "ajax/getColorData.php", function( data ) {
  this.colorData = data;
});

now envision that the value of 'data' is:

this.colorData = [
    {
        colorName: 'Red',
        colorIsInRainbow:true
    },
    {
        colorName: 'Orange',
        colorIsInRainbow: true
    },
    {
        colorName: 'Magenta',
        colorIsInRainbow: false
    }
];

Question 1

Now, after I download the data, let's say that I want to add a method, "colorStartsWithR" to every entry in the array. I "think" that rather than define it on every single member of the array, I could somehow define this method on the prototype. But, I'm not sure that I can do that because these objects were not created by me, but were returned by $.get, so it's not clear to me if I'm thinking in the right direction.

Question 2

And to take it a bit further, what if I wanted to add a property to every member of the array, specifically:

    {
        colorName: 'Magenta',
        colorIsInRainbow: false,
        userClickedThisColor:ko.observable()
    }

This way, I can bind (via knockoutjs) the list and include a checkbox. In this case, I question whether or not the prototype would come in handy because each member should get its own ko.observable property. A quick instinct is to do something like:

for (var i=0;i<this.colorData.length;i++)
{
 this.colorData[i].userClickedThisColor=ko.observable()
}

and that works fine, but imagine that there is more than one way to retrieve a list of colors, or that a user can create new color instances, and that the customization I did above is more complicated. I would now need to duplicate the above "add-on" logic. Is there a smarter way?

Having spent most of my time in strongly-typed languages, sometimes these things aren't as obvious to me. Thanks for any suggestions...

-Ben

BenjiFB
  • 4,545
  • 10
  • 46
  • 53

3 Answers3

2

Well, those objects don't have a special prototype, so you can't add members to it. I think these are the alternatives:

1) Add the members to each instance (I know you don't like it, but it's an option). I'd go for this option

2) Just create a method, and pass each object as a parameter or as this with fn.call()

3) Create a "class" with the added members, then create instances of it passing the original object in the constructor. Not ideal, but maybe you have to do it.

function Color(plainObj){
   this.colorName: plainObj.colorName;
   this.colorIsInRainbow: plainObj.colorIsInRainbow;

   //if you want all properties to be copied dynamically, uncomment the following:
   //for(var key in plainObj) this[key] = plainObj[key];                 

   this.userClickedThisColor = ko.observable()
   ...
}
Color.prototype.colorStartsWithR = function() {
    return this.colorName.charAt(0) == "R";
};

//etc

Then, to apply it

for(var i=0; i<colorData.length; i++){
   colorData[i] = new Color(colorData[i]); //Overwrites original colorData array
}

Hope this helps. Cheers

Edgar Villegas Alvarado
  • 18,204
  • 2
  • 42
  • 61
  • Thanks Edgar! I'm only able to mark one post as an answer it seems, though yours is also helpful. – BenjiFB Jan 08 '14 at 02:01
  • Thanks! So, just to confirm about this line: Color.prototype.userClickedThisColor = ko.observable(); am I correct that despite that being defined in the prototype, each instance will indeed get its own separate copy? – BenjiFB Jan 08 '14 at 02:06
  • No, all will share the same. For each one to have a different one, you should move that to the constructor – Edgar Villegas Alvarado Jan 08 '14 at 02:07
  • Adding for others' notes: it seems like in the above constructor, you could instead dynamically enumerate all properties of the object to copy them over. That way, if properties are added or removed, no need to update that part of the code. http://stackoverflow.com/questions/85992/how-do-i-enumerate-the-properties-of-a-javascript-object – BenjiFB Jan 08 '14 at 02:10
  • That's right. As they are only 2 in this case, I'm adding them manually (shorter code). For 4 or more, better to do it automatically – Edgar Villegas Alvarado Jan 08 '14 at 02:14
  • I edited my answer adding that, also moving `ko.observable()` – Edgar Villegas Alvarado Jan 08 '14 at 02:17
2

One option is to create a wrapper object and add the new method to the wrappers prototype:

var colorData = [
    {
        colorName: 'Red',
        colorIsInRainbow:true
    },
    {
        colorName: 'Orange',
        colorIsInRainbow: true
    },
    {
        colorName: 'Magenta',
        colorIsInRainbow: false
    }
];

function ColorDataWrapper(colorData){
   this.colorData = colorData;
}

ColorDataWrapper.prototype.colorStartsWithR = function(){
   return this.colorData.colorName.substring(0,1) == "R";
};

for(var i = 0; i < colorData.length; i++){
   colorData[i] = new ColorDataWrapper(colorData[i]);
}

alert(colorData[0].colorStartsWithR());
alert(colorData[1].colorStartsWithR());

JS Fiddle: http://jsfiddle.net/ywrK6/1/

Another option is to just create a function that accepts the object as an argument:

function colorStartsWithR(obj){
    return obj.colorData.colorName.substring(0,1) == "R";
}
Kevin Bowersox
  • 93,289
  • 19
  • 159
  • 189
  • Adding a comment mostly for my notes: It looks like in this solution, the ColorDataWrapper function is creating the wrapper at a level higher than the other solutions, which means that there's no need to either accept a parameter for each member (jfriend00's example), or do dynamic property enumeration (Edgar's example). That's nice because it's simple, but in the method, you have an extra level of indirection in the method colorStartsWithR (which seems like a small price to pay for the simplicity). Just wanted to point this out (if I understand correctly). – BenjiFB Jan 08 '14 at 02:30
  • If one is going to loop over all the elements in the array, then you can just add the method `colorStartsWithR()` directly to each object in the array. The method doesn't have to be on the prototype - it can be added directly to an object. There's no need to replace that object with a different kind of object. – jfriend00 Jan 08 '14 at 02:50
1

The objects in your array are plain objects of the type Object. To add methods to the prototype of those objects, you'd have to add the methods to the Object type like this:

Object.prototype.colorStartsWithR = function() {
    return this.colorName.substr(0,1) == "R";
}

That would work in this case, but it is really not recommended. The better solutions are to either change the objects in the array to be a different type of object that has it's own prototype to which you can add colorStartsWithR() to OR just use a utility function to test a name to see if starts with "R"`.

Here are examples:

function colorObj(color, inRainbow) {
    this.colorName = color;
    this.colorIsInRainbow = inRainbow;
}

colorObj.prototype.colorStartsWithR = function() {
        return this.colorName.substr(0,1) == "R";
}

this.colorData = [
    new colorObj('Red', true),
    new colorObj('Orange, true),
    new colorObj('Magenta, false)
};

Then, you could do:

this.colorData[1].colorStartsWithR();

Or, just a utility function:

var colorData = [
    {
        colorName: 'Red',
        colorIsInRainbow:true
    },
    {
        colorName: 'Orange',
        colorIsInRainbow: true
    },
    {
        colorName: 'Magenta',
        colorIsInRainbow: false
    }
];

function startsWithR(str) {
    return str.substr(0,1) == "R";

}

startsWithR(colorData[1].colorName);
jfriend00
  • 683,504
  • 96
  • 985
  • 979