3

I have a couple of lines of code in JQuery:

     var central = $('#townid option:contains("Central")');
     if (central.length){
        central.insertAfter('select option:first-child');
     }

How can I rewrite it without using JQuery library just with JavaScript?

kamaci
  • 72,915
  • 69
  • 228
  • 366

1 Answers1

6

A correct translation would be something like:

var selects = document.getElementsByTagName('select'),
    options = document.getElementById('townid').getElementsByTagName('option'),
    options = Array.prototype.slice.call(options), //2 lines only for readability
    tmp = document.createDocumentFragment();

for(var i = 0, l = options.length; i < l; i++) {
    var option = options[i],
        text = option.innerText || option.textContent;
    if(text.indexOf('Central') > -1) {
        tmp.appendChild(option);
    }
}

for(var i = 0, l = selects.length; i < l; i++) {
    var select = selects[i],
         opts = select.getElementsByTagName('option');
    if(opts.length > 1) {
        select.insertBefore(tmp.cloneNode(true), opts[1]);
    }
    else {
        select.appendChild(tmp.cloneNode(true));
    }
}

DEMO

This could be simplified a lot depending on the markup (and optimized depending on the browser (e.g. support for querySelectorAll)). E.g. if you know that there will always only be one option that contains "Central" and whether there exists only one select element or not.

Here is a stripped down version for one select element, known size of the list (i.e. > 1) and only one option that contains Central. So basically just reordering the option:

var options = document.getElementById('townid').getElementsByTagName('option');

for (var i = 0, l = options.length; i < l; i++) {
    var option = options[i],
        text = option.innerText || option.textContent;
    if (text.indexOf('Central') > -1) {
        if (i > 1) {
            option.parentNode.insertBefore(option, options[1]);
        }
        break;
    }
}

DEMO

Update:

If the option's text should be exactly Central, compare the text normally:

if(text === 'Central')
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • +1 for teaching me something new: http://stackoverflow.com/questions/2125714/explanation-of-slice-call-in-javascript – mplungjan Mar 07 '11 at 12:42
  • @mplungjan: :) It was necessary because adding the `option` to the `DocumentFragment` also removes it from the `NodeList` (as it is live) and messed up with the loop index. – Felix Kling Mar 07 '11 at 12:44
  • I thought it was because you cannot slice a dom nodelist because it is not an array... – mplungjan Mar 07 '11 at 12:49
  • @Felix This syntax one is also new to me: `var option = options[i], text = option.innerText || option.textContent;` or is it a typo? – mplungjan Mar 07 '11 at 12:51
  • @mplungjan: I only use the `slice` to convert the NodeList to an array. The content of the array does not change, no matter where I add the options in the DOM, but the NodeList does. If I would iterate over the NodeList, then after moving one of the options to the DocumentFragment, the NodeList has one element less and so the iteration is messed up. It is like removing elements from an array while looping over it. – Felix Kling Mar 07 '11 at 12:52
  • @mplungjan: This is declaration of multiple variables in one statement. It is equivalent to `var option = options[i]; var text = option.innerText || option.textContent;`. So you basically save typing `var` ;) – Felix Kling Mar 07 '11 at 12:53
  • @Felix Okee, but that will place both in the global scope. Also it looked like you were shorcutting `option=new Option(option[i],text...||...)` so I was doubly confused – mplungjan Mar 07 '11 at 12:57
  • @mplungjan: No, it won't put them into global scope. Both variables are local. Maybe you confuse it with `var foo = bar = 42;`. Here `bar` will be global and `foo` local. *Edit:* Ah you mean it is not local to the `for` loop? JavaScript has no block scope anyway. Even in your code snippet, all the variables you declare in the `for` header are accessible outside of the `for` loop. – Felix Kling Mar 07 '11 at 12:58
  • @Felix Doh, no it won't when you add the var in front of option - sorry I did not think this one through - This one would save a var more ;) : `for (var option, text, i = 0, l = options.length; i < l; i++) { option = options[i]; text = option.innerText || option.textContent;` – mplungjan Mar 07 '11 at 13:01
  • @mplungjan: Ok I got it now :) You are right, this would also be nice way. – Felix Kling Mar 07 '11 at 13:02
  • Can you improve your code according to that needs: Central should be only once so no need to iterate over for loop, while loop maybe better. Also "Central C" or etc. is not valid, it should be just "Central". Voting up, thanks for your help. – kamaci Mar 07 '11 at 15:02
  • @kamaci: You're welcome. `for` loop, `while` loop does not matter. I'm using `break` in the loop to stop it once the option is found. For strict equality, use `text === 'Central'`. – Felix Kling Mar 07 '11 at 15:21
  • @Felix Kling I know you used break but I didn't see that break while writing that comment. Great work, thanks. – kamaci Mar 07 '11 at 18:39
  • +1. I'd love to see a jQuery->Raw JS interpreter for doing things like this automatically. – Yahel Mar 08 '11 at 21:36