City of Ghent Style Guide

Checkboxes dynamic

When to use this component

Use the checkboxes dynamic component when you need to help users select one or more options from a list of options.

When not to use this component

Do not use the checkboxes dynamic component when you need to help users:

  • Toggle a single option on or off.
  • Select one or more options from a fixed, short list of options.

In these cases, use (non-dynamic) checkboxes instead.

How it works

The checkboxes dynamic component is an adapted version of the checkboxes component that can handle various numbers of checkboxes, both short lists of checkboxes as well as average-length lists as long lists of checkboxes.

Depending on the number of checkboxes, the checkboxes dynamic component presents the checkboxes in a different way:

  • Up to 6 checkboxes: A normal list of checkboxes where all checkboxes are visible. This is the default view.
  • From 6 to 20 checkboxes: The first 3 checkboxes are shown. The following checkboxes are in an accordion with a “Show more” button to show or to hide them.
  • More than 20 checkboxes: The first 3 checkboxes are shown. The full list of checkboxes is placed in a modal that can be opened using a “Show more” button.

Behavior with more than 20 checkboxes

When there are more than 20 checkboxes, what would normally be a long list of checkboxes, is replaced by showing only the first 3 checkboxes with a “Show more” button that opens a modal where the list of checkboxes is placed instead.

The behavior is as follows:

  1. When the users clicks the “Show more” button, the modal is opened.
  2. In the modal, the list of checkboxes is shown.
  3. The user can select on or more checkboxes in the modal.
  4. To make finding and selecting the right checkboxes easier, the user can filter the list of checkboxes to narrow it down.
  5. When ready, the user can click the “Confirm selection” button, and the selected checkboxes will be the new state.
  6. Closing the modal without confirmation restores the previous state.

When the user selects one or more of the first 3 checkboxes that are already visible outside the modal, the corresponding checkboxes inside the modal are also selected.

When one or more checkboxes are selected, inside or outside the modal, the user can see this both:

  • In the default view, outside the modal: When one or more of the first 3 checkboxes are selected, this is shown. When one or more of the checkboxes that are inside the modal but not one of the first 3 checkboxes, those checkboxes are added to the first 3 checkboxes and it is shown that they are selected.
  • Inside the modal: The corresponding checkboxes are selected. Below the filter field, the selected options are also shown using filter tags. The user can also deselect options here. Read more about how tags work.

Accessibility

  • See our accordion and modal components.
  • The aria-label for the filter field inside the modal indicates that the filtering will happen on input.
  • The result count of the filtering is wrapped in an aria-live region.

Javascript

This display uses the accordion JS and @digipolis-gent/modal.
We refer you to the accordion and modal components for a detailed account on how to use these libraries.

Create new accordions for each element with className .checkbox-accordion.

new Accordion(element);

Create new modals for each element with className .modal:not(.has-custom-binding), if you haven’t done so already.

new Modal(element);

On top of that, create a new CheckboxFilterDynamic instances for each element with className checkbox-filter-dynamic .
Don’t forget to add your translation for the hiddenTagText.

new CheckboxFilterDynamic(element, {hiddenTagText: 'Remove tag'})

More than 20 checkboxes

The checkboxes shown as preview are copies!
You link them to their original checkbox by providing a data-original attribute with the ID of the original checkbox.
Do not give them a name if you don’t want them to show up in your FormData!

CheckboxFilterDynamic options parameter

Option Type Default Description
filterField String .checkbox-filter__filter QuerySelector for the filter input field.
modalPreview String .modal-preview QuerySelector for the wrapper containing modal preview checkboxes.
accordionPreview String .accordion-preview QuerySelector for the wrapper containing accordion preview checkboxes.
accordionBtn String button.accordion--button QuerySelector for the button that toggles the accordion.
previewCheckboxes String div.checkbox.preview QuerySelector for the preview checkboxes.
checkboxes String div.checkbox:not(.preview) QuerySelector for the checkboxes.
selectedContainer String .checkbox-filter__selected QuerySelector for the container holding the selected filter tags.
checkboxContainers String .checkbox-filter__checkboxes QuerySelector for the container holding the checkboxes.
openBtn String .checkbox-filter__open QuerySelector for the button used to open the modal.
submitBtn String .checkbox-filter__submit QuerySelector for the button used to confirm the selection and close the modal.
closeBtns String .checkbox-filter__close QuerySelector for a list of buttons used to close the modal.
resultSpan String .checkbox-filter__result QuerySelector for the container to display the number of search results.
makeTags Boolean true Prevent creation of tags, in case you have your own implementation.
hiddenTagText String Remove tag Text used behind the remove-tag button, insert your translation here.
onRemoveTag function function(checkbox, tag){} A hook to append your own logic after a tag has been removed.
{% if options|length < 6 %}

  {% include '@checkboxes' %}

{% else %}

  <fieldset class="form-item {{ modifier }} checkbox-filter-dynamic">
    <legend>
      {{ label }}
      {% if label_optional %}
        <span class="label-optional">({{ label_optional }})</span>
      {% endif %}
    </legend>

    <div class="form-item">
      {% if field_description %}
        {% include '@field-message' with {
          field_message: field_description,
          modifier: null
        } %}
      {% endif %}

      <div class="form-columns">
        <div class="form-item-column">

          {% if options | length < 21 %}

            <div class="accordion-preview">
              {% for option in options|slice(0, 3) %}
                {% include '@input' with {
                  id: "input-" ~ id ~ "-" ~ modifier ~ "-" ~ option.id,
                  type: 'checkbox',
                  name: option.name,
                  label: option.label,
                  modifier: modifier
                } %}
              {% endfor %}
            </div>

            <div class="checkbox-accordion">
              <div class="accordion--content" aria-hidden="true"
                   hidden="hidden" id="{{ "hidden-options-" ~ id ~ "-" ~ modifier }}">

                {% for option in options|slice(3, 21) %}
                  {% include '@input' with {
                    id: "input-" ~ id ~ "-" ~ modifier ~ "-" ~ option.id,
                    type: 'checkbox',
                    name: option.name,
                    label: option.label,
                    modifier: modifier
                  } %}
                {% endfor %}

              </div>

              <button type="button"
                      class="accordion--button button button-secondary button-small icon-left"
                      aria-expanded="false"
                      aria-controls="{{ "hidden-options-" ~ id ~ "-" ~ modifier }}">
                Show more
              </button>
            </div>

          {% else %}

            <div class="modal-preview">
              {% for option in options|slice(0, 3) %}
                {% include '@input' with {
                  id: "input-" ~ id ~ "-" ~ modifier ~ "-" ~ option.id ~ "-preview",
                  type: 'checkbox',
                  name: null,
                  attributes: 'data-original="' ~ "input-" ~ id ~ "-" ~ modifier ~ "-" ~ option.id ~ '"',
                  label: option.label,
                  modifier: modifier,
                  modifierWrapper: "preview",
                  value: option.value
                } %}
              {% endfor %}
            </div>

            {% set modalTitle = label %}

            {% if label_optional %}
              {% set modalTitle = modalTitle ~ '<span class="label-optional">(' ~ label_optional ~ ')</span>' %}
            {% endif %}

            {% set modalContent %}

              <div class="form-item checkbox-filter__filter__search-wrapper">
                <input type="search" id="checkboxes__filter_id_{{ modifier }}"
                       class="checkbox-filter__filter"
                       aria-label="Filter the list below">

                <div aria-live="polite" aria-atomic="true"
                     class="checkbox-filter__result-wrapper">
                  <span class="checkbox-filter__result">#</span> filter(s) found
                </div>
              </div>

              {% include '@tag-list' with { modifier: 'checkbox-filter__selected' } %}

              {% include '@checkboxes' %}

            {% endset %}

            {% set modalActions %}
              <button type="button"
                      class="button button-primary checkbox-filter__submit modal-close"
                      data-target="{{ 'modal' ~ modifier }}">Confirm selection
              </button>
            {% endset %}

            {% include "@modal" with {
              id: 'modal' ~ modifier,
              title: modalTitle,
              title_heading_level: 'h2',
              classes: 'checkbox-filter__modal',
              cancelClasses: 'checkbox-filter__close',
              modifier: 'fixed-height',
              content: modalContent,
              actions: modalActions,
              hasFocusables: true
            } %}

            <button type="button"
                    class="button button-secondary button-small icon-left icon-search checkbox-filter__open"
                    aria-controls="{{ 'modal' ~ modifier }}"
                    aria-expanded="false">
              Show more
            </button>

          {% endif %}

        </div>
        <div class="form-item-column">

          {% if modifier == 'error' %}
            {% include '@field-message' with {
              "modifier": "error"
            } %}
          {% endif %}

          {% if modifier == 'success' %}
            {% include '@field-message' with {
              "modifier": "success"
            } %}
          {% endif %}

        </div>
      </div>
    </div>
  </fieldset>
{% endif %}
<fieldset class="form-item error checkbox-filter-dynamic">
    <legend>
        Checkboxes
    </legend>

    <div class="form-item">

        <div class="form-columns">
            <div class="form-item-column">

                <div class="modal-preview">
                    <div class="checkbox preview">

                        <input type="checkbox" id="input-with-error-error-checkbox-20-default-20-plus-preview" data-original="input-with-error-error-checkbox-20-default-20-plus" value="20" class="checkbox error" />
                        <label for="input-with-error-error-checkbox-20-default-20-plus-preview">Checkbox option 20</label>
                    </div>
                    <div class="checkbox preview">

                        <input type="checkbox" id="input-with-error-error-checkbox-19-default-20-plus-preview" data-original="input-with-error-error-checkbox-19-default-20-plus" value="19" class="checkbox error" />
                        <label for="input-with-error-error-checkbox-19-default-20-plus-preview">Checkbox option 19</label>
                    </div>
                    <div class="checkbox preview">

                        <input type="checkbox" id="input-with-error-error-checkbox-18-default-20-plus-preview" data-original="input-with-error-error-checkbox-18-default-20-plus" value="18" class="checkbox error" />
                        <label for="input-with-error-error-checkbox-18-default-20-plus-preview">Checkbox option 18</label>
                    </div>
                </div>

                <div id="modalerror" class="modal modal--fixed-height checkbox-filter__modal" role="dialog" aria-modal="true" aria-labelledby="modalerror-title" tabindex="-1">

                    <div class="modal-inner">
                        <div class="modal-header">
                            <button type="button" class="button close icon-cross modal-close checkbox-filter__close" data-target="modalerror">
                                <span>Close</span>
                            </button>
                        </div>
                        <div class="modal-content">
                            <h2 id="modalerror-title">Checkboxes</h2>

                            <div class="form-item checkbox-filter__filter__search-wrapper">
                                <input type="search" id="checkboxes__filter_id_error" class="checkbox-filter__filter" aria-label="Filter the list below">

                                <div aria-live="polite" aria-atomic="true" class="checkbox-filter__result-wrapper">
                                    <span class="checkbox-filter__result">#</span> filter(s) found
                                </div>
                            </div>

                            <div class="tag-list-wrapper">
                                <ul class="tag-list checkbox-filter__selected">
                                </ul>
                            </div>

                            <fieldset class="form-item error">
                                <legend>
                                    Checkboxes
                                </legend>
                                <div class="form-item">
                                    <div class="form-columns">
                                        <div class="form-item-column">
                                            <div class="checkbox">

                                                <input type="checkbox" id="input-with-error-error-checkbox-20-default-20-plus" name="checkboxes-dynamic" value="20" class="checkbox error" />
                                                <label for="input-with-error-error-checkbox-20-default-20-plus">Checkbox option 20</label>
                                            </div>
                                            <div class="checkbox">

                                                <input type="checkbox" id="input-with-error-error-checkbox-19-default-20-plus" name="checkboxes-dynamic" value="19" class="checkbox error" />
                                                <label for="input-with-error-error-checkbox-19-default-20-plus">Checkbox option 19</label>
                                            </div>
                                            <div class="checkbox">

                                                <input type="checkbox" id="input-with-error-error-checkbox-18-default-20-plus" name="checkboxes-dynamic" value="18" class="checkbox error" />
                                                <label for="input-with-error-error-checkbox-18-default-20-plus">Checkbox option 18</label>
                                            </div>
                                            <div class="checkbox">

                                                <input type="checkbox" id="input-with-error-error-checkbox-17-default-20-plus" name="checkboxes-dynamic" value="17" class="checkbox error" />
                                                <label for="input-with-error-error-checkbox-17-default-20-plus">Checkbox option 17</label>
                                            </div>
                                            <div class="checkbox">

                                                <input type="checkbox" id="input-with-error-error-checkbox-16-default-20-plus" name="checkboxes-dynamic" value="16" class="checkbox error" />
                                                <label for="input-with-error-error-checkbox-16-default-20-plus">Checkbox option 16</label>
                                            </div>
                                            <div class="checkbox">

                                                <input type="checkbox" id="input-with-error-error-checkbox-15-default-20-plus" name="checkboxes-dynamic" value="15" class="checkbox error" />
                                                <label for="input-with-error-error-checkbox-15-default-20-plus">Checkbox option 15</label>
                                            </div>
                                            <div class="checkbox">

                                                <input type="checkbox" id="input-with-error-error-checkbox-14-default-20-plus" name="checkboxes-dynamic" value="14" class="checkbox error" />
                                                <label for="input-with-error-error-checkbox-14-default-20-plus">Checkbox option 14</label>
                                            </div>
                                            <div class="checkbox">

                                                <input type="checkbox" id="input-with-error-error-checkbox-13-default-20-plus" name="checkboxes-dynamic" value="13" class="checkbox error" />
                                                <label for="input-with-error-error-checkbox-13-default-20-plus">Checkbox option 13</label>
                                            </div>
                                            <div class="checkbox">

                                                <input type="checkbox" id="input-with-error-error-checkbox-12-default-20-plus" name="checkboxes-dynamic" value="12" class="checkbox error" />
                                                <label for="input-with-error-error-checkbox-12-default-20-plus">Checkbox option 12</label>
                                            </div>
                                            <div class="checkbox">

                                                <input type="checkbox" id="input-with-error-error-checkbox-11-default-20-plus" name="checkboxes-dynamic" value="11" class="checkbox error" />
                                                <label for="input-with-error-error-checkbox-11-default-20-plus">Checkbox option 11</label>
                                            </div>
                                            <div class="checkbox">

                                                <input type="checkbox" id="input-with-error-error-checkbox-10-default-20-plus" name="checkboxes-dynamic" value="10" class="checkbox error" />
                                                <label for="input-with-error-error-checkbox-10-default-20-plus">Checkbox option 10</label>
                                            </div>
                                            <div class="checkbox">

                                                <input type="checkbox" id="input-with-error-error-checkbox-9-default-20-plus" name="checkboxes-dynamic" value="9" class="checkbox error" />
                                                <label for="input-with-error-error-checkbox-9-default-20-plus">Checkbox option 9</label>
                                            </div>
                                            <div class="checkbox">

                                                <input type="checkbox" id="input-with-error-error-checkbox-8-default-20-plus" name="checkboxes-dynamic" value="8" class="checkbox error" />
                                                <label for="input-with-error-error-checkbox-8-default-20-plus">Checkbox option 8</label>
                                            </div>
                                            <div class="checkbox">

                                                <input type="checkbox" id="input-with-error-error-checkbox-7-default-20-plus" name="checkboxes-dynamic" value="7" class="checkbox error" />
                                                <label for="input-with-error-error-checkbox-7-default-20-plus">Checkbox option 7</label>
                                            </div>
                                            <div class="checkbox">

                                                <input type="checkbox" id="input-with-error-error-checkbox-6-default-20-plus" name="checkboxes-dynamic" value="6" class="checkbox error" />
                                                <label for="input-with-error-error-checkbox-6-default-20-plus">Checkbox option 6</label>
                                            </div>
                                            <div class="checkbox">

                                                <input type="checkbox" id="input-with-error-error-checkbox-5-default-20-plus" name="checkboxes-dynamic" value="5" class="checkbox error" />
                                                <label for="input-with-error-error-checkbox-5-default-20-plus">Checkbox option 5</label>
                                            </div>
                                            <div class="checkbox">

                                                <input type="checkbox" id="input-with-error-error-checkbox-4-default-20-plus" name="checkboxes-dynamic" value="4" class="checkbox error" />
                                                <label for="input-with-error-error-checkbox-4-default-20-plus">Checkbox option 4</label>
                                            </div>
                                            <div class="checkbox">

                                                <input type="checkbox" id="input-with-error-error-checkbox-3-default-20-plus" name="checkboxes-dynamic" value="3" class="checkbox error" />
                                                <label for="input-with-error-error-checkbox-3-default-20-plus">Checkbox option 3</label>
                                            </div>
                                            <div class="checkbox">

                                                <input type="checkbox" id="input-with-error-error-checkbox-2-default-20-plus" name="checkboxes-dynamic" value="2" class="checkbox error" />
                                                <label for="input-with-error-error-checkbox-2-default-20-plus">Checkbox option 2</label>
                                            </div>
                                            <div class="checkbox">

                                                <input type="checkbox" id="input-with-error-error-checkbox-1-default-20-plus" name="checkboxes-dynamic" value="1" class="checkbox error" />
                                                <label for="input-with-error-error-checkbox-1-default-20-plus">Checkbox option 1</label>
                                            </div>
                                            <div class="checkbox">

                                                <input type="checkbox" id="input-with-error-error-checkbox-0-default-20-plus" name="checkboxes-dynamic" class="checkbox error" />
                                                <label for="input-with-error-error-checkbox-0-default-20-plus">Checkbox option 0</label>
                                            </div>
                                        </div>
                                        <div class="form-item-column">
                                            <div class="field-message error" role="alert" id="with-error-validation">
                                                Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec laoreet, urna sit amet convallis rhoncus, felis ex.
                                                <div class="accolade "></div>
                                            </div>

                                        </div>
                                    </div>
                                </div>
                            </fieldset>

                        </div>
                        <div class="modal-actions">
                            <button type="button" class="button button-primary checkbox-filter__submit modal-close" data-target="modalerror">Confirm selection
                            </button>

                        </div>
                    </div>

                    <div class="modal-overlay modal-close checkbox-filter__close" data-target="modalerror" tabindex="-1"></div>
                </div>

                <button type="button" class="button button-secondary button-small icon-left icon-search checkbox-filter__open" aria-controls="modalerror" aria-expanded="false">
                    Show more
                </button>

            </div>
            <div class="form-item-column">

                <div class="field-message error" role="alert" id="with-error">
                    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec laoreet, urna sit amet convallis rhoncus, felis ex.
                    <div class="accolade "></div>
                </div>

            </div>
        </div>
    </div>
</fieldset>
{
  "label": "Checkboxes",
  "id": "with-error",
  "field_description": null,
  "field_message": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec laoreet, urna sit amet convallis rhoncus, felis ex.",
  "description": "Description checkboxes.",
  "options": [
    {
      "label": "Checkbox option 20",
      "id": "checkbox-20-default-20-plus",
      "value": 20,
      "name": "checkboxes-dynamic"
    },
    {
      "label": "Checkbox option 19",
      "id": "checkbox-19-default-20-plus",
      "value": 19,
      "name": "checkboxes-dynamic"
    },
    {
      "label": "Checkbox option 18",
      "id": "checkbox-18-default-20-plus",
      "value": 18,
      "name": "checkboxes-dynamic"
    },
    {
      "label": "Checkbox option 17",
      "id": "checkbox-17-default-20-plus",
      "value": 17,
      "name": "checkboxes-dynamic"
    },
    {
      "label": "Checkbox option 16",
      "id": "checkbox-16-default-20-plus",
      "value": 16,
      "name": "checkboxes-dynamic"
    },
    {
      "label": "Checkbox option 15",
      "id": "checkbox-15-default-20-plus",
      "value": 15,
      "name": "checkboxes-dynamic"
    },
    {
      "label": "Checkbox option 14",
      "id": "checkbox-14-default-20-plus",
      "value": 14,
      "name": "checkboxes-dynamic"
    },
    {
      "label": "Checkbox option 13",
      "id": "checkbox-13-default-20-plus",
      "value": 13,
      "name": "checkboxes-dynamic"
    },
    {
      "label": "Checkbox option 12",
      "id": "checkbox-12-default-20-plus",
      "value": 12,
      "name": "checkboxes-dynamic"
    },
    {
      "label": "Checkbox option 11",
      "id": "checkbox-11-default-20-plus",
      "value": 11,
      "name": "checkboxes-dynamic"
    },
    {
      "label": "Checkbox option 10",
      "id": "checkbox-10-default-20-plus",
      "value": 10,
      "name": "checkboxes-dynamic"
    },
    {
      "label": "Checkbox option 9",
      "id": "checkbox-9-default-20-plus",
      "value": 9,
      "name": "checkboxes-dynamic"
    },
    {
      "label": "Checkbox option 8",
      "id": "checkbox-8-default-20-plus",
      "value": 8,
      "name": "checkboxes-dynamic"
    },
    {
      "label": "Checkbox option 7",
      "id": "checkbox-7-default-20-plus",
      "value": 7,
      "name": "checkboxes-dynamic"
    },
    {
      "label": "Checkbox option 6",
      "id": "checkbox-6-default-20-plus",
      "value": 6,
      "name": "checkboxes-dynamic"
    },
    {
      "label": "Checkbox option 5",
      "id": "checkbox-5-default-20-plus",
      "value": 5,
      "name": "checkboxes-dynamic"
    },
    {
      "label": "Checkbox option 4",
      "id": "checkbox-4-default-20-plus",
      "value": 4,
      "name": "checkboxes-dynamic"
    },
    {
      "label": "Checkbox option 3",
      "id": "checkbox-3-default-20-plus",
      "value": 3,
      "name": "checkboxes-dynamic"
    },
    {
      "label": "Checkbox option 2",
      "id": "checkbox-2-default-20-plus",
      "value": 2,
      "name": "checkboxes-dynamic"
    },
    {
      "label": "Checkbox option 1",
      "id": "checkbox-1-default-20-plus",
      "value": 1,
      "name": "checkboxes-dynamic"
    },
    {
      "label": "Checkbox option 0",
      "id": "checkbox-0-default-20-plus",
      "value": 0,
      "name": "checkboxes-dynamic"
    }
  ],
  "modifier": "error"
}
  • Content:
    .checkbox-filter-dynamic {
    
      .modal-preview,
      .accordion-preview,
      .checkbox-accordion {
        .checkbox:last-child {
          margin-bottom: .5rem;
        }
      }
    
      .checkbox-accordion {
        .accordion--content {
          // Adjust for checkbox focus outline.
          margin: -4px -4px 0;
          padding: 4px 4px 0;
    
          transition: max-height .5s ease-in-out;
          overflow: hidden;
        }
    
        .accordion--button {
          &[aria-expanded=false] {
            @include icon(chevron-down);
          }
    
          &[aria-expanded=true] {
            @include icon(chevron-up);
          }
        }
      }
    }
    
    .checkbox-filter__modal {
      .tag-list-wrapper {
        min-height: 2.2rem;
        margin-bottom: 1.2rem;
      }
    
      .tag-list {
        .tag {
          margin: 0;
        }
      }
    }
    
  • URL: /components/raw/checkboxes-dynamic/_checkboxes-dynamic.scss
  • Filesystem Path: components/31-molecules/checkboxes-dynamic/_checkboxes-dynamic.scss
  • Size: 729 Bytes
  • Content:
    'use strict';
    
    (function () {
    
      if (Accordion) { // eslint-disable-line no-undef
        const selected = document.querySelectorAll('.checkbox-accordion');
        for (let i = selected.length; i--;) {
          new Accordion(selected[i]); // eslint-disable-line no-undef
        }
      }
    
      if (CheckboxFilterDynamic) { // eslint-disable-line no-undef
        const selected = document.querySelectorAll('.checkbox-filter-dynamic');
        for (let i = selected.length; i--;) {
          new CheckboxFilterDynamic(selected[i], {hiddenTagText: 'Remove tag'}); // eslint-disable-line no-undef
        }
      }
    
    })();
    
  • URL: /components/raw/checkboxes-dynamic/checkbox_dynamic.bindings.js
  • Filesystem Path: components/31-molecules/checkboxes-dynamic/checkbox_dynamic.bindings.js
  • Size: 579 Bytes
  • Content:
    'use strict';
    
    (function (root, factory) {
      if (typeof define === 'function' && define.amd) {
        define(factory);
      }
      else {
        if (typeof exports === 'object') {
          module.exports = factory();
        }
        else {
          root.CheckboxFilterDynamic = factory();
        }
      }
    })(this || window, function () {
      return function (elem, options) {
    
        options = options || {};
        options.filterField = options.filterField || '.checkbox-filter__filter';
        options.modalPreview = options.modalPreview || '.modal-preview';
        options.accordionPreview = options.accordionPreview || '.accordion-preview';
        options.accordionBtn = options.accordionBtn || 'button.accordion--button';
        options.previewCheckboxes = options.previewCheckboxes || 'div.checkbox.preview';
        options.checkboxes = options.checkboxes || 'div.checkbox:not(.preview)';
        options.selectedContainer = options.selectedContainer || '.checkbox-filter__selected';
        options.checkboxContainers = options.checkboxContainers || '.checkbox-filter__checkboxes';
        options.openBtn = options.openBtn || '.checkbox-filter__open';
        options.submitBtn = options.submitBtn || '.checkbox-filter__submit';
        options.closeBtns = options.closeBtns || '.checkbox-filter__close';
        options.resultSpan = options.resultSpan || '.checkbox-filter__result';
        options.makeTags = options.makeTags !== false;
        options.hiddenTagText = options.hiddenTagText || 'remove tag';
        options.onRemoveTag = options.onRemoveTag || function () {};
    
        /**
         * Filter input field.
         * @type {HTMLInputElement}
         */
        const filterField = elem.querySelector(options.filterField);
    
        /**
         * Container for the modal preview checkboxes.
         * @type {HTMLElement}
         */
        const modalPreview = elem.querySelector(options.modalPreview);
    
        /**
         * Container for the accordion preview checkboxes.
         * @type {HTMLElement}
         */
        const accordionPreview = elem.querySelector(options.accordionPreview);
    
        /**
         * Button to toggle the accordion.
         * @type {HTMLElement}
         */
        const accordionBtn = elem.querySelector(options.accordionBtn);
    
        /**
         * List of preview checkboxes, these exist outside of the modal.
         * @type {NodeList|Array}
         */
        const previewCheckboxes = elem.querySelectorAll(options.previewCheckboxes) || [];
    
        /**
         * List of checkboxwrappers, each containing a checkbox and a label.
         * @type {NodeList|Array}
         */
        const checkboxes = elem.querySelectorAll(options.checkboxes) || [];
    
        /**
         * Container to display the selected items.
         * @type {HTMLElement}
         */
        const selectedContainer = elem.querySelector(options.selectedContainer);
    
        /**
         * Container for the checkboxes.
         * @type {HTMLElement}
         */
        const checkboxContainers = elem.querySelectorAll(options.checkboxContainers);
    
        /**
         * Button to trigger opening the modal.
         * @type {HTMLElement}
         */
        const openBtn = elem.querySelector(options.openBtn);
    
        /**
         * Button to confirm the selection and close the modal.
         * @type {Element}
         */
        const submitBtn = elem.querySelector(options.submitBtn);
    
        /**
         * A list of elements to trigger closing the modal.
         * At least one must have the button role.
         * @type {NodeList}
         */
        const closeBtns = elem.querySelectorAll(options.closeBtns);
    
        /**
         * Container to display the number of search results.
         * @type {HTMLElement}
         */
        const resultSpan = elem.querySelector(options.resultSpan);
    
        /**
         * Store the checked checkboxes prior to making changes.
         * @type {Array}
         */
        let selectedFilters = [];
    
        /**
         * Filter the displayed checkboxes.
         * @param {boolean} clear Clear the filtervalue if true.
         */
        const filter = clear => {
          if (!filterField) {
            return;
          }
    
          if (clear) {
            filterField.value = '';
          }
    
          let count = 0;
    
          [].slice.call(checkboxContainers).forEach(container => {
            container.style.display = 'none';
          });
    
          checkboxLoop(({checkboxWrapper, checkbox, label}) => {
            if (
              !label ||
              label.textContent
                .toUpperCase()
                .indexOf(filterField.value.toUpperCase()) === -1
            ) {
              checkboxWrapper.setAttribute('hidden', 'true');
              checkbox.setAttribute('hidden', 'true');
            }
            else {
              checkboxWrapper.removeAttribute('hidden');
              checkbox.removeAttribute('hidden');
              count++;
            }
          });
    
          [].slice.call(checkboxContainers).forEach(container => {
            let displayedCount = container.querySelectorAll(`${options.checkboxes}:not([hidden])`).length;
            if (displayedCount) {
              container.style.display = '';
            }
          });
    
          if (resultSpan) {
            resultSpan.textContent = '' + count;
          }
        };
    
        /**
         * Make a tag.
         * @param {HTMLInputElement} checkbox Input type checkbox.
         * @param {Element} label Label for the input type checkbox.
         * @return {Element} A gent styleguide tag component.
         */
        const makeTag = (checkbox, label) => {
    
          const li = document.createElement('li');
    
          const tag = document.createElement('span');
          tag.className = 'tag filter';
          tag.textContent = label.textContent;
          tag.setAttribute('data-value', checkbox.value);
    
          const button = document.createElement('button');
          button.type = 'button';
          button.innerHTML = `<span class="visually-hidden">${options.hiddenTagText} ${label.textContent}</span>`;
          button.addEventListener('click', () => checkbox.click());
    
          tag.appendChild(button);
          li.appendChild(tag);
    
          return li;
        };
    
        /**
         * Remove a tag from the selectedContainer.
         * @param {HTMLInputElement} checkbox Input type checkbox.
         */
        const removeTag = checkbox => {
          if (options.makeTags && selectedContainer) {
            const tag = selectedContainer.querySelector('.filter[data-value="' + checkbox.value + '"]');
            if (tag) {
              selectedContainer.removeChild(tag.parentElement);
              options.onRemoveTag(checkbox, tag);
            }
          }
        };
    
        const addTag = (checkbox, label) => {
          if (options.makeTags && selectedContainer) {
            selectedContainer.appendChild(makeTag(checkbox, label));
          }
        };
    
        /**
         * Loop over all checkboxes and execute a callback for each iteration.
         * @param {function} next The callback function.
         */
        const checkboxLoop = next => {
          for (let i = 0; i < checkboxes.length; i++) {
            let checkboxWrapper = checkboxes[i];
            let checkbox = checkboxWrapper.querySelector('input[type=checkbox]');
            let label = checkboxWrapper.querySelector('label');
    
            // Sometimes the input element isn't rendered at this point.
            if (checkbox && label) {
              next({checkboxWrapper, checkbox, label});
            }
          }
        };
    
        /**
         * Reset the component to it's stored value.
         */
        const reset = () => {
          checkboxLoop(({checkbox}) => {
            const isSelected = selectedFilters.indexOf(checkbox) !== -1;
            if ((isSelected && !checkbox.checked) || (!isSelected && checkbox.checked)) {
              checkbox.click();
            }
          });
        };
    
        /**
         * Initialise the component.
         */
        const init = () => {
          checkboxLoop(({checkbox, label}) => {
            if (checkbox.checked && !checkbox.indeterminate) {
              addTag(checkbox, label);
            }
          });
          filter(true);
        };
    
        /**
         * Bind two checkboxes
         * @param {HTMLInputElement} a A checkbox.
         * @param {HTMLInputElement} b Another checkbox.
         */
        const bindState = (a, b) => {
          if (a.checked !== b.checked) {
            a.click();
          }
        };
    
        /**
         * Bind the preview checkboxes to their original counterpart in the modal.
         */
        const addPreviewCheckboxesEvent = () => {
          if (!previewCheckboxes.length) {
            return;
          }
    
          for (let i = previewCheckboxes.length; i--;) {
            const checkboxWrapper = previewCheckboxes[i];
            const checkbox = checkboxWrapper.querySelector('input[type=checkbox]');
            const original = elem.querySelector('#' + checkbox.getAttribute('data-original'));
    
            if (checkbox) {
              checkbox.addEventListener('change', () => bindState(original, checkbox));
              original.addEventListener('change', () => bindState(checkbox, original));
            }
          }
        };
    
        const addModalEvents = () => {
          // Enable opening the modal.
          if (openBtn) {
            openBtn.addEventListener('click', () => {
              selectedFilters = [];
    
              checkboxLoop(({checkbox}) => {
                if (checkbox.checked) {
                  selectedFilters.push(checkbox);
                }
              });
    
              document.addEventListener('keydown', handleKeyboardInput);
            });
          }
    
          // Add close events to all closeBtns.
          if (closeBtns) {
            for (let i = closeBtns.length; i--;) {
              closeBtns[i].addEventListener('click', () => {
                reset();
                document.removeEventListener('keydown', handleKeyboardInput);
              });
            }
          }
    
          // Update selectedFilters and close.
          if (submitBtn) {
            submitBtn.addEventListener('click', () => {
              document.removeEventListener('keydown', handleKeyboardInput);
            });
          }
    
          // Make sure the filter method is not repeated while typing.
          if (filterField) {
            let filterTimeOut = null;
            filterField.addEventListener('input', () => {
              if (filterTimeOut) {
                clearTimeout(filterTimeOut);
              }
              filterTimeOut = setTimeout(filter, 200);
            });
          }
        };
    
        const createPreviewClone = (checkboxWrapper, checkbox) => {
          if (!modalPreview) {
            return;
          }
          if (modalPreview.querySelector('#' + checkbox.id + '-preview')) {
            return;
          }
    
          const wrapperClone = checkboxWrapper.cloneNode(true);
          const checkboxClone = wrapperClone.querySelector('input');
          const labelClone = wrapperClone.querySelector('label');
    
          checkboxClone.name = null;
          checkboxClone.id += '-preview';
          labelClone.for += '-preview';
    
          checkbox.addEventListener('change', () => bindState(checkboxClone, checkbox));
          checkboxClone.addEventListener('change', () => bindState(checkbox, checkboxClone));
          modalPreview.appendChild(wrapperClone);
        };
    
        const addAccordionEvent = () => {
          if (!accordionBtn || !accordionPreview) {
            return;
          }
    
          accordionBtn.addEventListener('click', () =>
            checkboxLoop(({checkboxWrapper, checkbox}) => {
              if (checkbox.checked && !accordionPreview.contains(checkboxWrapper)) {
                accordionPreview.appendChild(checkboxWrapper);
              }
            })
          );
        };
    
        /**
         * Add all events.
         */
        const addEvents = () => {
    
          addModalEvents();
          addPreviewCheckboxesEvent();
          addAccordionEvent();
    
          // Add events for all checkboxes.
          checkboxLoop(({checkboxWrapper, checkbox, label}) => {
            checkbox.addEventListener('change', () => {
              if (checkbox.checked) {
                addTag(checkbox, label);
                createPreviewClone(checkboxWrapper, checkbox);
              }
              else {
                removeTag(checkbox);
              }
            });
          });
        };
    
        /**
         * Handle keyboard input
         * @param {object} e event
         */
        const handleKeyboardInput = e => {
          let keyCode = e.keyCode || e.which;
          if (keyCode === 27) {
            e.preventDefault();
            reset();
          }
        };
    
        init();
        addEvents();
    
        return {};
      };
    });
    
  • URL: /components/raw/checkboxes-dynamic/checkbox_dynamic.functions.js
  • Filesystem Path: components/31-molecules/checkboxes-dynamic/checkbox_dynamic.functions.js
  • Size: 11.8 KB