1

I have a simple php 'foreach' loop which works fine when there is only one loop. The php code displays a simple select box which shows or hides a div based on the selection value. The select box and div's are then repeated within each loop. The problem is that when there is more than one repeat of the code in the loop, the show/hide JavaScript does not work as the ID's are not unique, only the first in the loop works.

How can I get this to work within a loop? Does the ID need to be unique or is there a better way to do this with Javascript?

PHP CODE:

foreach ( $order->get_items() as $item_id => $item ) {
             
                echo 'Service Area:';
                echo '<select id="serviceareaselect">';
                echo '<option value="servicearea">Select an Area...</option>';
                echo '<option value="area1">Area 1</option>';
                echo '<option value="area2">Area 2</option>';
                echo '</select>';
             
                // Service Area 1
                echo '<div id="area1" class="servicearea" style="display:none">';
                echo '<input type="checkbox" id="dur1" name="dur1" value="Dur1"><label for="vehicle1">Dur1</label><br>';
                echo '<input type="checkbox" id="dur2" name="dur2" value="Dur2"><label for="vehicle1">Dur2</label><br>';
                echo '</div>';
                
                // Service Area 2
                echo '<div id="area2" class="servicearea" style="display:none">';
                echo '<input type="checkbox" id="aln1" name="aln1" value="Aln1"><label for="vehicle1">Aln1</label><br>';
                echo '<input type="checkbox" id="aln2" name="aln2" value="Aln2"><label for="vehicle1">Aln2</label><br>';
                echo '</div>';
                
}

JAVASCRIPT CODE:

$(function()
{
$("#serviceareaselect").change(function(){
    $(".servicearea").hide();
    $("#" + $(this).val()).show();
    });
});
Cerbrus
  • 70,800
  • 18
  • 132
  • 147
DSmit
  • 105
  • 13
  • 1
    `Does the ID need to be unique` - yes, ID must be unique (most IDE's will even shout at you if you write HTML with non-unique ID's). For info, please see [this question](https://stackoverflow.com/questions/9454645/does-id-have-to-be-unique-in-the-whole-page) – Can O' Spam Aug 31 '22 at 12:42
  • 4
    Of course IDs have to be unique, they always do. _"or is there a better way"_ - yes; forget IDs, and use the structural relationship the elements have in the DOM instead. Give the select a class, so that you can use that to bind the event handler to it. And then, wrap your select and input fields in an additional div container, then you can simply use `$(this).parents(...)` to navigate from the current select field to that container div, and then use `find` to find the appropriate area div (add `area1` and `area2` as classes instead of IDs on those divs.) – CBroe Aug 31 '22 at 12:44
  • If you have a single unique element on a page that you want to be able to select, an ID is appropriate. If you have multiple repeating elements, it's normally cleaner and easier to code if you use classes. – James Aug 31 '22 at 12:46

2 Answers2

2

Using no ID attributes at all it is easy to identify which items to process by inspecting the event and especially event.target

If you assign a delegated event listener to some ancestor element ( in this case I used the document itself ) thanks to event bubbling you are able to identify which particular DOM node triggered the change event and from that, using other selector mechanisms (querySelector, querySelectorAll, parentNode etc ) it is trivial to manipulate other DOM elements as appropriate.

The example below is slightly modified in that the HTML portion that you were rendering in PHP now have common parent elements to make the task of identification easier.

Regardless of how many such elements section items were to be rendered the event handler mechanism should work the same with no need to add integers to ID attributes which, quite honestly, gets messy.

The Javascript is vanilla Javascript as I don't use jQuery and would likely mislead with my attempts at using it but no doubt there are appropriate jQuery methods available to do this.

document.addEventListener('change', e => {
  if (e.target.name == 'serviceareaselect') {
    let parent = e.target.parentNode;
        parent.querySelectorAll('.servicearea').forEach(n => n.style.display = 'none');
        
    if (e.target.options.selectedIndex > 0) parent.querySelector(`[data-id="${e.target.value}"]`).style.display = 'block';
  }
})
label{display:block}
<section>
  Service Area:
  <select name="serviceareaselect">
    <option value="servicearea">Select an Area...
    <option value="area1">Area 1
    <option value="area2">Area 2
  </select>

  // Service Area 1
  <div data-id="area1" class="servicearea" style="display:none">
    <label><input type="checkbox" name="dur1" value="Dur1">Dur1</label>
    <label><input type="checkbox" name="dur2" value="Dur2">Dur2</label>
  </div>

  // Service Area 2
  <div data-id="area2" class="servicearea" style="display:none">
    <label><input type="checkbox" name="aln1" value="Aln1">Aln1</label>
    <label><input type="checkbox" name="aln2" value="Aln2">Aln2</label>
  </div>
</section>

<section>
  Service Area:
  <select name="serviceareaselect">
    <option value="servicearea">Select an Area...
    <option value="area1">Area 1
    <option value="area2">Area 2
  </select>

  // Service Area 1
  <div data-id="area1" class="servicearea" style="display:none">
    <label><input type="checkbox" name="dur1" value="Dur1">Dur1</label>
    <label><input type="checkbox" name="dur2" value="Dur2">Dur2</label>
  </div>

  // Service Area 2
  <div data-id="area2" class="servicearea" style="display:none">
    <label><input type="checkbox" name="aln1" value="Aln1">Aln1</label>
    <label><input type="checkbox" name="aln2" value="Aln2">Aln2</label>
  </div>
</section>
Professor Abronsius
  • 33,063
  • 5
  • 32
  • 46
  • Thanks for the suggestion, I like the simplicity. The problem is that when this is repeated in a php foreach loop, the second/repeated select box shows/hides the first div's in the loop. – DSmit Aug 31 '22 at 13:16
  • I have quickly rattled up a test page using PHP and repeating the `section` 10 times and it seems to work as expected. – Professor Abronsius Aug 31 '22 at 13:24
  • One thing I have just noticed is the use of `label` elements. Without an ID attribute the `for` attribute becomes useless so it could be removed entirely and the `input` then nested inside the label. This would be valid. – Professor Abronsius Aug 31 '22 at 13:29
  • Sorry... i didn't see you had added sections to the code! I will give it another try. Thanks. – DSmit Aug 31 '22 at 13:34
  • The `section` elements as parent containers make identification of event items easier as you can easily work up the DOM to the Parent node and from there query within. However without that common parent ( and it does not need to be a `section` at all) you can still do this but the logic becomes a little more complex. – Professor Abronsius Aug 31 '22 at 13:45
  • Thanks for this, tested the code and everything works perfectly. I'm marking this as the solution because of its simplicity and it works perfectly for what I am trying to achieve. Thanks! – DSmit Aug 31 '22 at 16:05
  • I'm glad that I was able to offer some guidance and help you solve this little problem. Good luck with the rest of your coding adventures. – Professor Abronsius Aug 31 '22 at 18:19
  • 1
    One other point in regards to the nesting of the `input` within the `label` now that there are no ID attributes is that in the CSS you can declare the `label` as ` block-level` element which then means you no longer need the `
    ` tags
    – Professor Abronsius Aug 31 '22 at 18:21
0

The Id has to be unique. You can use $item_id in the loop to make it unique.

PHP code:

foreach ( $order->get_items() as $item_id => $item ) {
 
    echo 'Service Area:';
    echo '<select id="serviceareaselect_'.$item_id.'" class="serviceareaselect" data-itemid="'.$item_id.'">';
    echo '<option value="servicearea">Select an Area...</option>';
    echo '<option value="area1_'.$item_id.'">Area 1</option>';
    echo '<option value="area2_'.$item_id.'">Area 2</option>';
    echo '</select>';
 
    // Service Area 1
    echo '<div id="area1_'.$item_id.'" class="servicearea_'.$item_id.'" style="display:none">';
    echo '<input type="checkbox" id="dur1" name="dur1" value="Dur1"><label for="vehicle1">Dur1</label><br>';
    echo '<input type="checkbox" id="dur2" name="dur2" value="Dur2"><label for="vehicle1">Dur2</label><br>';
    echo '</div>';
    
    // Service Area 2
    echo '<div id="area2_'.$item_id.'" class="servicearea_'.$item_id.'" style="display:none">';
    echo '<input type="checkbox" id="aln1" name="aln1" value="Aln1"><label for="vehicle1">Aln1</label><br>';
    echo '<input type="checkbox" id="aln2" name="aln2" value="Aln2"><label for="vehicle1">Aln2</label><br>';
    echo '</div>';
    
}

JS code:

$(function(){
    $(".serviceareaselect").change(function(){
        let item_id = $(this).data("itemid");
        $(".servicearea_" + item_id).hide();
        $("#" + $(this).val()).show();
    });
});
C. Celora
  • 429
  • 1
  • 3
  • 12
  • Thanks for this, I tested it and it works perfectly for generating unique ID's in a foreach loop, thank you. – DSmit Aug 31 '22 at 16:03