Collections of Records in JavaScript

AID: 3547
  • Status: Published

9060 points

  • By
  • TypeGeneral
  • Posted on2010-08-08 at 03:53:08
Awards
  • Community Pick
  • Experts Exchange Approved
When you need to keep track of a simple list of numbers or strings, the Array object is your most direct tool.  As we saw in my earlier EE Article, typical array handling might look like this:
var aNames= [ "Adam", "Bill", "Carl" ]; // create an array

alert( aNames[0] ); // Adam  --  look it up by its index number

// iterate through them
for ( var j=0; j< aNames.length; j++ ) {
    alert( aNames[j] );  // 
}
                                    
1:
2:
3:
4:
5:
6:
7:
8:

Select allOpen in new window


But JavaScript also supports Associative Arrays (aka Maps or Dictionaries) -- providing a way to access list items not just by position, but by an associated key.
fig1-1.jpg
  • 51 KB
  • The Associate Array is a "bag of properties" of the base Object object
The Associate Array is a "bag of properties" of the base Object object

However, this support might not work exactly the way that you might think it should...  
// this seems reasonable...

var aNames= new Array();  
aNames[ "Anderson" ]= "Adam";
aNames[ "Browne"   ]= "Bill";
aNames[ "Crawford" ]= "Carl";

alert( aNames[ "Anderson" ] );  // shows Adam (as expected)

// ... but this might not seem reasonable:

alert( aNames.length ); // shows 0 !!! (why not 3?)
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:

Select allOpen in new window


The above code creates an Array and then puts nothing into any of the array elements!

Here's what's going on:  Every JavaScript variable (including Array variables) is an object with the underlying features of the Object object.  It is that base class that supports the key/value pair handling of associative arrays.  So, for instance:
var mapNames= new Object();     // or:  var mapNames= {};
mapNames[ "Anderson" ]= "Adam";
mapNames[ "Browne"   ]= "Bill";
mapNames.Crawford     = "Carl"; // alternate syntax
mapNames[ 2          ]= "Two";

alert( mapNames.length ); // undefined (it is not an Array)

for ( var j in mapNames ) { // show the keys
    alert( j );             // Anderson, Browne, Crawford, 2
}
for ( var j in mapNames ) { // show the data associated with each key
    alert( mapNames[j] );   // Adam, Bill, Carl, Two
}
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:

Select allOpen in new window


These key/value pairs are often called properties of the object.

The Object object supports a syntax option that allows you to define the key and the value at the same time:
var mapNames= {
   "Anderson" : "Adam",    // syntax is key:value
   "Browne"   : "Bill",
   "Crawford" : "Carl"
};
for ( var n in mapNames ) {        // show the keys and values
    alert( n+ "=" + mapNames[n] ); // Anderson=Adam, Browne=Bill, etc...
}
                                    
1:
2:
3:
4:
5:
6:
7:
8:

Select allOpen in new window


The key is on the left and the value is on the right.  The value does not need to be a string or other simple value.  It can be an array, or it can be another object that has its own map of key/value pairs (properties).  You can make the object as complex as you want.  See JSON for some related information and examples.


An Object as a Function Parameter


This flexibility makes it very useful for many things, one of which is passing data to a function call in such a way that the function can refer to the parameters by name.  You've probably seen this syntax:
...
DoThat( {color:"Red", font:"Arial" } ); // unnamed object
...
function DoThat( o ) {
    alert( o.color );    // shows:  Red
    alert( o.font );     // shows:  Arial
}
                                    
1:
2:
3:
4:
5:
6:
7:

Select allOpen in new window


The caller creates an anonymous object (a map of key:value pairs) and the called function can then access the values by name.  Note the shortcut used in the call:  If the key is a text string, you can omit the quotes when you identify the property.


Creating an Associative Array 'Record'


You can create a map object on-the-fly, adding new properties at whim.  This flexibility has a practical programming downside; for example:
var o= {};              // or: var o= new Object();
o.lastName= "Anderson";
o.age= 17;

... later that day ...

o.LastName= "Smith";  // oops, uppercase L
                                    
1:
2:
3:
4:
5:
6:
7:

Select allOpen in new window


If you misspell a key name, then you have just added a new property to the object.  That's probably not what you wanted to do!  Though JavaScript associative maps don't need to be declared in advance, doing so can help you to avoid some errors.  Let's look at a way of "formalizing" the object properties.

It is often the case that you want to predefine a record -- a structure that contains all of the data you want to be contained in that object.  You'll want to be able to refer to the definition of that object so that you can see all of its properties (and, for one thing, make sure that you spell them right each and every time you use one).  

At the same time, you probably want to provide a way to initialize such a record.  You'll want to set default values and provide a way to populate it easily.  Enter, stage right, the JavaScript constructor syntax:
function PersonRec() {
    this.sNameLast=  '';   // the "this." part is required
    this.sNameFirst= '';
    this.nAge=       0;
    this.fIsParent=  false;
    this.asChildren= new Array();
}
                                    
1:
2:
3:
4:
5:
6:
7:

Select allOpen in new window


Here we have defined an object with a set of specific properties.   When I get to this stage, I start to think of the object as a record with fields.

Coming from a C++ background, I thought it strange to use the function keyword as the means to define a structure, but it makes sense in the context that it is actually a constructor function.  The above example accepts no parameters, but the following one accepts a variable number of parameters and assigns them to the record fields.
function PersonRec(p1,p2,p3,p4,p5) {
    this.sNameLast= (p1 != undefined) ? p1 : '';
    this.sNameFirst=(p2 != undefined) ? p2 : '';
    this.nAge=      (p3 != undefined) ? p3 : -1;
    this.fIsParent= (p4 != undefined) ? p4 : false;
    this.asChildren=(p5 != undefined) ? p5 : new Array();
}
                                    
1:
2:
3:
4:
5:
6:
7:

Select allOpen in new window


That version accepts a variable number of parameters.  Missing parameters are detected and the corresponding fields will be assigned default values.  To create an instance of the record, use syntax like:
var rPer1= new PersonRec();  // populated with defaults

var rPer2= new PersonRec( "Anderson", "Adam", 35, true, ["Andy","Al"] ); // all data

var rPer3= new PersonRec( "Browne", "Bill" );  // with some data and some defaults

rPer3.nAge= 43;              // update the record
rPer3.sNameFirst= "William"; // update the record
                                    
1:
2:
3:
4:
5:
6:
7:
8:

Select allOpen in new window


The constructor can do a lot more than just assign values.  It can calculate fields based on other fields, or pre-populate a lookup table, or read and parse data from a webpage, or grab data from a database  -- any kind of initialization that you want to have happen each time an instance of the record object is created.

What's more, the record does not need to just house data.  As with any object-oriented programming, you can provide methods (functions) that will apply to the associated data.  For instance, we could add:
function PersonRec(p1,p2) {
    this.sNameLast= p1;
    this.sNameFirst=p2;
    ... 
    //-------------------------------- add some methods; i.e., member functions
    this.GetFullName= function() {
       return( this.sNameFirst +" "+ this.sNameLast ); 
    };

    this.toString= function() { 
        var s=  "Name:  " + this.GetFullName() + "\n";
        s+=     "Age:   " + this.nAge          + "\n";
        if ( this.fIsParent ) {
            s+= "Children: "    + this.asChildren;
        }
        return( s );
    };
}
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:

Select allOpen in new window


The Object.toString() member already exists, but it just outputs [Object object], which is not too useful.  It's handy to write an override function like that shown, so that you can get a quick display of the data for easy debugging.


Using an Associative Array 'Record'


An object like the one we've defined here is most often used as one item in an array of such records.  This is the classic collection concept.  Let's say that you had a dropdown list of options, and when the user selects one, you would want to populate a set of input fields on a form.

Your JavaScript might populate the collection by using AJAX, or parsing a web page, or reading from a disk file.  Or you might pre-populate an array in the source code:
var arPersons = [
   new PersonRec("Anderson", "Adam", 35, true, ["Andy","Alice"] ),
   new PersonRec("Browne",   "Bill", 25, false ),
   new PersonRec("Crawford", "Carl", 45, true, ["Caroline"] )
];
                                    
1:
2:
3:
4:
5:

Select allOpen in new window


Now that we have defined an array of these records, we can easily cycle through them using JavaScript array syntax:
var oCombo= document.getElementById('selectName'); // a <SELECT> element

oCombo.options.length=0;                // clear out the box
oCombo.options.length=arPersons.length; // prepare to repopulate

for (var j=0; j<arPersons.length; j++) {
   oCombo.options[j].text= arPersons[j].sNameLast;
}

... later, see what's been selected ...

var i= oCombo.selectedIndex;
alert( arPersons[i].sNameFirst +" is "+ arPersons[i].nAge );
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:

Select allOpen in new window


And to generate a table like this:
fig1-2.jpg
  • 16 KB
  • Output a simple table from the collection
Output a simple table from the collection

...just cycle through the array elements and display the PersonRec object attribute values like so:
function BuildOutputTable() {
    var sOut="<table border=2>";
    sOut +="<tr>";
    sOut +="<td>Last Name</td><td>First Name</td><td>Age</td><td># of Kids</td>"
    sOut +="</tr>";
    for ( var j=0; j<arPersons.length; j++ ) {
        var rP= arPersons[j];  // a PersonRec object
        sOut += "<tr>";
        sOut += "<td>" +rP.sNameLast+         "</td>";
        sOut += "<td>" +rP.sNameFirst+        "</td>";
        sOut += "<td>" +rP.nAge+              "</td>";
        sOut += "<td>" +rP.asChildren.length+ "</td>";
        sOut += "</tr>";
    }
    sOut += "</table>";
    return(sOut);
}
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:

Select allOpen in new window



Summary


In this article:

  • We looked at the JavaScript Object object and its built-in functionality for handling an associative array.

  • We took special notice of the differences between array data (indexed by a number) and object properties (indexed by a key value).

  • One useful ability of an associative array is that it lets you pass parameter data to a function in a very flexible way.

  • We saw how to create a constructor function as a means to formalize the elements in the object and to do initialization.  We saw how to add methods (functions) to such a record.

  • Finally, we created and accessed an array of Objects, treating them as a set of records.

In the next installment, we will look at how to sort and do other manipulations of this type of JavaScript record.


References


Related Articles by Dan Rollins:

   Sorting Arrays and Collections in JavaScript
   2D Arrays in JavaScript
   HTA - Hypertext Application tutorial
   Convert string to number / Convert number to string in JavaScript
   Debugging JScript Programs with Visual Studio 2008

The following webpages contain useful information on related topics:

    Objects as associative arrays
    Mastering Javascript Arrays
    Mastering JSON ( JavaScript Object Notation )

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
If you liked this article and want to see more from this author, please click the Yes button near the:
      Was this article helpful?
label that is just below and to the right of this text.   Thanks!
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    Asked On
    2010-08-08 at 03:53:08ID3547
    Tags

    associative array

    ,

    map

    ,

    object properties

    ,

    records and fields

    ,

    array

    ,

    constructors in JavaScript

    ,

    Dan Rollins

    Topic

    JavaScript

    Views
    3696

    Comments

    Expert Comment

    by: mplungjan on 2010-08-20 at 04:26:54ID: 18430

    Alternative:

    function PersonRec(p1,p2,p3,p4,p5) {
      this.sNameLast   =  p1 || "";
      this.sNameFirst  =  p2 || "";
      this.nAge        = (p3 || (p3===0)?0:-1; // okeee - perhaps not elegant
      this.fIsParent   = (p4 || (p3===true)?true:false; // nor here
      this.asChildren  =  p5 || [];
    }
    
                                            
    1:
    2:
    3:
    4:
    5:
    6:
    7:
    

    Select allOpen in new window

    Author Comment

    by: DanRollins on 2010-08-20 at 13:31:45ID: 18456

    Ah, yes, the cryptic OR-op-idiom technique which leverages the fact that that "undefined" equates to "false" so the second part executes only if the first has no value.  I shy away from syntax that is prone to parentheses mistakes and/or takes deep mental calculus to read ... especially in tutorial articles.  Even the "ternary" x?y:z was a break from the simple code that I really like :-)

    =====================
    One issue I hit on that "constructor issue" (and did not discuss in the article) is what happens when the caller skips a parameter in the middle of the list:

         PersonRec("Rollins", "Dan", ,true, ['Ace','Spike'] ) ;

    It seems to be a JavaScript compile-time syntax error to use two consecutive commas that way in a function call.  I guess it's like with C++ where one needs to put the seldom-used parameters at the end.  One could actually send in the string 'undefined', I suppose.

    Expert Comment

    by: mplungjan on 2010-08-20 at 21:42:37ID: 18486

    I would send null

    Add your Comment

    Please Sign up or Log in to comment on this article.

    Join Experts Exchange Today

    Gain Access to all our Tech Resources

    Get personalized answers

    Ask unlimited questions

    Access Proven Solutions

    Search 3.2 million solutions

    Read In-Depth How-To Guides

    1000+ articles, demos, & tips

    Watch Step by Step Tutorials

    Learn direct from top tech pros

    And Much More!

    Your complete tech resource

    See Plans and Pricing

    30-day free trial. Register in 60 seconds.

    Loading Advertisement...

    Top JavaScript Experts

    1. leakim971

      511,289

      Sage

      2,168 points yesterday

      Profile
      Rank: Genius
    2. mplungjan

      291,279

      Guru

      2,800 points yesterday

      Profile
      Rank: Savant
    3. nap0leon

      195,491

      Guru

      0 points yesterday

      Profile
      Rank: Sage
    4. Proculopsis

      182,948

      Guru

      0 points yesterday

      Profile
      Rank: Sage
    5. COBOLdinosaur

      157,309

      Guru

      0 points yesterday

      Profile
      Rank: Genius
    6. chaituu

      130,684

      Master

      0 points yesterday

      Profile
      Rank: Sage
    7. Ray_Paseur

      130,217

      Master

      330 points yesterday

      Profile
      Rank: Savant
    8. tommyBoy

      125,345

      Master

      0 points yesterday

      Profile
      Rank: Genius
    9. StingRaY

      114,318

      Master

      0 points yesterday

      Profile
      Rank: Wizard
    10. DaveBaldwin

      80,081

      Master

      336 points yesterday

      Profile
      Rank: Genius
    11. ansudhindra

      79,054

      Master

      2,000 points yesterday

      Profile
      Rank: Wizard
    12. ChrisStanyon

      62,768

      Master

      800 points yesterday

      Profile
      Rank: Sage
    13. hielo

      61,266

      Master

      0 points yesterday

      Profile
      Rank: Savant
    14. HainKurt

      59,030

      Master

      0 points yesterday

      Profile
      Rank: Genius
    15. BuggyCoder

      54,739

      Master

      0 points yesterday

      Profile
      Rank: Sage
    16. mroonal

      54,339

      Master

      10 points yesterday

      Profile
      Rank: Sage
    17. tagit

      54,093

      Master

      1,600 points yesterday

      Profile
      Rank: Genius
    18. gurvinder372

      52,824

      Master

      10 points yesterday

      Profile
      Rank: Genius
    19. basicinstinct

      52,586

      Master

      0 points yesterday

      Profile
      Rank: Genius
    20. JonNorman

      45,158

      2,200 points yesterday

      Profile
      Rank: Master
    21. Lalit-Chandra

      44,420

      0 points yesterday

      Profile
      Rank: Master
    22. xmediaman

      36,450

      3,800 points yesterday

      Profile
      Rank: Guru
    23. kozaiwaniec

      33,100

      0 points yesterday

      Profile
      Rank: Guru
    24. Kravimir

      32,700

      0 points yesterday

      Profile
      Rank: Genius
    25. designatedinitializer

      32,300

      0 points yesterday

      Profile
      Rank: Master

    Hall Of Fame