DashboardCode / BsMultiSelect

BsMultiselect is "multiselect input" plugin that reuses your Bootstrap 4 theme and doesn't bring its own css (unless you would like to add it).
https://dashboardcode.github.io/BsMultiSelect/
Apache License 2.0
107 stars 29 forks source link

The change event is not firing #44

Closed MikeAlhayek closed 3 years ago

MikeAlhayek commented 3 years ago

First of all, thanks a lot for this plugin!

I am trying to listen for the change event. Here is what I have done

// drilldown.js
const modelsMenu = document.querySelector('.drilldown-models-menu');

if(modelsMenu != null) {
    modelsMenu.addEventListener('change', e => {
        console.log('change was fired');
    });
}

// this works!
window.addEventListener("load", function () {
    var event = new Event('change');
    modelsMenu .dispatchEvent(event);
});
// activator.js
$(function () {
    // is there no way to this plugin without jQuery?
    $('.content-picker').each((_i, item) => {
        var menu = $(item);
        menu.bsMultiSelect({
            placeholder: menu.attr('placeholder') || ''
        });
    });

});

But for some reason the change event isn't firing. When I manually dispatch the change event on load, it works. But when I select an item from the content-picker menu the change event does not fire. Am I doing something wrong here?

rpokrovskij commented 3 years ago

I see you have initiated the plugin as $(element).bsMultiSelect() And if you initiate it as jQuery plugin, use jquery events $.on. They are not the same as DOM events, compleatly different beast. This is standard jquery plugins convention: jquery events for jquery plugin, dom events if you initiate without jquery. If you think I understand it wrong please show me popular plugins that do it different way (may be this is obsolette convention?).

MikeAlhayek commented 3 years ago

@rpokrovskij I guess I understood it wrong. Since this is a bootstrap 5 plug-in, is there a way to not depend on jQuery? Is there a way to initialize it using vanilla js without jQuery?

rpokrovskij commented 3 years ago
  1. https://dashboardcode.github.io/BsMultiSelect/snippetEsm.html
  2. https://dashboardcode.github.io/BsMultiSelect/snippetUmd.html
MikeAlhayek commented 3 years ago

@rpokrovskij Thanks for your help. I am a bit struggling to initialize the plugin. With out using Module/import, how can one create a new instance?

I am using gulp to concatenate js code into a single file. so in the gulp task I included BsMultiSelect.esm.js along with the below code. But, that did not work.

let menus = document.getElementsByClassName('content-picker');

for (let i = 0; i < menus.length; i++) {
    new BsMultiSelect(menus[i], {
        placeholder: menus[i].getAttribute('placeholder') || ''
    });
}

I also tried the following also, but that gives me dashboardcode undefined.

let menus = document.getElementsByClassName('content-picker');

for (let i = 0; i < menus.length; i++) {
    dashboardcode.BsMultiSelect(menus[i], {
        placeholder: menus[i].getAttribute('placeholder') || ''
    });
}

Thanks for your help

rpokrovskij commented 3 years ago

Use second method.

  1. https://dashboardcode.github.io/BsMultiSelect/snippetUmd.html

If you load UMD bundle (not ESM!) you have window.DashboardCode.BsMultiSelect as well as $.fn.BsMultiSelect (if there is $) - here I forward bootstrap 5 convention.

MikeAlhayek commented 3 years ago

@rpokrovskij I included node_modules/@dashboardcode/bsmultiselect/dist/js/BsMultiSelect.bs4.js << is this the UMD? I then added the following code

let menus = document.getElementsByClassName('content-picker');

for (let i = 0; i < menus.length; i++) {
    dashboardcode.BsMultiSelect(menus[i], {
        placeholder: menus[i].getAttribute('placeholder') || ''
    });
}

but that gives me an error

Uncaught ReferenceError: dashboardCode is not defined
rpokrovskij commented 3 years ago

yes this is umd (you can open it and see the UMD template at the start)

dashboardCode ?

I mean in error . Should be dashboardcode . https://dashboardcode.github.io/BsMultiSelect/snippetUmd.html (open as source also, it is also usefull)

MikeAlhayek commented 3 years ago

@rpokrovskij Thanks for your continuous help!

Its running with no error now, but the change event isn't firing. here is the code that I am using

let menus = document.getElementsByClassName('content-picker');

for (let i = 0; i < menus.length; i++) {
    menus[i].addEventListener('change', (e) => {
        console.log('change event was fired for BsMultiSelect ....');
    });

    dashboardcode.BsMultiSelect(menus[i], {
        placeholder: menus[i].getAttribute('placeholder') || ''
    });

    console.log('registering BsMultiSelect...');
}

I am including node_modules/@dashboardcode/bsmultiselect/dist/js/BsMultiSelect.bs4.js

I am expecting the console.log('change event was fired for BsMultiSelect ....'); to get called every time I pick/remove an item from the menu. The BsMultiSelect is active on the element

rpokrovskij commented 3 years ago

Thank you for the report. It should work, going to check it.

rpokrovskij commented 3 years ago

It just works. Please fill snippet with yours specific https://codepen.io/rpokrovskij/pen/qBjJvwN

MikeAlhayek commented 3 years ago

I guess it works with bs5 not 4. Here is a snippet where it does not work https://codepen.io/crestapps/pen/eYRPodN

rpokrovskij commented 3 years ago

No, it is just because of jquery is found (there I follow bootstrap - it have the same behaivour). Also bootstrap is seaching for 'data-bs-no-jquery' attribute to disable "attaching to jquery", decision which I do not like at all -> it forces plugins be loaded after body is loaded . But it is supported.

Option that I would prefer (if I need): load bsMultiSelect before jquery and after popper. Switch <script> order!

Also do you really need change as an event? It is "not versatile" snce would not work if you initiate DIV (and use js data) . I would in all casses inject my handler with options

e.g. in sample

dashboardcode.bsMultiSelect(e, {
              setSelected: function(option /*element*/, value /*true|false*/){
                 // to update other controls consider to call your handler through window.setTimer 0; removing it from current event loop 
                  if (value) 
                      // ..
                  else  
                      // ..

              }
          }); 

But it doesn't work well "update all", "disable all" functionality. But again if you have this funcitonality - inject your code where you call "update all", "disable all".

What works best for you? What specific you consider.

MikeAlhayek commented 3 years ago

@rpokrovskij The reason why I want to use the change event, is because I have another script that should work with all selectors and listens for the change event. I tried to fire a custom event and add that custom ever to my other script but that did not work. by did not work i mean my other script did not fire when the custom ever was triggers

$(function () {

    let menus = document.getElementsByClassName('content-picker');

    for (let i = 0; i < menus.length; i++) {
        let element = menus[i];
        var menu = $(element);
        menu.bsMultiSelect({
            placeholder: menu.attr('placeholder') || ''
        });

        menu.on('change', (e) => {
            console.log('js change');
            var event = new Event('manualChange');
            element.dispatchEvent(event);
        });
    }

});

in the other script

const modelsMenu = document.querySelector('.drilldown-models-menu');

if (modelsMenu != null) {

    modelsMenu.addEventListener('change manualChange', e => {

     console.log('a change or manualChange...');
    });
}

but that either did not work.

Not sure what is the right approach here. my admin panel used bootstrap 4 but eventually will be converted to bootstrap 5. I need the js script to handle both scenarios so when I cover I Can just swap out dependencies.

rpokrovskij commented 3 years ago

bsmultiselect umd bundle for bootsrap 5 have the same logic - nothing would changed for you with migration to BS5 staying with UMD

let isJQyery = window.JQuery && !window.document.body.hasAttribute('data-bs-no-jquery'); // BS5 way
    if (isJQyery) {
        trigger = (e, eventName) => $(e).trigger(eventName);
    } else {
        trigger = composeEventTrigger(window);
    }

But I like your way "transform it to DOM event" : just stay with 'change' event not 'manualChange'. Why var event = new Event('manualChange');?

MikeAlhayek commented 3 years ago

@rpokrovskij for some reason when I fire the new Event('change'); it seems to be going into infinite loop where the jquery calls the dom event and the dom event calls js change event. Is there a way to fix that?

rpokrovskij commented 3 years ago

Ok then this way doesn't work, remove it. But as I said this works: just switch scripts places (put BsMultiSelect.js before jquery.js).

MikeAlhayek commented 3 years ago

@rpokrovskij loading BsMultiSelect.js before jquery.js worked!

Thank a lot!

rpokrovskij commented 3 years ago

Thank you for trying BsMultiSelect.

you can go to my SO account https://stackoverflow.com/users/506147/roman-pokrovskij?tab=questions and "vote" for my questions - if I could get answers on them then BsMultiSelect could become better