1

I'm using a 3rd party javascript library which requires a nested array to be passed to a function. I'm using this function several times, and so storing the array in a variable and passing it to the functions. However, part of the string of 1 element of the array needs to be the current id of the thing I'm passing; I've got something that works that looks a bit like:

myArray = [["foo bar id","some data"],["foo2 bar2 id","some other data"]]

func1(($m1 = myArray.slice(0), 
       $.each($m1, function(i){
              $m1[0] = myArray[i][0].replace("id","1");
       })))

Is it really necessary to make a copy of the array; is there a way of doing it via the $(this) object, or would that not work because that's only in the scope of the .each loop, and doesn't get passed back to func1?

Update

The original answer below doesn't seem to work - the values of the array the 2nd time I reference it are the values given in the first instance:

myArray = [["foo bar id","some data"],["foo2 bar2 id","some other data"]]
func1(

  myArray = myArray.map(function(value)
  {
    value[0] = value[0].replace(/\bid\b/gi, '1');
    console.log(value[0]); //returns "foo bar 1" as expected
    return value;
  });

)

func1(

  myArray = myArray.map(function(value)
  {
    value[0] = value[0].replace(/\bid\b/gi, '2');
    console.log(value[0]); //This returns "foo bar 1", not "foo bar 2"!
    return value;
  });

)

Update (full proper code!)

To put it all into context, the 3rd party library is jsmol, which allows molecular graphics to be displayed. jsmol includes some convenience functions for simple html elements, the one I'm using is Jmol.jmolMenu() - the nested array I've actually got is

arr = [ ["select model=id; color blue", "colour this model blue"],
        ["select model=id; color green", "colour this model green"]
      ]

and then I have n molecular models displayed in the same applet, which jsmol references as 1.1, 2.1, 3.1, ... etc

I have n Jmol.jmolMenus (which just create html select lists); the 1st one to change the colour of 1.1, the second one to change the colour of 2.1 and so on - this is where I need to change the value of the id to the relevant model, and why the replace can only replace the current instance of the current copy - otherwise it makes the lists for the other molecules not work!

In my first menu, I effectively need

Jmol.jmolMenu(jmol,
    [
      ["select model=1.1; color blue", "colour this model blue"],
      ["select model=1.1; color green", "colour this model green"]
    ]
);

and in my second:

Jmol.jmolMenu(jmol,
    [
      ["select model=2.1; color blue", "colour this model blue"],
      ["select model=2.1; color green", "colour this model green"]
     ]
);

I hope this makes a bit of sense, and explains why I need the arrays modifying when I do (which might have seemed weird before!)

ChrisW
  • 4,970
  • 7
  • 55
  • 92

1 Answers1

2

Update:
Seeing as you're dealing with a nested array, my initial answer doesn't quite suffice, but still, it's an easy fix:

var myArray = ["foo bar id","my first array"];
myArray = myArray.map(function(value)
{
    return value.replace('id','1');//replaces only the first occurrence of "id", thouh
});

Why are you using jQuery to alter a standard JS array? Why not use plain vanillaJS? It's faster, shorter and a lot easier to write here:

var myArray = [["foo bar id","some data"],["foo2 bar2 id","some other data"]];
myArray = myArray.map(function(value)
{
    value[0] = value[0].replace(/\bid\b/gi, '1');
    return value;
});

Just map, as before, but treat the value argument as the array that it is. If you need the keys of each sub-array, for example to access the corresponding id in another array:

var ids = ['1', '2'];
myArray = myArray.map(function(value, idx)//second argument is the key
{
    value[0] = value[0].replace(/\bid\b/gi, ids[idx]);
    return value;
});

That's all there is to it.

There is a "gotcha" you might encounter with your current code. As I mentioned in the comment, your replace call will only replace the first occurrence of "id" in the string, not all of them. It'll also replace "id", even if it is part of a word, and the replace call is case-sensitive:

'ID placid id'.replace('id', '1');//ID plac1 id

These issues can be solved by using a regex:

'ID placid id'.replace(/\bid\b/gi, '1');//1 placid 1

the \b in the pattern stands for a word-boundary. the g flag makes the pattern global, all occurrences of id will be replaced. the i flag makes the pattern case-insensitive.

Elias Van Ootegem
  • 74,482
  • 9
  • 111
  • 149
  • Hmm, I don't think I realised I could! – ChrisW Aug 14 '14 at 11:47
  • It's a bit more complicated than I implied - it's actually a nested array which is why I had a `$.each` to begin with; sorry that this wasn't clear – ChrisW Aug 14 '14 at 11:54
  • I've edited the question to reflect that I've got a nested array - I guess my next obvious question is whether the loop through the nested elements should be in the function or outside it - or does it not really matter? – ChrisW Aug 14 '14 at 12:03
  • 1
    @ChrisW: Updated my answer, added some more info, too. Note that I replaced the `.replace('id', '1')` with `replace(/\bid\b/gi, ids[idx])`. My answer explains what that's all about – Elias Van Ootegem Aug 14 '14 at 12:20
  • Thanks, I didn't realise I didn't need a loop! – ChrisW Aug 14 '14 at 12:34
  • @ChrisW: You're welcome... if this answer solved your problem, please mark it as accepted (sorry for nagging). Just a side-note: as an alternative to `Array.prototype.map`, you can use ECMAScript5's `Array.prototype.forEach` function, too (check MDN for the differences) – Elias Van Ootegem Aug 14 '14 at 12:45
  • So, my actual question was meant to be why doesn't modifying `$(this)` work (which I originally tried before copying the array) - is this because of the scope of `.each`? – ChrisW Aug 14 '14 at 12:48
  • 1
    @ChrisW: no, `$(this)` doesn't work, because the callback passed to `$.each` is executed in the context of the element it iterates over (`myArray[0].apply(callback)`), which means that `$(this)` would be the same as writing `$(['foobar id', 'other value'])`, which makes no sense. If the context were a string, then `$('foobar')` would be interpreted as a DOM selector, which would make even less sense. What you could've done in `$.each` was: `this[0].replace`, because `this` would reference the sub-array` (or again, if it was a string: `this.replace`). But the latter wouldn't have worked – Elias Van Ootegem Aug 14 '14 at 12:53
  • [check this answer out](http://stackoverflow.com/a/16472719/1230836) for more details on context bindings of jQ callbacks and scope issues that they might cause – Elias Van Ootegem Aug 14 '14 at 12:54
  • Hmm, sorry to come back to this - I've discovered a slight flaw! I'm sure it's something I've misunderstood but have updated my question – ChrisW Aug 14 '14 at 14:30
  • @ChrisW: `Array.prototype.map` actually applies the callback (the function you pass as argument) to _all_ values in the array, so applying it a second time makes no sense. Besides: all the occurrences of `id` have been replaced with 1, so the second `myArray.map` can't replace anything: `'foo bar 1'.replace(/id/, 2);` <-- there's no match for `/id/` that can be replaced! That's why I added the bit about using the index in the array to refer back to an array of ids, so you can replace the `id` substring with the corresponding id – Elias Van Ootegem Aug 14 '14 at 14:37
  • Ah, I see... so do I really need to manually create a new array with the id values for every time I want to reference it? :/ – ChrisW Aug 14 '14 at 14:41
  • @ChrisW: No, but using `Array.prototype.map`, you can create a copy, just have an id-array, and do something like: `var withIds = myArray.map(function(v, idx) { v[0] = v[0].replace(/\bid\b/gi, ids[idx]); return v;});`. With this, the `myArray` array will not be altered, but instead, you'll have a copy assigned to `withIds`, containing all of the data, but where `id` is replaced with the actual id – Elias Van Ootegem Aug 14 '14 at 14:44
  • Hmm, so what's the advantage in me having to create a whole id array manually over my original code of using `.slice()` to create a copy for me that I then manipulated? – ChrisW Aug 14 '14 at 14:53
  • @ChrisW: I don't know. I don't know what it is you're actually trying to do, where you're doing it, and where the id's are coming from. I just assumed you had all id's at the ready, because your initial code was using `$.each`. If you don't then you don't need `Array.prototype.map`, or `$.each`, but you'd just manipulate those values you need to change. Besides: I'm not saying you should create an id array manually, it could well be fetched using an AJAX request – Elias Van Ootegem Aug 14 '14 at 14:59
  • Argh! I'm really confused :/ you said `Array.prototype.map` creates a copy, but if it creates a copy, why are the values in the original referenced array changed? I don't understand what `.map` is doing. Is it looping through the nested arrays? I'm just a simple chemist trying to get some code working for someone! :( – ChrisW Aug 14 '14 at 15:10
  • @ChrisW: it's quite simple: the values in the original are changed because the values are arrays, Arrays are objects, and thus they are passed around by reference. `Array.prototype.map` does not change the array it maps, but rather returns a copy of that array. Sorry, I was a bit unclear about that. That's because your initial example array wasn't a 2D array, but an array of strings. `.map` does teh same as `$.each`: it iterates an array, and applies a function to each element in that array. Nothing more, nothing less. – Elias Van Ootegem Aug 14 '14 at 15:36
  • PS: If you're trying to get some code working for someone else, there's nothing preventing you from saying _"Your mess, not mine"_ :-P. I'm off now, time to grab something to eat – Elias Van Ootegem Aug 14 '14 at 15:37
  • Ha, it's my job to write code for them tho ;) I've updated my question with the actual context, it might help explain why I need to do what I'm doing :) – ChrisW Aug 14 '14 at 15:44