tags:

views:

209

answers:

5

Hello everybody, before I ask my question I wanted to let everybody know that I appreciate the fact that there's always somebody out there willing to help, and on my end I'll try to give back to the community as much as I can. Thanks

Now, I would like to get some pointers as to how to properly take advantage of the "for...in" loop in JavaScript, I already did some research and tried a couple things but it is still not clear to me how to properly use it.

Let's say I have a random number of "select" tags in an HTML form, and I don't require the user to select an option for all of them, they can leave some untouched if they want. However I need to know if they selected none or at least one.

The way I'm trying to find out if the user selected any of them is by using the "for...in" loop. For example:

var allSelected = $("select option:selected");
var totalSelected = $("select option:selected").length;

The first variable produces an array of all the selected options. The second variable tells me how many selected options I have in the form (select tags could be more than one and it changes every time). Now, in order to see if any has been selected I loop through each element (selected option), and retrieve the "value" attribute. The default "option" tag has a value="0", so if any selected option returns a value greater than 0, I know at least one option has been selected, however it does not have to be in order, this is my loop so far:

for(var i = 0; i < totalSelected; i++){
  var eachOption = $(allSelected[i]).val();
  var defaultValue = 0;
  if(eachOption == defaultValue){
    ...redirect to another page
  }else if(eachOption > defaultValue){
    ... I display an alert
  }
}

My problem here is that as soon as the "if" matches a 0 value, it sends the user to the next page without testing the rest of the elements in the array, and the user could have selected the second or third options.

What I really want to do is check all the elements in the array and then take the next action, in my mind this is how I could do it, but I'm not getting it right:

var randomValue = 25;  
for(randomValue in allSelected){
  var found = true;
  var notFound = false
  if(found){
    display an alert
  }else{
    redirect to next page
  }
}

This loop or the logic I'm using are flawed (I'm pretty sure), what I want to do is test all the elements in the array against a single variable and take the next action accordingly.

I hope this makes some sense to you guys, any help would be appreciated.
Thanks,
JC

+4  A: 

There are two aspects to your question.

  1. Use "for ... in" only to loop through the property names of objects, and never to loop through arrays (either real arrays, or things that are more-or-less like arrays, such as jQuery objects).

  2. If you're using jQuery, it's counter-idiomatic to do things that way:

    $("select option:selected).each(function() {
      // .. "this" points to each option
    });

If you want to gather up all the settings of all the selects, you could iterate over all the <select> tags, filter out those that are set only to their "default" value, and then collect all the values in a name/value pair array.

Pointy
+1 for jQuery .each()
Ty W
ok, thanks, that makes sense, because I need the value of the "value" attribute of each element and "each()" was really not working, neither the "for in", but know I can see why.Thanks
jnkrois
+12  A: 
T.J. Crowder
Ok, that makes sense. I don't know how many selected options I have to test each time (it changes), but I do know the possible values that can be selected.Would you say that I can hard code an object and then check the selected value against the properties of said object?Thanks
jnkrois
@jnrois: I've updated to address your specific loop. I got a bit...head's down on the Javascript side of the question. :-)
T.J. Crowder
That explains why I was getting screwed up results on my particular javascript. useful to know!
Wayne Werner
`Object.prototype.foo = 1;` is perhaps a better example.
Justin Johnson
Thanks, your code seems to be a lot smarter than mine, I'll try this variation.
jnkrois
@Justin: Not a better example, because extending `Object.prototype` is a very, very bad idea. :-) That's why I went with `Thing`.
T.J. Crowder
It illustrates the idea that unknown properties can be set from unknown locations better. Also, `Object.prototype` is reasonable when not writing a public API.
Justin Johnson
@Justin: Fair 'nuff, but best not to lead the newbie down the garden path. I'd argue that you're much better off defining a `FooObject` as your base object for your private stuff and working off that. Extending `Object.prototype` basically means you can't, *ever*, use any third party *anything* in your code, private or otherwise, because so much code assumes that `for..in` will not show any properties on `{}`. Obviously this changes a bit (and I haven't worked through the implications) with the new 5th edition stuff where you can make your nifty new properties non-enumerable.
T.J. Crowder
@T.J. Crowder That's fair, but I'd hope that libraries were with it enough to check `hasOwnProperty` where necessary.
Justin Johnson
@Justin: Libraries, code snippets from the web, etc., etc. You'd think so, but sadly, not in my experience. :-)
T.J. Crowder
@The-Internet /sadpanda
Justin Johnson
+4  A: 

You should know that the for-in statement in JavaScript is meant to enumerate object properties.

When you want to iterate over an array-like1 object, a sequential loop (for, while, do...while) is always recommended.

Why you shouldn't use for-in for array-like objects:

  • The order of iteration is not guaranteed
  • Inherited properties are also enumerated

See also:

[ 1 ] By array-like I mean any object that contains sequentially numbered properties and a length property.

CMS
Thanks, I'll read up this a little more to learn the basics of it.
jnkrois
+1 One thousand times this! The main difference is between iteration and enumeration. My only suggestion would to be more explicit about the other class of iteration, since there is nothing abnormal about `for..in`. Referring to `for`, `while` and `do..while` as *sequential* or *counting* loops is perhaps more appropriate.
Justin Johnson
@Justin: Agree, *sequential loop* is the more appropriate term. The problem I think is when people come from other languages where there are constructs that *look* similar (e.g. `foreach` from Java or C#)...
CMS
+1  A: 

I'd do it something like this

<html>
<head>
  <title>Test Page</title>
  <script src="http://code.jquery.com/jquery-latest.js"&gt;&lt;/script&gt;
  <script type="text/javascript">

  $(function()
  {
    function checkSelected()
    {
      var DEFAULT = "0";
      var selected = [];

      $("select option:selected").each( function()
      {
        var $option = $(this);
        var optionValue = $option.val(); 
        if ( optionValue !== DEFAULT )
        {
          selected.push( {name: $option.parent().attr( 'name' ), value: optionValue } );
        }
      });

      if ( selected.length )
      {
        $.each( selected, function( index, item )
        {
          alert( item.name + ': ' + item.value );        
        });
      } else {
        alert( 'None selected!' );
      }
    }

    // just for this demo
    $('button').click( function( event )
    {
      event.preventDefault();
      checkSelected();
    })
  });


  </script>
</head>

<body>

<select name="one">
  <option value="0">Zero</option>
  <option value="1">One</option>
</select>

<select name="two">
  <option value="0">Zero</option>
  <option value="1">One</option>
</select>

<button>Test It</button>

</body>
</html>
Peter Bailey
I tested this and it works perfect, I'll study it, so I can understand what you did and the logic behind it. It's funny how sometimes reading code is easier than reading a real explanation.Thanks a lot for your response.
jnkrois
You should note that I used jQuery's each() method for looping, and not the native `for` or `for..in` loops. Although this example certainly could be done with standard `for` loops.
Peter Bailey
Noted, I'll try one or the other.
jnkrois
+1  A: 

A simpler way to write (what I think) your trying to achieve using .each()

// this is to know if we want to redirect
var redir = true;
$("select option:selected").each(function() {
  var val = $(this).val();

  if (val > 0) {
    alert('Found one!');
  }      
  if (val != 0) { 
    redir = false;
    // you can return false here if you want to stop processing the each loop too!
  } 
});

if (redir) {
  window.location = "/nextpage";
}
gnarf
Please forgive my ignorance, but how is "redir" available to me ouside the "each" function, is it because you are declaring it before the each even start?
jnkrois
@jnkrois - Yes, the variable is in the scope of the containing function, and functions inherit their base variables from their containing function.
gnarf
Thanks, I've gotten stuck in the past because of this. I'll try it this way. Many thanks
jnkrois
@jnkrois: Re how `redir` is available within the function passed into `each`: The function is a "closure" that has access to whatever's in scope where it's defined, more: http://blog.niftysnippets.org/2008/02/closures-are-not-complicated.html
T.J. Crowder