Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Backbone.js Handling of Attributes That Are Arrays

I really like Backbone, but I am having the hardest time doing what would seem to be simple things. I appreciate any help with the following example.

I have a model, Criteria, that I want to use to store the state of some items in my UI. there are a couple simple attributes, and one attribute that is an array of IDs used to store the IDs of tags the user has selected in the UI.

So, I create a new instance. I add some items to the tags array. Then, I want to start fresh, create a new instance, assigned to the same variable. But, my tags array continues to hold information I added to it as a part of the first instance of Criteria.

I have documented the test case below.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Test</title>
    <script src="Scripts/Libraries/jquery-1.6.1.js" type="text/javascript"></script>
    <script src="Scripts/Libraries/underscore.js" type="text/javascript"></script>
    <script src="Scripts/Libraries/backbone.js" type="text/javascript"></script>

    <script language="javascript" type="text/javascript">

        $(function () {

            // Simple model to hold some state about my UI.
            var Criteria = Backbone.Model.extend({

                defaults: {
                    "status": "Normal",
                    "priority": "Normal",
                    "tags": new Array()
                }

            });

            // Create new criteria.
            window.criteria = new Criteria();

            // The length of the tags array should be 0. PASSES
            console.log("Expect 0: Actual " + window.criteria.get("tags").length);

            // Add a tag id to the tags array.
            window.criteria.get("tags").push(5); // Tag with ID of 5.

            // The length of the tags array should be 1. PASSES
            console.log("Expect 1: Actual " + window.criteria.get("tags").length);

            // Create a new instance of criteria.
            window.criteria = new Criteria();

            // The length of the tags array should be 0. FAILS
            // CONFUSED. I thought this is now a new instance with a new set of attributes.
            // Why does the tags collection still have an item in it.
            console.log("Expect 0: Actual " + window.criteria.get("tags").length);

            // OK. So, I will call the clear method on the model. This is supposed to remove all attributes
            // from the model.
            // Then, I will create it again.
            window.criteria.clear();
            window.criteria = new Criteria();

            // The length of the tags array should be 0. FAILS. Still 1.
            console.log("Expect 0: Actual " + window.criteria.get("tags").length);

            // ARGH!
            console.log("HELP!");

        });

    </script>

</head>
<body>
    <h1>Test</h1>
    <p>Backbone test page.</p>
</body>
</html>

Am I just way off the mark here? Am I trying to use Backbone for things it was not intended? Or am I missing something more general in javascript OO programming?

P.S. I originally used a Backbone collection of tags, but that presented a whole different set of issues relating to having a Tag model referenced in multiple collections and how Backbone's remove method unsets the "collection" reference when an item is removed from any collection. Another day, another issue.

like image 891
Kevin Avatar asked Jun 22 '11 01:06

Kevin


4 Answers

Thom Blake is right about why it's keeping the same values for the array. one option for solving this is to set the default value in the initializer

        var Criteria = Backbone.Model.extend({

            defaults: {
                "status": "Normal",
                "priority": "Normal"
            },

            initialize: function(){
              if( !this.get('tags') ){ 
                this.set({tags: new Array()});
              }
            }

        });
like image 37
Derick Bailey Avatar answered Nov 08 '22 05:11

Derick Bailey


"defaults" can also be a function.

var Criteria = Backbone.Model.extend({
    defaults: function () {
        return {
            "status": "Normal",
            "priority": "Normal",
            "tags": new Array()
        }
    }
});

This would create a new array when a new Criteria is instantiated. See: http://backbonejs.org/#Model-defaults

like image 98
btford Avatar answered Nov 08 '22 06:11

btford


When you define 'tags' under 'defaults', you create a new Array and set that to the default value for that class. Then, when you create a new instance, it has the same Array reference, which still has the things you pushed into it.

Rather than setting a default value for tags, you should be able to just set it to [] before you use it the first time:

window.criteria = new Criteria()
window.criteria.set({'tags', []})  //you can use new Array() if you want
window.criteria.get('tags').push(5)

window.criteria = new Criteria()
console.log(window.criteria.get('tags'))   //should be undefined
window.criteria.set({'tags', []})
like image 13
Tamzin Blake Avatar answered Nov 08 '22 05:11

Tamzin Blake


To be clear, the last option provided by Maksym H. will not solve the problem. The defaults property is provided assuming all of the values set are immutable. An array, however, is mutable, meaning its value can be changed (e.g. tags[0] = "hello" can be changed with tags[0] = "hi there").

By using btford's answer, you are forcing a new instance of any mutable object/property to be created on every new instance of the model, so it is never shared, because the object is created with a function scoped variable.

Similarly, Derick Bailey's answer is correct, it just uses the initialize method instead of the defaults method.

like image 3
Chris M Avatar answered Nov 08 '22 05:11

Chris M