1

So I'm building a form using Bootstrap, and I've got a modal that contains a dropdown. Two or Three text fields need to appear based on the users selection, and I've got that figured out with this:

The form:

<div class="row">
    <div class="form-group input-group">
        <div class="col">
            <label for="preinstallscripts" class="has-float-label">
                <select class="form-control spacing" name="preinstallscripts" id="preinstallscripts" required>
                    <option value="">Choose...</option>
                    <option value="MovePath">Move Path</option>
                    <option value="RemovePath">Remove Path</option>
                    <option value="LinkPath">Link Path</option>
                    <option value="chmod">CHMOD</option>
                    <option value="chmod -r">CHMOD -R</option>
                    <option value="chown">CHOWN</option>
                    <option value="chown group">CHOWN Group</option>
                    <option value="AddSource">Add Source</option>
                    <option value="RemoveSource">Remove Source</option>
                    <option value="RunScript">Run Script</option>
                </select>
                <span>Scripts</span>
            </label>
        </div><!-- End Column -->
    </div><!-- End Form Group -->
</div><!-- End Row -->

And my JQuery:

$(document).ready(function() {
    $('#preinstallscripts').change(function(e) {
        e.preventDefault();

        var selection = $(this).val();

        if (selection === 'MovePath') {
            $('#preinstall-inputs').html('<div class="row" style="margin-bottom: -10px;"><div class="form-group input-group"><div class="col"><input type="text" name="preinstall[movepath][]" id="preinstall-movepath" class="form-control"></div><div class="col"><input type="text" name="preinstall[movepath][]" id="preinstall-movepath" class="form-control"></div></div></div>');
        };
    });
});

Here's where my issue arises, within that modal, I have a button to add an additional dropdown, but this dropdown will contain the exact same options with the exact same values. If I add a dropdown, then select something from it, it won't add the text fields as the added dropdown has the same id as the initial one, and the JQuery won't properly work as there's now two dropdowns with id="preinstallscripts".

I'm thinking that there must be a way to edit the JQuery on my page when a button is pressed, that way if someone presses the button to add an additional dropdown, the function that I pasted will be updated to target that new dropdown. But I do that then the previous (initial) dropdown won't change its textfields if the user goes back and changes their selection as that JQuery function is now targeting the new ID of the new dropdown.

I'm assuming it's a poor idea in practice to just write 5 JQuery functions even if only 1 gets used, as I could write 5 different functions targeting id="preinstallscripts1" through id="preinstallscripts5" and the button that adds a new dropdown will contain a var, starting at 1, and each time it adds a new dropdown I'll do var++, and echo that var into the appended HTML as .append(<select id="preinstallscripts"+var>

That method is redundant and I feel as if there has to be a better way of going about this.

EDIT: For some more context, here's the JQuery that adds an additional dropdown on the press of a button:

$(document).ready(function() {
    var preInstallMax      = 2;                         //maximum input boxes allowed
    var preInstallWrapper  = $(".preinstall-modal");    //Fields wrapper
    var preInstallAdd      = $(".addPreInstall");       //Add button ID

    var z = 0;                                      //initlal text box count
    $(preInstallAdd).click(function(e){                 //on add input button click
        e.preventDefault();
        if(z < preInstallMax){                      //max input box allowed
            z++;                                    //text box increment
            $(preInstallWrapper).append('<hr><div class="row"><div class="form-group input-group"><div class="col"> <label for="preinstallscripts" class="has-float-label"> <select class="form-control spacing" name="preinstallscripts" id="preinstallscripts" required><option value="">Choose...</option><option>Move Path</option><option>Remove Path</option><option>Link Path</option><option>CHMOD</option><option>CHMOD -R</option><option>CHOWN</option><option>CHOWN Group</option><option>Add Source</option><option>Remove Source</option><option>Run Script</option> </select> <span>Scripts</span> </label></div></div></div><div id="preinstall-inputs2"></div>'); //add input box
        }
    });
});

And here's a screenshot of the modal if it helps to visualize it:

Modal

Brian
  • 45
  • 8
  • instead of ids why dont you try modifying your code to assign classes. I agree that ids are unique but classes can be repeatable in jquery. – Kishen Nagaraju May 18 '18 at 20:04
  • Your code that adds the new dropdown could easily be modified to make the ids unique (append a counter to the text). In addition to that, as mentioned, you can attach your handler by class instead of by id. – Patrick Q May 18 '18 at 20:06
  • @KishenNagaraju Well I need a way to differentiate each additional dropdown once added. They will contain the same ` – Brian May 18 '18 at 20:06
  • @PatrickQ So pretty much what I figured I'd have to do in my post? Is that not considered poor practice or is that pretty much the only way of going about it? And is there pros/cons to using classes of ID's? – Brian May 18 '18 at 20:07
  • 1
    Your idea of creating the unique ids is correct, but you do not create one handler function for each of them. Instead, you create one general handler for all that can deal with them appropriately. See [this answer](https://stackoverflow.com/questions/50412147/calculate-total-sum-of-dynamically-added-items-to-a-table/50413084#50413084) that I posted today on a somewhat related question. That question also involves dynamically added elements and their interaction with associated elements. Should give you a good starting point. – Patrick Q May 18 '18 at 20:09
  • @PatrickQ Thanks for the info, I've read over your answer on the post you linked, I think I've got the idea now. I'll come back and post another letting you know that I figured it out. Seems pretty straight forward. – Brian May 18 '18 at 20:19

1 Answers1

1

I would do things little bit differently. Many of the comments above mentioned to use unique IDs but I don't quite agree as jQuery has a lot of useful methods for tree traversing in order to add context.

1) I would build one template for your "preinstall script" block of HTML code as it is something you will be reusing.

<div id="template" style="display:none">
    <div class="row">
        <div class="form-group input-group">
            <div class="col">
                <label class="has-float-label">
                    <!-- note the use of class instead of id -->
                    <select class="form-control spacing preinstallscripts" required>
                        <option value="">Choose...</option>
                        <option value="MovePath">Move Path</option>
                        <option value="RemovePath">Remove Path</option>
                        <option value="LinkPath">Link Path</option>
                        <!-- etc -->
                    </select>
                    <span>Scripts</span>
                </label>
            </div>
        </div>
        <!-- add HTML code here for step 2 -->
    </div>
</div>

2) When user selects an option from the dropdown, instead of appending blocks of HTML code, I would toggle blocks of HTML code (1) based off the selection and (2) relative to the current row container. By default, these other blocks of code will be hidden. In terms of toggling blocks based off user's selection, we will use a data-attribute e.g. data-script to each block. For example,

<div style="display:none" data-script="MovePath">
    <!-- ... -->
</div>

<div style="display:none" data-script="RemovePath">
    <!-- ... -->
</div>

3) As one of the comments pointed out, you will need to use delegated event handling as you are working with dynamically-created elements.

$(document).on('change', '.preinstallscripts', function (e) {
    var row = $(this).closest('.row');
    // toggle row's blocks based off user's selection
    row.find('[data-script]').each(function () {
        $(this).toggle(this.dataset.script == e.target.value);
    });
});

4) In terms of adding a new "preinstall script" block, use the template.

$('.addPreInstall').click(function () {
    modal.find('.modal-body').append(template.html());
});

DEMO

$(function() {
  var template = $('#template'),
    modal = $('.preinstall-modal');

  $(document).on('change', '.preinstallscripts', function(e) {
    var row = $(this).closest('.row');
    // toggle row's blocks based off user's selection
    row.find('[data-script]').each(function() {
      $(this).toggle(this.dataset.script == e.target.value);
    });
  });

  $('.addPreInstall').click(function() {
    // append a new row
    modal.find('.modal-body').append(template.html());
    // trigger initial click
  }).click();


  modal.modal('show');

});
.preinstall-modal .row {
  border-top: 1px solid #ddd;
  padding-top: 20px;
}

.preinstall-modal .row:first-child {
  border-top: none;
  padding-top: 0;
}
<link rel="stylesheet" href="https://cdn.rawgit.com/tonystar/bootstrap-float-label/v3.0.1/dist/bootstrap-float-label.min.css" />

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">

<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>

<!-- MODAL -->

<div class="modal preinstall-modal" tabindex="-1" role="dialog">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">Preinstall scripts</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">

      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-outline-success addPreInstall">Add Another Preinstall Script</button>
        <button type="button" class="btn btn-primary">Save changes</button>
      </div>
    </div>
  </div>
</div>

<!-- TEMPLATE -->

<div id="template" style="display:none">
  <div class="row">
    <div class="form-group input-group">
      <div class="col">
        <label class="has-float-label">
      <!-- note the use of class instead of id -->
      <select class="form-control spacing preinstallscripts" required>
       <option value="">Choose...</option>
       <option value="MovePath">Move Path</option>
       <option value="RemovePath">Remove Path</option>
              <option value="LinkPath">Link Path</option>
              <!-- etc -->
      </select>
      <span>Scripts</span>
     </label>
      </div>
    </div>
    <div style="display:none" data-script="MovePath">
      Insert Move Path HTML code
    </div>
    <div style="display:none" data-script="RemovePath">
      Insert Remove Path HTML code
    </div>
    <div style="display:none" data-script="LinkPath">
      Insert Link Path HTML code
    </div>
  </div>

</div>
Mikey
  • 6,728
  • 4
  • 22
  • 45
  • Wow, thank you so much! Sorry for not noticing your answer earlier. Shortly after I asked this I was trying out different things, and on my last attempt I was appending the input field based on the users selection, _in addition too_ the `
    ` _**and**_ the next dropdown.
    – Brian May 23 '18 at 01:59