skip to Main Content

In laravel 10/ livewire 3/ tailwindcss 3 app I need to make multiselect working like select2, but without jquery used, whuch is required by select2.

I found this https://github.com/alexpechkarev/alpinejs-multiselect code and try to implement in my blade file of simple livewire component I created with command :

php artisan make:livewire Test

I did not modify component file, but in blade file I edited :

<div class="admin_page_container" id="app_image_admin_page_container" x-cloak>

    <div class="w-full" x-data="alpineMuliSelect({selected:['te_11', 'te_12'], elementId:'multSelect'})">

        <select style="display:none;" id="multSelect">
            <option value="te_1" data-search="arsenal">Arsenal</option>
            <option value="te_3" data-search="Tottenham Hotspur Spurs">Spurs</option>
            <option value="te_3" data-search="Manchester City">Man City</option>
        </select>
    </div>
</div>

and I added js code into resources/js/app.js :

import './bootstrap';
import Alpine from 'alpinejs';

window.Alpine = Alpine;

import focus from '@alpinejs/focus'

import flatpickr from "flatpickr";


// I ADDED THESE LINES BEFORE Alpine.start() method
document.addEventListener("alpine:init", () => {
    window.Alpine.data("alpineMuliSelect", (obj) => ({
        elementId: obj.elementId,
        options: [],
        selected: obj.selected,
        selectedElms: [],
        show: false,
        search: '',
        open() {
            this.show = true
        },
        close() {
            this.show = false
        },
        toggle() {
            this.show = !this.show
        },
        isOpen() {
            return this.show === true
        },

        // Initializing component
        init() {
            const options = document.getElementById(this.elementId).options;
            for (let i = 0; i < options.length; i++) {

                this.options.push({
                    value:  options[i].value,
                    text:   options[i].innerText,
                    search: options[i].dataset.search,
                    selected: Object.values(this.selected).includes(options[i].value)
                });

                if (this.options[i].selected) {
                    this.selectedElms.push(this.options[i])
                }
            }

            // searching for the given value
            this.$watch("search", (e => {
                this.options = []
                const options = document.getElementById(this.elementId).options;
                Object.values(options).filter((el) => {
                    var reg = new RegExp(this.search, 'gi');
                    return el.dataset.search.match(reg)
                }).forEach((el) => {
                    let newel = {
                        value: el.value,
                        text: el.innerText,
                        search: el.dataset.search,
                        selected: Object.values(this.selected).includes(el.value)
                    }
                    this.options.push(newel);
                })
            }));
        },
        // clear search field
        clear() {
            this.search = ''
        },
        // deselect selected options
        deselect() {
            setTimeout(() => {
                this.selected = []
                this.selectedElms = []
                Object.keys(this.options).forEach((key) => {
                    this.options[key].selected = false;
                })
            }, 100)
        },
        // select given option
        select(index, event) {
            if (!this.options[index].selected) {
                this.options[index].selected = true;
                this.options[index].element = event.target;
                this.selected.push(this.options[index].value);
                this.selectedElms.push(this.options[index]);

            } else {
                this.selected.splice(this.selected.lastIndexOf(index), 1);
                this.options[index].selected = false
                Object.keys(this.selectedElms).forEach((key) => {
                    if (this.selectedElms[key].value == this.options[index].value) {
                        setTimeout(() => {
                            this.selectedElms.splice(key, 1)
                        }, 100)
                    }
                })
            }
        },
        // remove from selected option
        remove(index, option) {
            this.selectedElms.splice(index, 1);
            Object.keys(this.options).forEach((key) => {
                if (this.options[key].value == option.value) {
                    this.options[key].selected = false;
                    Object.keys(this.selected).forEach((skey) => {
                        if (this.selected[skey] == option.value) {
                            this.selected.splice(skey, 1);
                        }
                    })
                }
            })
        },
        // filter out selected elements
        selectedElements() {
            return this.options.filter(op => op.selected === true)
        },
        // get selected values
        selectedValues() {
            return this.options.filter(op => op.selected === true).map(el => el.value)
        }
    }));
});


Alpine.plugin(focus)
Alpine.start();

But I got error :

livewire.js?id=5d8beb2e:1192 Alpine Expression Error: alpineMuliSelect is not defined

Expression: "alpineMuliSelect({selected:['te_11', 'te_12'], elementId:'multSelect'})"

Not shure in which way have I to describe alpineMuliSelect method ?

UPLOADED EXAMPLE :

I uploaded test app with this example at https://github.com/sergeynilov/TestApp

Standart laravel/livewire app.
To instal alpinejs I used command

npm install alpinejs

Running server with command :

php artisan serve 

I open home page at http://127.0.0.1:8000
where I put select input

I created a component :

php artisan livewire:make HomePage  

Added small count component just be sure that alpine works ok
I left misspeling in “alpineMuliSelect” function .

In resources/js/app.js I added definition of “alpineMuliSelect”.
I got the same error as on my page.

UPDATED INFO :
I removed alpinejs from package.json and all alpinejs code from resources/js/app.js

Now I do not have any errors in console, but select input is not visible and checking in browser I see it has

style="display:none;"

enter image description here
from

I added debugging in resources/js/app.js :

import './bootstrap';
document.addEventListener("alpine:init", () => {
    console.log('resources/js/app.js ::')  // I SEE THIS MESSAGE
    window.Alpine.data("alpineMuliSelect", (obj) => ({
        elementId: obj.elementId,
        options: [],
        selected: obj.selected,
        selectedElms: [],
        show: false,
        search: '',
        open() {
            console.log('resources/js/app.js OPEN::')
            this.show = true
        },
        close() {
            console.log('resources/js/app.js CLOSE::')
            this.show = false
        },
        toggle() {
            this.show = !this.show
        },
        isOpen() {
            console.log('resources/js/app.js isOpen::')
            return this.show === true
        },

        // Initializing component
        init() {
            const options = document.getElementById(this.elementId).options;
            console.log('resources/js/app.js init options::') // I SEE THIS MESSAGE
            console.log(options)
            for (let i = 0; i < options.length; i++) {
                ...

and checking output I see that alpineMuliSelect is triggered

enter image description here

After I removed

style="display:none;"

from select input – in this case I see usual select input visible.

Any ideas ?

2

Answers


  1. I had a bit of a run with your code. Pulled locally and I was able to recreate your issue.

    TL;DR: Alpine Js seems to be re-initiating itself and didn’t know how to handle the registration of your component.

    Understanding this Github discussion led me closer to the problem you were having. Worth a quick look to see how Alpine works.

    Now to go in-depth:

    Alpine Js is registered in the app already. I would assume it comes bundled with Livewire out of the box. Therefore, in your app.js, remove every form of Alpine Js initialization there. You don’t have to import it as well.

    Essentially, the only thing that should be in your app.js is:

    import './bootstrap';
    

    You should no longer see an error (this is something you already tried earlier but you were unsure I think.

    The next problem you faced was having the select box hidden because of style="display:none;" attribute. If you go by the original code of the alpinejs-multiselect project, you will find that this is the expected behavior. The select box is really not displayed, as the creator used class="hidden" as well. This is because another input box referenced by:

    <input name="teams[]" type="hidden" x-bind:value="selectedValues()">
    is what displays the selected options. The selectbox just holds values for Alpine to manipulate.

    Although, I moved the Alpine.data bit to the blade file just to get things working, I believe you can move it back to your app.js and things should work just fine.

    Lastly, I would advise that you clone the code of the original alpine multiselect project, then remove the parts you don’t need. I did that and it worked fine within your project.

    I am also making a PR that proves all what I’ve said, to give you a headstart.
    Hopefully, this works and you’re able to carry on with your project!

    PR: https://github.com/sergeynilov/TestApp/pull/1

    Again, in case you missed it the first time, try to pull in the entire multiselect code from the originator and style the way you want. All your worries should be gone that way!

    Cheers!

    Login or Signup to reply.
  2. Da quello che capisco hai già seguito il mio suggerimento e le indicazioni sul sito di Livewire, quindi dovresti aver risolto il problema dell’inclusione di AlpineJs.

    By cloning your repository I see that you have inserted the <select> tag with its <options>, however the entire part of the view that must manage the multi-select is missing. Unfortunately the repo "alexpechkarev/alpinejs-multiselect" is not well maintained and at first glance it is a bit difficult to understand how to implement it.
    In addition to the Javascript code

    Alpine.data("alpineMuliSelect", (obj) => ({ .....
    

    the entire HTML part that displays the new multi-select control must be included in the view, as the <select> tag only serves to provide the options and is then no longer used, in fact it is hidden because the multi-select will be displayed instead.

    To understand better, in the alex karev/alpine js-multiselect repo you must go to the docs folder and open the index.html file: the HTML of the component starts from line 37 and ends at line 184 (or from 35 to 185 if you also want to include the external div)

    The value of the input is then assigned to the tag with name=teams[]" set as an array but which will contain a single line containing the values ​​separated by commas.

    Keep in mind that this input, as it is, can hardly be passed to the Livewire class with a wire:model as its value is set by the Alpine code and not entered by the user: basically it is set up for a classic HTML form which will send the data with a POST or a GET: is this scenario good for you?
    Also, if you were to use this type of control multiple times, as it is set up, it would be impractical as the HTML would have to be replicated each time.

    As for the Javascript code, you can leave it at the bottom of the component as in the original, you can keep it in app.js (but if you add more code of this type it starts to become heavy to read) or you can create an external JS file (e.g. : resources/js/alpineMultiSelect.js) and include it with an import inserted into app.js (e.g. ‘import ./alpineMultiSelect.js’)

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search