Choices-js / Choices

A vanilla JS customisable select box/text input plugin ⚡️
https://choices-js.github.io/Choices/
MIT License
6.25k stars 617 forks source link

error Uncaught InvalidCharacterError #1189

Closed tyzberd closed 3 months ago

tyzberd commented 3 months ago

The error occurs in version 11.0.1, but it is not present in version 11.0.0.

jquery-3.6.3.min.js:2 Uncaught InvalidCharacterError: Failed to execute 'add' on 'DOMTokenList': The token provided ('choices choices-currency st-dropdown') contains HTML space characters, which are not valid in tokens. at k (choices.min.js:2:2727) at e.containerOuter (choices.min.js:2:37427) at e._createElements (choices.min.js:2:71755) at e.init (choices.min.js:2:45942) at new e (choices.min.js:2:45495) at HTMLSelectElement. (form-payment-all-country.js:109:36) at Function.each (jquery-3.6.3.min.js:2:3003) at E.fn.init.each (jquery-3.6.3.min.js:2:1481) at Object.init (form-payment-all-country.js:108:34) at HTMLDocument. (form-payment-all-country.js:192:14)

I am calling this code.

let dropdown = {
        choices: $('.js-choice'),
        choicesStandart: $('.js-choice-standart'),
        init: function () {
            this.choices.each(function () {
                let calcFromDropdownChoices = new Choices(
                    $(this)[0],
                    {
                        classNames: {
                            containerOuter: 'choices',
                            selectedState: 'is-selected',
                        },
                        itemSelectText: '',
                        searchEnabled: false,
                        shouldSort: false,
                        allowHTML: true,
                        callbackOnCreateTemplates: function (template) {
                            return {
                                item: ({classNames}, data) => {
                                    return template(`
          <div class="${classNames.item} ${
                                        data.highlighted
                                            ? classNames.highlightedState
                                            : classNames.itemSelectable
                                    } ${
                                        data.placeholder ? classNames.placeholder : ''
                                    }" data-item data-id="${data.id}" data-value="${data.value}" ${
                                        data.active ? 'aria-selected="true"' : ''
                                    } ${data.disabled ? 'aria-disabled="true"' : ''}>
<span>${data.label}</span>
          </div>
        `);
                                },
                                choice: ({classNames}, data) => {
                                    return template(`
          <div class="${classNames.item} ${classNames.itemChoice} ${data.selected ? classNames.selectedState : ''} ${
                                        data.disabled ? classNames.itemDisabled : classNames.itemSelectable
                                    }" data-select-text="${this.config.itemSelectText}" data-choice ${
                                        data.disabled
                                            ? 'data-choice-disabled aria-disabled="true"'
                                            : 'data-choice-selectable'
                                    } data-id="${data.id}" data-value="${data.value}" ${
                                        data.groupId > 0 ? 'role="treeitem"' : 'role="option"'
                                    }>
<span>${data.label}</span>
          </div>
        `);
                                },
                            };
                        },
                    }
                );
            });

            this.choicesStandart.each(function () {
                let calcDropdown = new Choices(
                    $(this)[0],
                    {
                        classNames: {
                            containerOuter: 'choices choices-currency st-dropdown',
                        },
                        itemSelectText: '',
                        searchEnabled: false,
                        shouldSort: false,
                        allowHTML: true,
                        callbackOnCreateTemplates: function (template) {
                            return {
                                item: ({classNames}, data) => {
                                    return template(`
          <div class="${classNames.item} ${
                                        data.highlighted
                                            ? classNames.highlightedState
                                            : classNames.itemSelectable
                                    } ${
                                        data.placeholder ? classNames.placeholder : ''
                                    }" data-item data-id="${data.id}" data-value="${data.value}" ${
                                        data.active ? 'aria-selected="true"' : ''
                                    } ${data.disabled ? 'aria-disabled="true"' : ''}>
<img src="/img/payments-icons/${data.customProperties.imageName}" class="choice__icon">
<span>${data.label}</span>
          </div>
        `);
                                },
                                choice: ({classNames}, data) => {
                                    return template(`
          <div class="${classNames.item} ${classNames.itemChoice} ${
                                        data.disabled ? classNames.itemDisabled : classNames.itemSelectable
                                    }" data-select-text="${this.config.itemSelectText}" data-choice ${
                                        data.disabled
                                            ? 'data-choice-disabled aria-disabled="true"'
                                            : 'data-choice-selectable'
                                    } data-id="${data.id}" data-value="${data.value}" ${
                                        data.groupId > 0 ? 'role="treeitem"' : 'role="option"'
                                    }>
            <img src="/img/payments-icons/${data.customProperties.imageName}" class="choice__icon">      
            <div class="choice__content">
                <span>${data.label}</span>
                 ${data.customProperties.dropdownInfo !== undefined
                                        ? `<div class="choice__info">Комиссия в RUB, ${data.customProperties.dropdownInfo}</div>`
                                        : ''}                    

            </div>
          </div>
        `);
                                },
                            };
                        },
                    }
                );
                $(this).data('tt', calcDropdown);

                calcDropdown.passedElement.element.addEventListener(
                    'showDropdown',
                    function (event) {
                        setTimeout(function () {
                            $('body').addClass('st-dropdown-open');
                            $('.js-overlay').show();
                        }, 0)
                    },
                    false,
                );
                calcDropdown.passedElement.element.addEventListener(
                    'hideDropdown',
                    function (event) {
                        setTimeout(function () {
                            $('body').removeClass('st-dropdown-open');
                            $('.js-overlay').hide();
                        }, 0)
                    },
                    false,
                );
            });
        }
    }
    dropdown.init();
tyzberd commented 3 months ago

Everything works fine in version 10.2.0. Maybe the issue is on my side.

Xon commented 3 months ago

The issue is with your callbackOnCreateTemplates code. Various classNames entries can be arrays not just a single string.

The code (including the example in the readme), use getClassNames. This might not be exported depending on how you are using the bundles but it is simple enough to re-implement;

export const getClassNames = (ClassNames: Array<string> | string): Array<string> => {
  return Array.isArray(ClassNames) ? ClassNames : [ClassNames];
};
Xon commented 3 months ago

1190 will expose getClassNames as the 3rd argument to callbackOnCreateTemplates.

With v11.0.2 (not yet released), you will be able to replace the line: callbackOnCreateTemplates: function (template) { with;

callbackOnCreateTemplates: function (template, escapeForTemplate, getClassNames) {