Incorporating localStorage in a more complicated scenario (save multiple arrays) - javascript

I'm trying to implement local storage into my program.
Initially, the program was simple and had only 1 array to save. That array served as the container and the container then fed to #1 - the display output and to #2 - the console output...
old simpler program
pseudo-code to make the red box go away...
The above link eventually worked - as you can see on line 54 of the JS, as soon as a new entry is entered into the input box, the updated container was then saved to localStorage. And then upon returning to the site, per line 11 - if upon the initial loading of the program the saved localStorage container doesn't match the initial array, then the saved container is used.
However, I've changed the program drastically so that there are now multiple options to choose from and thus multiple arrays. Whichever option/array is chosen becomes the main container, and the main container still feeds out to the display output and the console output. See the updated program below...
multiple arrays = more complicated program
pseudo-code
The problem is that I am now having a difficult time figuring out how to implement localStorage on this new updated program. It was much easier previously when I only had 1 array to test against. Now, I can't just save the container any time a new entry is inputted (or removed) because the container can be any 1 of the 6 options.
Does anyone have any general ideas/pointers/suggestions as to what I can do?

The easiest (maybe not the most efficient) option would be to use an array of arrays. If before you had:
var container = ["a", "b", "c"];
localStorage.setItem("container", JSON.stringify(container));
You'd now have:
var container = [
["a", "b", "c"],
["d", "e", "f"]
];
localStorage.setItem("container", JSON.stringify(container));
Then for getting a single one in your new code, you'd just do:
var container = JSON.parse(localStorage.getItem('container')); //The same as before
var firstItem = container[0];
Hope this helps. Cheers

Why not just create a JSON object with multiple arrays, and save the entire stringified object in a localStorage variable instead of that single-dimensional array. It could even have a parameter referencing the selected container, and could be structured something like:
var containers = {
selectedContainer: "container1",
container1: ["text","contents","of","container1"],
container2: ["text","contents","of","container2"],
container3: ["text","contents","of","container3"],
container4: ["text","contents","of","container4"],
container5: ["text","contents","of","container5"],
container6: ["text","contents","of","container6"]
}
Content could then be pushed and popped to each of these container arrays, and the entire object saved in the single localStorage variable:
containers.container1.push("additional contents");
localStorage['containers'] = JSON.stringify(containers);
obj = JSON.parse(localStorage['containers']);
You could then reference the selected array with the selectedContainer parameter:
var container = (obj[obj.selectedContainer])

Related

Managing/saving a (nested?) 2d data array with Angular

Let's say I have the following arrays:
var a = ["Sample A", "Sample B"];
var b = ["X", "Y", "Z"];
Now let's say I have a button that, when clicked, generates the header of a 2D table, like this:
Sample ASample BXYZ
...and another button that adds a new row to the table:
Sample ASample BXYZ
How would I do the following things:
save/restore data from/to the table
manage insertions/deletions from any of the arrays that form the header
Notes:
the elements of the arrays that form the header can be deleted in any order, but new elements can be appended at the end.
... however, I expect specs to change slightly so that the elements in those arrays can change their position.
EDIT: As suggested by users, I'm adding a plunkr example: http://plnkr.co/edit/FMIgwqKKDbZOCjNTXrrh?p=preview
I know this is flawed and wrong because it's tracking the data by it's index (which is error prone).

Is there a way to map a value in an object to the index of an array in javascript?

Prepending that a solution only needs to work in the latest versions of Chrome, Firefox, and Safari as a bonus.
-
I am trying to use an associative array for a large data set with knockout. My first try made it a true associative array:
[1: {Object}, 3: {Object},...,n:{Object}]
but knockout was not happy with looping over that. So I tried a cheating way, hoping that:
[undefined, {Object}, undefined, {Object},...,{Object}]
where the location in the array is the PK ID from the database table. This array is about 3.2k items large, and would be iterated over around every 10 seconds, hence the need for speed. I tried doing this with a splice, e.g.
$.each(data, function (index, item) {
self.myArray.splice(item.PKID, 0, new Object(item));
}
but splice does not create indices, so since my first PKID is 1, it is still inserted at myArray[0] regardless. If my first PK was 500, it would start at 0 still.
My second thought is to initialize the array with var myArray = new Array(maxSize) but that seems heavy handed. I would love to be able to use some sort of map function to do this, but I'm not really sure how to make the key value translate into an index value in javascript.
My third thought was to keep two arrays, one for easy look up and the other to store the actual values. So it combines the first two solutions, almost, by finding the index of the object in the first example and doing a lookup with that in the second example. This seems to be how many people manage associative arrays in knockout, but with the array size and the fact that it's a live updating app with a growing data set seems memory intensive and not easily manageable when new information is added.
Also, maybe I'm hitting the mark wrong here? We're putting these into the DOM via knockout and managing with a library called isotope, and as I mentioned it updates about every 10 seconds. That's why I need the fast look up but knockout doesn't want to play with my hash table attempts.
--
clarity edits:
so on initial load the whole array is loaded up (which is where the new Array(maxLength) would go, then every 10 seconds anything that has changed is loaded back. That is the information I'm trying to quickly update.
--
knockout code:
<!-- ko foreach: {data: myArray(), afterRender: setInitialTileColor } -->
<div class="tile" data-bind="attr: {id: 'tileID' + $data.PKID()}">
<div class="content">
</div>
</div>
<!-- /ko -->
Then on updates the hope is:
$.each(data.Updated, function (index, item) {
var obj = myModel.myArray()[item.PKID];
//do updates here - need to check what kind of change, how long it's been since a change, etc
}
Here is a solution how to populate array items with correct indexes, so it doesn't start from the first one (0 (zero) I meant)
just use in loop
arr[obj.PKID] = obj;
and if your framework is smart (to use forEach but not for) it will start from your index (like 500 in case below)
http://jsfiddle.net/0axo9Lgp/
var data = [], new_data = [];
// Generate sample array of objects with index field
for (var i = 500; i < 3700; i++) {
data.push({
PKID: i,
value: '1'
});
}
data.forEach(function(item) {
new_data[item.PKID] = item;
});
console.log(new_data);
console.log(new_data.length); // 3700 but real length is 3200 other items are undefined
It's not an easy problem to solve. I'm assuming you've tried (or can't try) the obvious stuff like reducing the number of items per page and possibly using a different framework like React or Mithril.
There are a couple of basic optimizations I can suggest.
Don't use the framework's each. It's either slower than or same as the native Array method forEach, either way it's slower than a basic for loop.
Don't loop over the array over and over again looking for every item whose data has been updated. When you send your response of data updates, send along an array of the PKIds of the updated item. Then, do a single loop:
.
var indexes = []
var updated = JSON.parse(response).updated; // example array of updated pkids.
for(var i=0;i<allElements.length;i++){
if(updated.indexOf(allElements[i].pkid)>-1)
indexes.push(i);
}
So, basically the above assumes you have a simple array of objects, where each object has a property called pkid that stores its ID. When you get a response, you loop over this array once, storing the indexes of all items that match a pk-id in the array of updated pk-ids.
Then you only have to loop over the indexes array and use its elements as indexes on the allElements array to apply the direct updates.
If your indexes are integers in a reasonable range, you can just use an array. It does not have to be completely populated, you can use the if binding to filter out unused entries.
Applying updates is just a matter of indexing the array.
http://jsfiddle.net/0axo9Lgp/2/
You may want to consider using the publish-subscribe pattern. Have each item subscribe to its unique ID. When an item needs updating it will get the event and update itself. This library may be helpful for this. It doesn't depend upon browser events, just arrays so it should be fairly fast.

Passing Location in Object as Variable

I have a large object, mixed with arrays of data (it's a treeview of folders and images - I have no control over what is outputted here.
For example:
var test = {
Folders: [{
Folders:[{
Folders:[{
Folders:[
{value:1},{value:2}
]
}]
}]
},{}
]
}
Value 1 (which in my case is an image) can be found here:
test.Folders[0].Folders[0].Folders[0].Folders[0].value
My end users are using a drop down to select their folder, I need to somehow pass the location via the drop down.
I've tried adding the "path" to the data-attr attribute of the drop down:
<option value="folder6" data-attr="[0].Folders[0].Folders[0].Folders[0]">Folder6</option>
However attempting to use that like this:
var myLocation = $('#element').find('option:selected').attr('data-attr');
//myLocation now is a string "Folders[0].Folders[0].Folders[0].Folders[0].value"
console.log(test[myLocation]
Doesn't work (it's down to the Arrays and numbers as passing a string as an object location in this fashion normally works).
I'm quietly confident I'm going about this the wrong way fullstop. I'm open to ideas on how better to do this in general, or how to get this horrible fudge to work.
There are many ways to solve this issue and it mostly depends on your needs and global architecture.
#1 The Evil Way (using eval)
var path = '[0].Folders[0].Folders[0].Folders[0]';
eval('test.Folders' + path); //Object {value: 1}
#2 Use an object as a map to index every folder
var foldersMap = {};
//loop over your tree and build the index
foldersMap[path] = folder;
//then retrieve it later
foldersMap[path];
#3 Store the object on the option directly
//while building the option
optionEl.folder = folder;
//then later retrieve it from the selected option
yourSelect.options[yourSelect.selectedIndex].folder;
#4 Create your own keypath function that can traverse an object structure based on a string keypath rather than using eval. I will provide an implementation as soon as I have more time.
There are probably many other ways, but these are just ideas.
Conceptually a tree can be n-levels deep. Baking in the path like that may not be a good ideas unless you know how "deep" the structure will be ahead of time.
It looks like you might be trying to show each potential folder in a drop down list (or maybe just the ones with images).
To do this, I would create a function to recursively loop through your json structure. The result of this function would either directly build the drop down menu or flatten the values into an array which could then be bound to the drop down menu. I would use a front-end templating library like handlebar.js, but you could also manipulate a dropdown control via JQuery.
Try storing it as a JSON array in data-attr. jQuery will pull it out as an array. Then you just need to find a way to loop it. Here's my first crack at it (with no validation and exception handling)...
HTML:
<select>
<option data-attr="[0,0,0,0]">Foo</option>
</select>
JavaScript:
var test = {
Folders: [{
Folders: [{
Folders: [{
Folders: [{
value: 1
}, {
value: 2
}]
}]
}]
}, {}]
};
var folderIndexes = $("option").data("attr"); // an array
alert(getValue(test, folderIndexes));
function getValue(folders, folderIndexes) {
for (var i = 0; i < folderIndexes.length; i++) {
folders = folders.Folders[folderIndexes[i]];
}
return folders.value;
}
Fiddle... http://jsfiddle.net/atn22/

How do I use #DbLookup results to populate a Readers field in xpages?

db = new Array("myserver", "myfolder\\mydb.nsf")
dir = getComponent("Dir").value;
div = getComponent("Div").value;
lu = #DbLookup(db, "ManagerAccess", dir + "PP" + div, "DTManagers");
var a = [];
a.push(lu);
var item:NotesItem = docBackEnd.replaceItemValue('FormReaders', #Unique(a));
item.setReaders(true);
That code is on the querySaveDocument ssjs. The result I get from the #DbLookup (when I put in a computed field) look like this:
Pedro Martinez,Manny Ramirez,David Ortiz,Terry Francona
I tried doing an #Explode(#Implode) thing on it, but it doesn't seem to work.
The error I get in the browser just tells me that the replaceItemValue line is broken.
To test it, I pushed several strings one at a time, and it worked correctly populating my FormReaders field with the multiple entries.
What am I doing wrong?
I see several problems here:
A. In cases as described by you #Dblookup in fact would return an array. If you push an array into a plain computedField control it will exactly look as that you wrote:
value1, value2, ..., valueN
A computedField doesn't know anything about multiple values etc, it just can display strings, or data that can be converted to strings.
If you want to test the return value you could try to return something like lu[0]; you then should receive the array's 1st element, or a runtime error, if lu is NOT an array. Or you could ask for the array's size using lu.length. That returns the number of array elements, or the number of characters if it's just a plain string.
B. your code contains these two lines:
var a = [];
a.push(lu);
By that you create an empty array, then push lu[] to the first element of a[]. The result is something like this:
a[0] = [value1, value2, ..., valueN],
i.e. a is an array where the first element contains another array. Since you don't want that, just use #Unique(lu) in your replaceItemValue-method.
C. I don't see why replaceItemValue would throw an error here, apart from what I wrote in topic B. Give it a try by writing lu directly to the item (first without #Unique). That should work.
D. for completeness: in the first line you used "new Array". A much better way to define your db parameters is
var db = ["myserver", "myfolder/mydb.nsf"];
(see Tim Tripcony's comment in your recent question, or see his blog entry at http://www.timtripcony.com/blog.nsf/d6plinks/TTRY-9AN5ZK)

Changing the variables of a class in OOP in javascript

I have defined a function called Node which stores the properties of nodes in a graph data structure. The function is something like this:
function Node(){
...
this.outEdges = [];
this.inEdges = [];
...
}
where the inEdges and outEdges store elements of type Edge which is another function I have defined. During the program these arrays are filled with elements.
At some point in my code I need to reset these two arrays so I write:
nodes[i].outEdges.length = 0;
nodes[i].inEdges.length = 0;
where nodes is an array of elements of type Node and I am accessing an element in a for loop.
The problem is, after setting outEdges and inEdges to 0, I expected them to be [] in the nodes[i] property list. However, when I output nodes[i] into console, the outEdges and inEdges still have the elements in them. The stranger thing is that when I output nodes[i].outEdges to console, it prints [] , which is correct, but clicking on [ ] again opens the list of the elements! I can't really figure out why the nodes[i] variables don't change?
That happens (probably) because the browser prints out the empty array but by the time you check it, it has content again. So when you click to expand the browser shows the actual content.
As you can see the values [1,3,7] were added after the command console.log(o) but they are shown on the screen (even though the length shown is 0).
You're not supposed to set the length field. Just re-initialize them:
nodes[i].outEdges = [];
nodes[i].inEdges = [];
Edit: My bad, setting the length should work. It does work for me on Chrome at least. However, I still think it's safer and better style to re-init.
Just create a new object with the same name
nodes[i].outEdges = new Array();

Categories