javascriptvue.jsmulti-selectcore-ui

Vue3 CoreUI CMultiSelect submitting unintentionally when 'Select all options' is clicked


I'm fairly new to coding in Vue.js (3) and the project I have stepped into uses CoreUI for Vue as its main framework for forms and displays. I'm not sure if this is a bug in the CoreUI CMultiSelect itself, but when a user clicks 'Select all options' in one of the form dropdowns, the form automatically submits before the user can fill out the rest/submit it for themselves. I'm especially confused because even when I disconnect the component from the custom @change (I just deleted the line) defined to save their selection, it still demonstrates the problem. Any help would be appreciated!

Side note: I am also unfamiliar how to disable the select-all option, though it mentions this is possible in the CoreUI Vue MultiSelect Documentation. I set select-all="false" within the component to no avail. If this is what I have to do for the time being until I figure this out, I would appreciate guidance on how to do so as well.

Here are the component and selection options:

<CMultiSelect
  placeholder="Application Types"
  :options="battery_application_choices"
  @change="batteryappChanged($event)"
>

<!-- inside data() -->
battery_application_choices: [
  { text: 'Demand Reduction', value: 'Demand Reduction' },
  { text: 'Energy Arbitrage', value: 'Energy Arbitrage' },
  { text: 'Frequency Response', value: 'Frequency Response' },
  { text: 'Microgrid Component', value: 'Microgrid Component' },
  { text: 'Other', value: 'Other' },
  { text: 'Solar / Storage', value: 'Solar / Storage' },
  { text: 'UPS', value: 'UPS' },
],

Select All Bug. I am not performing any other action besides clicking the 'Select all options' button


Solution

  • Answer from myself since that's how coding is sometimes.

    1) Temporary Fix

    I found how to implement my temporary fix, where I needed to type :select-all="false" with a colon to bind it instead of select-all="false". For newbies like me, : is short for v-bind in Vue, which binds data to components so that when the data changes, the state of the app changes to reflect it.

    Several days later...
    2) Robust Fix

    In order to describe the robust solution to this problem, I will be outlining what I learned about JS events and how Vue handles watching them and their propagation. I will also be covering a bit of how Vue uses what you write to generate HTML code, specifically how the CoreUI multiselect component generates HTML elements like <select> and <options>.

    What was causing the error was the fact that the <CMultiSelect> was nested in a parent <CForm>. This in itself is not bad practice, and is even practical given that some forms should have multiple selection fields alongside their other form fields. However, a problem arose when a user clicked the 'Select all options' button. This is because putting a <CMultiSelect> into your code instructs Vue to compose a collection of elements at runtime that are abstracted away from you, the developer. Vue generates a <select> block that contains an <option> element for each option that you bind to the <CMultiSelect>. It also generates a 'Select all options' <button> that for some reason can produce a SubmitEvent. When this event is produced, it propagates upward since it was not stopped with event.preventDefault() or event.stopPropagation(). It gets caught by the submission watcher of the parent <CForm> and triggered the custom form submission in my project. This behavior was also caused by the small x buttons to delete tags from the multiselect view, but I did not look into the generated code with console.log()s enough to figure out why.

    I was able to fix this problem by checking the submitter property of event.

    Side note: I would recommend doing a quick console.log(event) in any event handler methods that you are having trouble with, since events are much more complex than I thought and you will be able to see properties that can be of use to you.

    event.submitter is a property of submit events, and returns the HTML element that generated that particular submission. In my case, it returned the 'submit(?)' button of the <CMultiSelect> which was of the form <button ...>Select all options</button>. I then accessed just the contents with event.submitter.innerHTML (which produced just Select all options) and if it matched this content, do nothing. This could also be used for a form with multiple submission buttons that trigger different types of submission (perhaps alternate processing of user data), because it allows you to tell where the event is coming from.

    For example, the below form...
    <form @submit.prevent="customHandler($event)">
        <button type='submit'>Save</button>
        <button type='submit'>Change</button>
    </form>
    
    ...Could have the following control logic in its handler method:
    customHandler(event) {
        if (event.submitter.innerHTML == 'Save') {
            // Save user data
        } else if (event.submitter.innerHTML == 'Change') {
            // Change user data in different way than 'Save'
        } else {
            // Do nothing
            // The CMultiSelect's 'Select all options' event will fall here
        } 
    }
    
    What did we learn?

    Since this issue took about 3 cumulative workdays out of my schedule I figured the post I made about it should carry the same weight. I know this is overkill but if even one other developer can avoid the same problem then it'll have been more than worth it. I would say I learned that JavaScript is so much more complicated than I thought imaginable, yet frustratingly stupid at the same time, but that would mean that I didn't know that before...