How come I can't lookup associated array like this? - javascript

urlMap = {
'0': {'6b4247404960fd4e418d242f3b7f0412': 'http://google.com', '123':'http://ibm.com'},
'1': {'4c27ffaef99b7a6dbe838b46bcc09779' : 'http://yahoo.com', '456':'http://abc.com'}
};
$(function() {
var l = new Array(1,2,3,4);
for (var i = 0; i < l.length; i++){
$("#"+i.toString()+".foo").change(function() {
g = i.toString();
window.location.href = urlMap[g][$(this).val()];
})}});
I tried to use urlMap[i] it won't work. When I hardcode urlMap['0'] it works.
From Firebug, I see this
urlMap[g] is undefined
[Break On This Error]
window.location.href = urlMap[g][$(this).val()];
How am I suppose to lookup the dictionary?
Thanks.

It's very hard to tell what you're trying to do. You have a map that has two entries, with the keys "0" and "1", but you have a loop that's looping through the values 0, 1, 2, and 3, and then using those values to look up things in the map.
There are at least three problems with the quoted code:
You're trying to access the keys "0", "1", "2", and "3" of an object that only has the keys "0" and "1".
You're creating a function in a loop that's a closure over the variable i, and so will only see the value of i as of later, then the closure is called. This is because closures receive a live reference to the variable, not a copy of its value when they're created. So all of your click handler functions will try to use the key "4" (the value of i after the loop completes), which doesn't exist in your map.
You're not declaring g anywhere, and so falling prey to The Horror of Implicit Globals.
Here's my best guess at what you're actually trying to do:
urlMap = {
'0': {'6b4247404960fd4e418d242f3b7f0412': 'http://google.com', '123':'http://ibm.com'},
'1': {'4c27ffaef99b7a6dbe838b46bcc09779' : 'http://yahoo.com', '456':'http://abc.com'}
};
$(function() {
for (var i = 0; i < 2; i++){
$("#"+i.toString()+".foo").change(createHandler(i));
}
function createHandler(index) {
return function() {
window.location.href = urlMap[index][$(this).val()];
};
}
});
Changes:
I only try to access elements "0" and "1" of the map, since those are the only elements it has.
I use the createHandler function to ensure that the handler we create is closing over the index argument to createHandler, rather than the i value in the loop. The index argument won't change, whereas i will (as I mentioned) change as the loop continues.
I got rid of g, which we don't need, and just use index directly. Property names are always strings (even when the object is an "array"); any time you index into an object, if the index value you supply is a number, it'll get turned into a string, so you don't have to do it explicitly (though you might if you prefer).

As the error clearly states, urlMap[g] does not exist.
You need to create it first.

Related

how do I reference a specific object in array that has changed with angular $watch

I am attempting to calculate the new total whenever price or qty change. I don't understand how to get a reference to the specific object that has changed.
$scope.parts = [{description: null, price: 0, qty: 0, subtotal: null}]
$scope.partsTotal = 0
$scope.$watch('parts', =>
$scope.partsTotal += $scope.parts.price * $scope.parts.qty
$scope.parts.subtotal = $scope.parts.price * $scope.parts.qty
, true)
Create a new watch on each object in the array, and then use watch with the first parameter being a function returning the object (I'm using underscorejs to iterate over the array):
UPDATE: you might lost the reference using _.each(), so if that doesn't work try a for loop...
for(var i=0; i<$scope.parts.length; i++) { var part = $scope.parts[i]; ... }
_.each($scope.parts, function(part) {
$scope.$watch(
function() { return part; },
function(newVal, oldVal) {
if(newVal.price !== oldVal.price || newVal.qty !== oldVal.qty) {
//run the update
}
}
);
});
You have an object literal inside of an array literal.
You would have to access the "subtotal" property in your example like this:
$scope.parts[0].subtotal
$watch(es) get the new values and the old values passed in when they fire. I would try to reproduce with your example but I can't bring myself to write it with Coffee. You can grab the values from the arguments going in, or you can access the object directly like you are there (which might be easier considering you're using a deep watch).

Javascript for...in Loops in Chrome Sample Extension

I was editing Chrome's sample oauth contacts extension
when I came across an interesting for-loop in line 7 of contacts.js:
for (var i = 0, contact; contact = contacts[i]; i++) {
variable i was never used in the body of the for loop, so it seemed like a typical "for...in" loop. I tried replacing the for-loop with
for (contact in contacts) {
but when I ran the extension, all my contacts came back undefined
Here is the full for-loop from the extension
for (var i = 0, contact; contact = contacts[i]; i++) {
var div = document.createElement('div');
var pName = document.createElement('p');
var ulEmails = document.createElement('ul');
pName.innerText = contact['name'];
div.appendChild(pName);
for (var j = 0, email; email = contact['emails'][j]; j++) {
var liEmail = document.createElement('li');
liEmail.innerText = email;
ulEmails.appendChild(liEmail);
}
div.appendChild(ulEmails);
output.appendChild(div);
}
What the code given does
What that does is evaluate what contacts[i] is and whether it is truthy or not, while at the same time caches the array element of the applicable index.
It's equivalent to the following code (note that in this example ++i has the same side effect as i++):
for (var i = 0; contacts[i]; ++i)
{ var contact = contacts[i];
// use contact
}
This could be interpreted as something like the following:
If !contacts[i] is false (i.e. it is truthy) continue the loop.
Otherwise, end the loop (it is falsy).
If the goal of that code was to iterate through all of an array, the problem would be that if you wanted to iterate through an element but it was falsy, it would end the entire loop instead of performing the (likely) intended effect. Take this example:
var foo = [1, 3, 5, 7, 9, 0, 2, 4, 6, 8];
// example for-loop given
for (var i = 0; foo[i]; ++i)
{ var bar = foo[i];
console.log('example: ' + bar);
}
// "normal" way of iterating through array
for (var i = 0, l = foo.length; i < l; ++i)
{ var bar = foo[i];
console.log('normal: ' + bar);
}
You'd find that the example only logs up to the number 9, while the "normal" way goes through the entire array. Of course though, if you could guarantee that all values within the array would be truthy (e.g. all array elements are objects), then this isn't that much of an issue.
What for-in does and why it doesn't work
You tried to replace that code with the following:
for (contact in contacts) { /*code here*/ }
However, this doesn't work for a number of reasons:
contact is a string of the property name, not the value of it. Take this example:
var foo =
{ bar1: 1
, bar2: 2
, bar3: 3
, bar4: 4
, bar5: 5 };
for (var i in foo) console.log(i);
What you get back is the property name (i.e. "bar1, bar2...") instead of the value. To do so for an object, you'd have to do something like the following:
for (var i in foo)
{ var bar = foo[i];
console.log(bar);
}
Now you should get back "1,2,3,4,5" on separate lines. If you got that, and some other things, you might be have defined items on Object.prototype - which is why it's generally a bad idea, unless it really makes the code cleaner, and there is a substantial purpose for doing so. To filter these out, add a hasOwnProperty() check:
for (var i in foo) if (foo.hasOwnProperty(i))
{ var bar = foo[i];
console.log(bar);
}
The upcoming version of ECMAScript (the "standard" version of JavaScript, minus the DOM) will have something called for-of loops, which will make it easier to do this sort of thing.
For-in loops generally aren't meant for arrays (it is possible, but it's just not a good idea usually). If you need to use for-in, you probably should be using an object instead - all arrays are objects, just that arrays have special internal length property and a few other things.
contact is an implied global, big no-no. In fact, implied globals are banned in strict mode. Use a variable declaration (inside or outside the for-in loop, doesn't matter) to solve this issue.
It's just learning about how JavaScript works and where to apply its various methods of doing things - some are more suitable than others in particular situations.
Here you are using an array,not an object.
Though using for..in outputs the same result as a normal for loop,this would be my answer.
MyRecommendation:
Use for..in for iterating over objects:
for..in iterates over properties of an object.
Note:the order of iteration is arbitary.
var Myobj = {
a: 1,
b: 2,
c: 3
};
for ( var prop in Myobj ) {
console.log(prop); // a...b...c
console.log(Myobj[prop]); // 1...2...3
}
but with this the problem is it will continue searching for enumerable properties up the prototype chain.So unless you dont use hasOwnProperty,it will iterate over local object and the prototype it is attached to.
//Improved version of above code:
for (var prop in Myobj) {
if ( Myobj.hasOwnProperty(prop) ) {
// prop is actually obj's property (not inherited)
console.log(prop); // a...b...c
console.log(Myobj[prop]); // 1...2...3
}
}
Use for loop for iteration over an array
for loop iterates over an array in sequential way.

How Do I Create Variable Object Keys Of Object Keys Of Objects?

Okay, so I have a search results array of objects where one of the object properties value (show value) matches a search. The structure of this array is as follows and may contain any number of different objects:
results = [
{
day: value,
time: value,
show: value,
sid: value,
network: value,
title: value,
ep: value,
link: value,
}
];
I am trying to consolidate all the results into one large object, merging any days or times that have the same value. However, I cannot simply look at each day or time value independently. For example, I need to retain 9:00 pm on Monday if there is a 9:00 pm on Tuesday as well.
To do this I am trying to create a new object structure like so:
for ( var i=0; i<results.length; i++ ) {
var uniqtime = results[i]["time"];
var uniqshow = results[i].show;
uniqresults[results[i].day] = {
uniqtime: {
uniqshow: {
sid: results[i].sid,
network: results[i].network,
title: results[i]["title"],
ep: results[i].ep,
link: results[i]["link"]
}
}
};
}
but obviously this won't work since the variable sub-object key names are treated as strings.
If I instead try to create the variable sub-objects/keys like so:
for ( var i=0; i<obj.length; i++ ) {
uniqresults[results[i].day] = {};
uniqresults[results[i].day][results[i]["time"]] = {};
uniqresults[results[i].day][results[i]["time"]][results[i].show] = {
sid: obj[i].sid,
network: results[i].network,
title: results[i]["title"],
ep: results[i].ep,
link: results[i]["link"]
};
}
I can indeed create the proper key names but I am forced to declare an empty object to define each key (uniqresults[obj[i].day] = {} & uniqresults[obj[i].day][obj[i]["time"]] = {}). If I don't declare like this it won't let me insert the other sub-keys/values that I need to. However, declaring like this doesn't allow me to merge my results arrays correctly since I am essentially emptying out the sub-key names each time I read a new result object!
Maybe I am making this more complicated than it should be. Maybe there is an easier way or a way underscore or jquery could simplify my task at hand. Regardless, I am quite desperate for a proper solution at the moment.
It seems like you could use a conditional check when redifining those objects.
var day, time;
for ( var i=0; i<obj.length; i++ ) {
// Instantiate the day reference if it doesn't exist
day = uniqresults[results[i].day] = uniqresults[results[i].day] || {};
// Instantiate the time reference if it doesn't exist
time = day[results[i].time] = day[results[i].time] || {};
time[results[i].show] = {
sid: obj[i].sid,
network: results[i].network,
title: results[i]["title"],
ep: results[i].ep,
link: results[i]["link"]
};
}
Cheers!
I don't know whether there's a neater solution to the wider problem, but the issue of overwriting the sub-objects each time can be solved by checking if they already exist before creating them.
A relatively compact and idiomatic way of doing this in JS is using the || operator, which unlike most languages returns the argument which evaluated to true, not simply a boolean true:
uniqresults[results[i].day] = uniqresults[results[i].day] || {};
The first time through, uniqresults[results[i].day] will be undefined, so evaluate to false, so {} will be assigned; subsequently, however, it will be an object, which evaluates to true, so will simply assign the variable to itself, leaving it unchanged.
Create an empty object only if the object's key does not exist:
if(!(results[i].day in uniqresults)){
uniqresults[results[i].day] = {};
}
And for sub keys so on.

Two Dimensional Array in Javascript Object

I want to create an Object that contains one or more two dimensional arrays in Javascript.
I tried it the following way (in this example I only try to add one two dimensional array):
var XSIZE = 8;
var YSIZE = 8;
var obj = {
field : new Array(XSIZE),
field[0] : new Array(YSIZE),
foo : 1,
bar : 100
}
Info:
- This gives me a strange error "missing : after property id" which does not seem to make much sense
- Unfortunately I didn't find examples showing how to do this so far by using google
- If I don't add field[0] ... for creating the 2nd array it works.
- changing the XSIZE and YSIZE to numbers like new Array(8)... doesn't work.
I would really appreciate if somebody could show me how to do it or explain why I cannot do this at all and need to use some other method.
Thanks a lot!
The error "missing : after property id" is because JavaScript sees the field part of field[0] and expects a colon before the value of that field. Instead it gets an open bracket so it complains.
You can't hard code an object definition that has its dimensions set up at run time. You have to build the object at run time as well. Like this perhaps
var XSIZE = 8;
var YSIZE = 8;
var obj = {
field : new Array(),
foo : 1,
bar : 100
}
for (var i = 0; i < XSIZE; i++) {
obj.field.push(new Array(YSIZE));
}
In object literal notation, the property names must be exactly that: property names. Firstly, field[0] isn't a property name. Secondly, the properties don't exist until the after the object defined, so you can't access properties until then.
What you should do is either set the array after the object is created:
var obj = {...}
obj.field[0] = [...];
or nest the array literals:
var obj = {
field: [ [...],
...
],
...
}
You don't need to worry about setting the array size when creating the array, as it will grow when you add elements.
You can only declare properties on the object being constructed that way; not on objects in another "level".
You could use a for loop instead:
for(var i = 0; i < XSIZE; i++) {
obj.field[i] = new Array(YSIZE);
}
Note that the YSIZE is not necessary since an empty array works just fine as well ([]).
You could get the two dimensional array as your obj property, without resorting to external procedures and keep everything internal to the object. Create your empty 'field' array 1st.
var obj = {
field:[],
foo:1,
bar:100
};
Now, create an object's method to create a two dimensional array off your initial dimensionless array. You can determine the length and the number of dimensions of multi dimension array as you wish at run time:
var obj = {
field:[],
multifield:function(x,y){for (var count=0;count<x;count++) {this.field[count]=new Array(y);}},
foo:1,
bar:100
};
You can then call the obj.multifield method entering whatever dimensions you decide:
obj.multifield(10,5); //-->create a 10x5 array in this case...
console.log(obj.field.length); // 10
console.log(obj.field[0].length); // 5

Data-structure to store "last 10" data sets?

I'm currently working with an object Literal to store temporary information to send to clients, it's like a history container for the last 10 sets of data.
So the issue that I', having is figuring out the most efficient way to splice on object as well as push an object in at the start, so basically i have an object, this object has 0 data inside of it.
I then insert values, but what I need to do is when the object reaches 10 keys, I need to pop the last element of the end of object literal, push all keys up and then insert one value at the start.
Take this example object
var initializeData = {
a : {},
b : {},
c : {},
d : {},
e : {},
f : {},
g : {},
h : {},
i : {},
j : {}
}
When I insert an element I need j to be removed, i to become the last element, and a to become b.
So that the new element becomes a.
Can anyone help me solve this issue, I am using node.js but native JavaScript is fine obviously.
Working with arrays after advice from replies, this is basically what I am thinking your telling me would be the best solution:
function HangoutStack(n)
{
this._array = new Array(n);
this.max = n;
}
HangoutStack.prototype.push = function(hangout)
{
if(this._array.unshift(hangout) > this.max)
{
this._array.pop();
}
}
HangoutStack.prototype.getAllItems = function()
{
return this._array;
}
Sounds like it would be a lot easier to use an array. Then you could use unshift to insert from the beginning and pop to remove from the end.
Edit: Example:
var items = []
function addItem(item) {
items.unshift(item)
if (items.length > 10) {
items.pop()
}
}
Alternatively, you could use push/shift instead of unshift/pop. Depends on which end of the array you prefer the new items to sit at.
What you need is called a Circular Buffer.
Have a look at this implementation in javascript
Yeah, use an Array.
var arr = [];
// adding an item
if (arr.unshift(item) > 10) {
arr.pop();
}
If you need the 'name' of the item, like "a" or "b" in your object example, just wrap each item in another object that contains the name and the object.
Objects in js are like dictionaries -- they have no inherent order to the items. It's just a collection of things. You can try to make up an order (like a through z in your example), but then you have to manage that order yourself when things change. It's just much easier to use an array.

Categories

Resources