views:

651

answers:

3

I have this object:

function formBuddy()
{
    var fields = new Array();
    var labels = new Array();
    var rules = new Array();
    var count=0;

    this.addField = function(field, label, rule)
    {
        fields[count] = field;
        labels[field] = label;
        rules[field] = rule;
        count = ++count;
    }
}

Its used in this way:

var cForm=new formBuddy();
cForm.addField("c_first_name","First Name","required");
cForm.addField("c_last_name","Last Name","required");

The problem is, in the addField() function the fields array is being set correct (perhaps because a numerical index is being used to refer to it) but the other 2 arrays (labels and rules) aren't being touched at all. Doing a console.log shows them as empty in firebug.

What do I need to change to make them work? I'd still like to refer to the rules and labels by the string index of the field.

+6  A: 

Use objects instead:

function formBuddy()
{
    var fields = {};
    var labels = {};
    var rules = {};
    var count = 0;

    this.addField = function(field, label, rule)
    {
        fields[count] = field;
        labels[field] = label;
        rules[field] = rule;
        count++;
    }
}


But as Christoph already mentioned, I would store this information in a single data structure too. For example:

function formBuddy() {
    var fields = {};
    this.addField = function(name, label, rule) {
        fields[name] = {
            name: name,
            label: label,
            rule: rule
        };
    };
    this.getField = function(name) {
        return fields[name];
    };
}

var cForm=new formBuddy();
cForm.addField("c_first_name","First Name","required");
cForm.addField("c_last_name","Last Name","required");
alert(cForm.getField("c_last_name").label);
Gumbo
Indeed, javascript has no associative arrays. An associative array is an object.
Pim Jager
If I did rules[field] then (where field is a string containing a field name) would it work?
Click Upvote
Why use Objects when you can simply augment Arrays? -1.
Luca Matteis
Because he probably intended for "labels" and "rules" to be looked-up by field name instead of index?
chakrit
@Luca: because it's bad practice to mis-use arrays as maps? `fields` should be an array because the keys are numeric, `labels` and `rules` should be objects if you want to use non-numeric keys; for another solution, check my answer below
Christoph
@Christoph: that's right, but sometimes you need represent objects in arrays, and given the example of the question, it seems perfectly suitable.@chakrit: They are just normal objects, you can look them up by field name.
Luca Matteis
@Luca - the data is a related set of >1 fields. That's clearly an object, and storing as such allows reuse, a robust association and the possibility of prototyping. There's a reason OO exists and we aren't all still writing C.
annakata
@annakata, why do you and other people seem to not understand that an Array is an object?
Luca Matteis
@Luca: annakata's comment referred to grouping `label` and `rule` together in a single object instead of storing them separately in different arrays
Christoph
@Luca: the problem with using arrays as stores for arbitrarily named properties is that names like `splice`, `push`, ... will shadow the methods with these names; also, if you don't use any array-specific features, why should you use an array instead of a plain objects?
Christoph
A: 

Arrays are treated as Objects in Javascript, therefore your piece of code works, it's just that firebug's console.log isn't showing you the "Objects" inside the array, rather just the array values ...

Use the for(var i in obj) to see what objects values the Array contains:

function formBuddy() {
    var fields = new Array();
    var labels = new Array();
    var rules = new Array();
    var count=0;

    this.addField = function(field, label, rule)
    {        
        fields[count] = field;
        labels[field] = label;
        rules[field] = rule;
        count = ++count;

        for(var i in labels) {
            console.log(labels[i]);
        }
        for(var i in rules) {
            console.log(rules[i]);
        }

        console.log(labels.c_last_name);
        // or
        console.log(labels["c_last_name"]);
    }
}

var cForm = new formBuddy();
cForm.addField("c_last_name","Last Name","required");
Luca Matteis
What i need to do is to be able to retreive the rule of a field. E.g rule=rules[field], or rule=rules.field. That isn't currently working with the arrays code and gives me an error.
Click Upvote
Works for me console.log(labels["c_last_name"]); or console.log(labels.c_last_name);
Luca Matteis
you should check `hasOwnProperty()` when looping over the properties - some time ago, it was fashionable to extend `Array.prototype`
Christoph
+2  A: 

fields should be an array, whereas labels and rules should be objects as you want to use strings as keys. Also, addField() is the same for each instance of FormBuddy() (names of constructor functions should be capitalized) and should reside in the prototype, ie

function FormBuddy() {
    this.fields = []; // this is the same as `new Array()`
    this.labels = {}; // this is the same as `new Object()`
    this.rules = {};
}

FormBuddy.prototype.addField = function(field, label, rule) {
    this.fields.push(field);
    this.labels[field] = label;
    this.rules[field] = rule;
};

You can access the labels/rules via

var buddy = new FormBuddy();
buddy.addField('foo', 'bar', 'baz');
alert(buddy.labels['foo']);
alert(buddy.rules.foo);


Just to further enrage Luca ;), here's another version which also dosn't encapsulate anything:

function FormBuddy() {
    this.fields = [];
}

FormBuddy.prototype.addField = function(id, label, rule) {
    var field = {
        id : id,
        label : label,
        rule : rule
    };

    this.fields.push(field);
    this['field ' + id] = field;
};

FormBuddy.prototype.getField = function(id) {
    return this['field ' + id];
};

var buddy = new FormBuddy();
buddy.addField('foo', 'label for foo', 'rule for foo');

It's similar to Gumbo's second version, but his fields object is merged into the FormBuddy instance. An array called fields is added instead to allow for fast iteration.

To access a field's label, rule, or id, use

buddy.getField('foo').label

To iterate over the fields, use

// list rules:
for(var i = 0, len = buddy.fields.length; i < len; ++i)
    document.writeln(buddy.fields[i].rule);
Christoph
Forgot the prototype?
Luca Matteis
@Luca: Yep - fixed it ;)
Christoph
I think he/she wants the properties to be private, I could be wrong though.
Luca Matteis
@Luca: I never understood why so many people are fond of these so-called 'private' variables: it's inefficient - for each instance, an additional function object holding a reference to the closure over the constructor function's variable object has to be created - and imo not 'in the spirit' of JS
Christoph
@Christoph: you clearly never developed large enough applications in an object oriented environment to not be able to understand `privatization`.
Luca Matteis
@Luca: I'm quite aware of encapsulation and information hiding, but also of the concepts called over-engineering and efficiency; in JavaScript, I'd prefer exposing properties directly over implementing unnecessary getters any day
Christoph
Private in JS is ridiculous - it's just not required in the way it is in build-compiled languages and wastes another few bytes of download time. Definite over-engineering 99% of the time. FWIW, I think the 'field'+ is kind of pointless, I'd just go with id alone
annakata
@annakata: dropping the prefix would enable us to get rid of `getField()`, but it might lead to naming conflicts - what about fields with ids like 'addField'? It's either using a different object as store or adding a namespace prefix; I went with the second one to add confusion to the code ;)
Christoph
btw, if the ids are actual HTML ids, it might be a good idea to use '#' as prefix...
Christoph