2

I'm moving items from one array to another as long as it doesn't already exist, then storing that in localStorage

var queue = [];

function moveToQueue(item) {
    if(queue.indexOf(item) == -1) {
        queue.push(item);
        store();
    }
}

function store() {
    localStorage.myApp_queue = JSON.stringify(queue);
}

When the page loads, I call my load function.

function load() {
    if(typeof localStorage.myApp_queue != 'undefined') {
        queue = JSON.parse(localStorage.myApp_queue);
    }
}

At this point, my interface shows the queue as I had last left it, but if I try to add the same item again, the "queue.indexOf(item)" is failing. The contents of queue is not the same as it was before the store()/load() methods were called.

Here are some console.log() calls before and after my page reload, starting with an empty queue. The logs have been added to the start of the moveToQueue function

function moveToQueue(item) {
    console.log('adding to queue');
    console.log(item);
    console.log(queue[0]);
    console.log(queue.indexOf(item));
    ...

Initial load:

adding to queue
e {id: 1, status: "A", title: "Comcar", $$hashKey: "004", $get: function…}
undefined
-1

adding to queue
e {id: 1, status: "A", title: "Comcar", $$hashKey: "004", $get: function…}
e {id: 1, status: "A", title: "Comcar", $$hashKey: "004", $get: function…}
0 

After page refresh - the Item I'm pushing is already in the queue array

adding to queue
e {id: 1, status: "A", title: "Comcar", $$hashKey: "006", $get: function…}
Object {id: 1, status: "A", title: "Comcar", $$hashKey: "004"}
-1

What have I missed here, what is localStorage and JSON doing to change my array.

I can see the log lists the item returned from storage as "Object", whereas the original item has a "type" (not quite sure about that) of "e".

If I use typeof the result for both is "object"

Gagravarr
  • 47,320
  • 10
  • 111
  • 156
Pete
  • 4,542
  • 9
  • 43
  • 76
  • Aah I've just realised something. The original items have getters, setters etc, they seem to be real javascript Objects. The ones that have been parsed in/out of localStorage are just simple dictionaries/key value pairs! So I need to stop them being complex objects? I think – Pete Feb 19 '13 at 12:33
  • I made changes in my code, now it will work for complex objects too :-) – Minko Gechev Feb 19 '13 at 12:58

2 Answers2

3

indexOf will compare the values by reference if they are objects, so your code won't work for complex objects (i.e. hashes, dictionaries or whatever). You can make your code work even for objects if you use function which checks the objects whether they are equal. You can find such function here: Object comparison in JavaScript

In the next sections I've put sample code how you can:

  1. Load and save arrays/objects using the localStorage.
  2. Make your code work for primitives.
  3. Make your code work for complex objects.

Load and save in the localStorage

//Saving array with specific "name" (key for the localStorage)
function saveArray(name, array) {
   localStorage.setItem(name, JSON.stringify(array));
}

//Loads specific array and deserialize it.
function loadArray(name) {
   return JSON.parse(localStorage.getItem(name));
}

For primitives:

For primitives you can achieve the wanted functionality pretty easy (your code is correct for this kind of data):

//This method adds unique elements, which are not already in the array
Array.prototype.addUnique = function (item) {
   if (this.indexOf(item) < 0) {
      this.push(item);
   }
};

var array = [];
array.addUnique(1);
array.addUnique(2);
array.addUnique(1);
array.addUnique(3);
//The current content of the array is [1,2,3] because of the addUnique functionality
saveArray('myarray', array);

When you reload the page use:

var array = loadArray('myarray'); //[1,2,3]
array.addUnique(6);
saveArray('myarray', array);

Now in your localStorage the value of array will be [1,2,3,6].

For objects

If you have more complex data in your array, like key-value pair objects use:

//Code from here:
//https://stackoverflow.com/questions/1068834/object-comparison-in-javascript
Object.prototype.equals = function(x) {
  var p;
  for(p in this) {
      if(typeof(x[p])=='undefined') {return false;}
  }

  for(p in this) {
      if (this[p]) {
          switch(typeof(this[p])) {
              case 'object':
                  if (!this[p].equals(x[p])) { return false; } break;
              case 'function':
                  if (typeof(x[p])=='undefined' ||
                      (p != 'equals' && this[p].toString() != x[p].toString()))
                      return false;
                  break;
              default:
                  if (this[p] != x[p]) { return false; }
          }
      } else {
          if (x[p])
              return false;
      }
  }

  for(p in x) {
      if(typeof(this[p])=='undefined') {return false;}
  }

  return true;
}

Array.prototype.exists = function (item) {
   var exists = false;
   this.forEach(function (e) {
      if (e.equals(item)) {
         exists = true;
         return;
      }
   });
   return exists;
};

Array.prototype.addUniqueObject = function (item) {
    if (!this.exists(item)) {
       this.push(item);
    }
};

var array = [];
array.addUniqueObject({ foo: 'bar' });
array.addUniqueObject({ foo: 'baz' });
array.addUniqueObject({ foo: 'bar' });
array.addUniqueObject({ foo: 'foobar' });
//The current content of the array is [{ foo: 'bar' }, { foo: 'baz' }, { foo: 'foobar' }] because of the addUniqueObject functionality
saveArray('myarray', array);

When you reload your page you can get array with the same content using:

loadArray('myarray');
//[{ foo: 'bar' }, { foo: 'baz' }, { foo: 'foobar' }]

NOTE

Like the methods equals is added to Object.prototype so every object which you create will have this method! The same is for each array which will have addUnique, addUniqueObject and equals.

If you don't want this you can simply refactor a little these methods.

Community
  • 1
  • 1
Minko Gechev
  • 25,304
  • 9
  • 61
  • 68
2

What have I missed here, what is localStorage and JSON doing to change my array?

It does not change your array (your old one was lost when unloading the page). It creates a totally new one, with totally new item objects. If you now create items that look exactly like those you already have, they are still two distinct objects and comparing them by reference (as indexOf with === does) will yield false.

So either you will need to change your construction function that no new ones are built but old ones are reused, or you will need to use a special comparison function that tells object equality by their ids for example.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Ok, that makes sense. The object itself is different, even if the properties inside are the same. – Pete Feb 19 '13 at 13:44