0

I have a set of checkboxes, and based on which items are selected, different upload fields will display. To facilitate this, I want to dynamically create an array that contains all the classes I want to fetch (without duplication).

I have created an object with the details:

var uploadReq = {
    a: [".document-2"],
    b: [".document-1", ".document-3"],
    c: [".document-1"],
    d: [".document-2", ".document-3"],
    e: [],
    f: [".document-3"]
};

In this example, if someone chose Category A and Category B, we should end up with an array something like [".document-2", ".document-1", ".document-3"] (order is unimportant).

I have an event listener like this:

$("#client-cat").change(function() {
    var arr = $("#client-cat input");
    var classes = [];

    for(i=0; i<arr.length; i++) {
        if(arr[i].checked) {
            var value = arr[i].value;
            var arr2 = uploadReq[value];
            $.extend(classes, arr2);
        }
    };

    console.log(classes);
})

HTML:

<form>
    <div class="segment">
        <label for="client-cat">Client category:</label>
        <div id="client-cat">
            <div>
                <input type="checkbox" tabindex="0" name="client-cat" value="a">
                <label>Category A</label>
            </div>

            <div>
                <input type="checkbox" tabindex="0" name="client-cat" value="b">
                <label>Category B</label>
            </div>

            <div>
                <input type="checkbox" tabindex="0" name="client-cat" value="c">
                <label>Category C</label>
            </div>

            <div>
                <input type="checkbox" tabindex="0" name="client-cat" value="d">
                <label>Category D</label>
            </div>

            <div>
                <input type="checkbox" tabindex="0" name="client-cat" value="e">
                <label>Category E</label>
            </div>

            <div>
                <input type="checkbox" tabindex="0" name="client-cat" value="f">
                <label>Category F</label>
            </div>
        </div>
    </div>

    <div class="segment">
        <div class="document-1">
            <label for="document-1">Upload a copy of Document 1:</label>
            <input type="file" id="document-1" name="1" accept=".pdf">
        </div>

        <div class="document-2">
            <label for="document-2">Upload a copy of Document 2:</label>
            <input type="file" id="document-2" name="2" accept=".pdf">
        </div>

        <div class="document-3">
            <label for="document-3">Upload a copy of Document 3:</label>
            <input type="file" id="document-3" name="3" accept=".pdf">
        </div>
    </div>

</form>

However, when I run the code with Category A and Category B checked, I only get [".document-1", ".document-3"].

I believe what is happening is that every time the for loop restarts, it is reverting to the original value for classes, instead of taking the value from the end of the loop and restarting it.

Research/attempted fixes:

  • Looked at this thread which discusses using $.extend to solve a similar problem (e.g. combine two arrays without catching duplicates)
  • Tested to see if it's a scoping issue by trying the code:

    $("#client-cat").change(function() {
        var arr = $("#client-cat input");
        var classes = [];
    
        for(i=0; i<arr.length; i++) {
            if(arr[i].checked) {
                classes.push("bob");
            }
        }
    
        console.log(classes);
    })
    

    But the above logs an array with as many "bob"s as there are checkboxes, so I don't think it's an issue with the scope of class.

  • Considered other methods of combining the data. Example: push works, but I end up with arrays inside arrays, which isn't what I want. Example: map might do it, but doesn't clear out duplicates.

JSFiddle here.

  • Unrelated to the problem, but you can use `$("#client-cat input:checked")` so you only loop over the checked boxes. – Barmar Feb 16 '18 at 17:06
  • can you not try `classes=classes.concat(arr2);` instead of `$.extend(classes, arr2);` – Inus Saha Feb 16 '18 at 17:11
  • @Barmar Thanks, I actually have that in my full code, but switched to the simpler to format troubleshoot my issue. – elegant-solutions Feb 16 '18 at 17:12
  • @Yunus Saha, that creates the same problem as `map`, in that it leaves duplicates. For instance, if you choose B and C, you get `[".document-1", ".document-3", ".document-1"]`. Edit: I understand the issue per the answer and why that solution is better. – elegant-solutions Feb 16 '18 at 17:15

2 Answers2

4

$.extend is not the right way to append arrays. It merges objects by adding properties in the second (or later) object that aren't in the first object. But when you use arrays, both arrays have a 0 property, so the first element of the second array is not added to the first array.

The function for appending arrays is concat. It doesn't modify the array in place, you need to assign back to the variable.

$("#client-cat").change(function() {
    var arr = $("#client-cat input:checked");
    var classes = [];

    arr.each(function() {
        classes = classes.concat(uploadReq[this.value]);
    });

    console.log(classes);
})
Barmar
  • 741,623
  • 53
  • 500
  • 612
  • There's an extra parenthesis on your classes = classes.concat... line – Tutch Feb 16 '18 at 17:21
  • For anyone who ends up using this in the future, I used this, and then used the solution [here](https://stackoverflow.com/questions/9229645/remove-duplicate-values-from-js-array) to remove the duplicates. – elegant-solutions Feb 16 '18 at 17:40
  • @elegant-solutions Where does the question say that there shouldn't be duplicates? – Barmar Feb 16 '18 at 17:43
  • Mm, I see what you mean, it's buried within the "Research/attempted fixes:" section, mentioned in bullet 1 and 3. But it should be earlier, I will edit. – elegant-solutions Feb 18 '18 at 14:18
1

jQuery.extend()

First param: An object that will receive the new properties if additional objects are passed in or that will extend the jQuery namespace if it is the sole argument.

Therefore, with arrays you're going to face problems.

You can use this approach: classes = [ ...classes, ...arr2]

var uploadReq = {
  a: [".document-2"],
  b: [".document-1", ".document-3"],
  c: [".document-1"],
  d: [".document-2", ".document-3"],
  e: [],
  f: [".document-3"]
};

$("#client-cat").change(function() {
  var classes = [];
  var arr = $("#client-cat input");
  for (var i = 0; i < arr.length; i++) {
    if (arr[i].checked) {
      var value = arr[i].value;
      var arr2 = uploadReq[value];
      classes = [ ...classes, ...arr2];
    }
  }

  console.log(classes);
});
.segment {
  margin-bottom: 2em;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!DOCTYPE html>
<html>

<head>
  <link rel="stylesheet" type="text/css" href="checkbox-display.css">
</head>

<body>

  <form>
    <div class="segment">
      <label for="client-cat">Client category:</label>
      <div id="client-cat">
        <div>
          <input type="checkbox" tabindex="0" name="client-cat" value="a">
          <label>Category A</label>
        </div>

        <div>
          <input type="checkbox" tabindex="0" name="client-cat" value="b">
          <label>Category B</label>
        </div>

        <div>
          <input type="checkbox" tabindex="0" name="client-cat" value="c">
          <label>Category C</label>
        </div>

        <div>
          <input type="checkbox" tabindex="0" name="client-cat" value="d">
          <label>Category D</label>
        </div>

        <div>
          <input type="checkbox" tabindex="0" name="client-cat" value="e">
          <label>Category E</label>
        </div>

        <div>
          <input type="checkbox" tabindex="0" name="client-cat" value="f">
          <label>Category F</label>
        </div>
      </div>
    </div>

    <div class="segment">
      <div class="document-1">
        <label for="document-1">Upload a copy of Document 1:</label>
        <input type="file" id="document-1" name="1" accept=".pdf">
      </div>

      <div class="document-2">
        <label for="document-2">Upload a copy of Document 2:</label>
        <input type="file" id="document-2" name="2" accept=".pdf">
      </div>

      <div class="document-3">
        <label for="document-3">Upload a copy of Document 3:</label>
        <input type="file" id="document-3" name="3" accept=".pdf">
      </div>
    </div>

  </form>

  <script src="http://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous">
  </script>

</body>

</html>
Ele
  • 33,468
  • 7
  • 37
  • 75