livewire에서 fuse.io select 새로운 방법으로 작업

Updated on

                                                    <div class="col-span-8 md:col-span-4">
                                                        <label class="block text-sm font-medium text-gray-700">
                                                            거래처
                                                        </label>
                                                        <div wire:ignore
                                                            class="mt-1">
                                                            <div x-data="
{
    value: @entangle('company_id'),
    open: false,
    data: {},
    focusedOptionIndex: null,
    options: {},
    input: '',
    previousInput: '',
    fuse: null,
    ing: false,
    disabled: false,

    fuseInit: function () {
        console.log(this.data)
        this.fuse = new Fuse(this.data, { includeMatches: true, threshold: 0.1, keys: ['name'] })
    },

    selectClick: function (data) {
        this.open = !this.open
        if(this.fuse === null) {
            this.data = data
            this.fuseInit()
        }
    },

    openListbox: function (data) {
       this.$nextTick(() => {
           this.$refs.listbox.style.width = `${this.$refs.select.getBoundingClientRect().width}px`;
           this.$refs.input.focus();
       })
    },

    closeListbox: function () {
        this.focusedOptionIndex = null
        this.options = {}
        this.input = ''
        this.previousInput = ''
        this.ing = false
    },

    focusNextOption: function () {
        if (this.focusedOptionIndex + 1 >= Object.keys(this.options).length) return
        this.focusedOptionIndex++

        if(this.options[this.focusedOptionIndex] !== undefined) {
            let pos = getRelativePos(this.$refs.list.children[this.focusedOptionIndex+1]);
            if(pos.next !== undefined) {
                this.$refs.list.scrollTop = pos.next;
            }
        }
    },

    focusPreviousOption: function () {
        if (this.focusedOptionIndex <= 0) return
        this.focusedOptionIndex--
            if(this.options[this.focusedOptionIndex] !== undefined) {
                let pos = getRelativePos(this.$refs.list.children[this.focusedOptionIndex+1]);
                if(pos.next !== undefined) {
                    this.$refs.list.scrollTop = pos.next;
                }
        }
    },

    search: function () {
        if(this.previousInput === this.input) return
        this.options = this.fuse.search(this.input, { limit: 25 })
        this.focusedOptionIndex = 0
        this.$refs.list.scrollTop = 0
        this.ing = false

        this.previousInput = this.input
    },

    selectOption: function () {
        if(this.input !== '' && this.options[this.focusedOptionIndex] !== undefined) {
            let item = this.options[this.focusedOptionIndex].item
            this.value = {'id': item.id, 'name':item.name}
        }

        this.$refs.select.focus();

        this.open = false
    },

    resize: function () {
        if(this.open) this.$refs.listbox.style.width = `${this.$refs.select.getBoundingClientRect().width}px`;
    },

    closeEvent: function () {
        this.open = false
        this.fuse = null
        this.disabled = false
    },

    disabledSet: function () {
        if($wire.get('submit') === 'update') {
            this.disabled = true;
        }
    },
}"

                                                                 x-init="
console.log(`select2-init`);
$watch('open', (value) => { if(value === true) { $nextTick(() => { openListbox() }) } else { closeListbox() } });
$watch('input', (value) => { ing = true });"
                                                                 @open-event.window="disabledSet()"
                                                                 @close-event.window="closeEvent()"
                                                                 x-on:resize.window.prevent="resize()"
                                                            >
                                                                <div class="relative">
                                                                    <input type="text" class="shadow-sm focus:ring-gray-500 focus:border-gray-500 block w-full text-xs sm:text-sm border-gray-300 cursor-pointer"
                                                                           placeholder="선택"
                                                                           readonly
                                                                           :class="{ 'rounded-t-md' : open , 'rounded-md' : !open, 'cursor-not-allowed bg-gray-200' : disabled === true }"
                                                                           x-ref="select"
                                                                           x-model="value.name"
                                                                           x-on:click="selectClick($component('collection').company_list)"
                                                                           x-on:keydown.enter.prevent="selectClick($component('collection').company_list)"
                                                                           x-bind:disabled="disabled"
                                                                    >
                                                                    <div class="flex-col fixed bg-white"
                                                                         x-ref="listbox"
                                                                         x-show="open"
                                                                         x-cloak>
                                                                        <input type="text" class="shadow-sm focus:ring-gray-500 focus:border-gray-500 block w-full text-xs sm:text-sm border-gray-300"
                                                                               placeholder="내용 입력"
                                                                               x-model="input"
                                                                               x-ref="input"
                                                                               x-on:click.away="open = false"
                                                                               x-on:input.debounce.250="search()"
                                                                               x-on:keydown.enter.prevent="selectOption()"
                                                                               x-on:keydown.arrow-up.prevent="focusPreviousOption()"
                                                                               x-on:keydown.arrow-down.prevent="focusNextOption()"
                                                                        >
                                                                        <div class="max-h-60 overflow-auto border-2 border-t-0"
                                                                             x-ref="list">
                                                                            <template x-for="(key, index) in Object.keys(options)" :key="index">
                                                                                <div class="p-2 bg-white border-gray-300 focus:border-gray-500 cursor-pointer"
                                                                                     x-on:click="selectOption()"
                                                                                     x-on:mouseenter="focusedOptionIndex = index"
                                                                                     role="option"
                                                                                     :aria-selected="focusedOptionIndex === index"
                                                                                     :class="{ 'text-white bg-gray-500': index === focusedOptionIndex, 'text-gray-900': index !== focusedOptionIndex }"
                                                                                >
                                                                                            <span x-text="options[index].item.name"
                                                                                                  :class="{ 'font-semibold': index === focusedOptionIndex, 'font-normal': index !== focusedOptionIndex }"
                                                                                                  class="block font-normal truncate text-xs md:text-sm xl:text-base"
                                                                                            ></span>
                                                                                </div>
                                                                            </template>
                                                                            <div class="p-2 bg-white border-gray-300 focus:border-gray-500 cursor-not-allowed text-blue-400 text-xs md:text-sm xl:text-base"
                                                                                 x-show="ing === true">검색 중...</div>
                                                                            <div class="p-2 bg-white border-gray-300 focus:border-gray-500 cursor-not-allowed text-red-400 text-xs md:text-sm xl:text-base"
                                                                                 x-show="options.length === 0 && input !== '' && ing === false">검색 결과 없음</div>
                                                                            <div class="p-2 bg-white border-gray-300 focus:border-gray-500 cursor-not-allowed text-gray-500 text-xs md:text-sm xl:text-base"
                                                                                 x-show="(options.length === 0 || options.length === undefined) && input === '' && ing === false">검색 내용 입력 (1자리 이상)</div>
                                                                        </div>
                                                                    </div>
                                                                </div>
                                                            </div>
                                                        </div>
                                                        @error('company_id')<p class="mt-1 text-sm text-red-500">{{ $message }}</p>@enderror
                                                    </div>

    <div
        wire:ignore
        x-id="collection"
        x-data="{
        company_list: [],
        product_list: [],
        fetch() {
            console.log(`data-fetch`);
            $fetch({ url: '/request/company/3', method: 'get' }).then(({ data }) => {
                this.company_list = data
            })
            $fetch({ url: '/request/product', method: 'get' }).then(({ data }) => {
                this.product_list = data
            })
        },
    }"
         @open-event.window="fetch">
    </div>

이것이 기존의 코드이다…
modal을 open 할 경우, 해당 값을 받아올 수 있는 페이지에 요청을 하고, return 값으로 데이터를 받아서, collection x-data에 저장해두었다.

그리고, select box를 클릭할때에, fuse 를 생성해주었다.

하지만, 여기에는 문제가 있었는데…
[추가]버튼을 눌러서, 똑같은 select 박스를 여러개 생성해야 되는데…
이렇게하면, 똑같은 select 박스를 클릭할때마다, 해당 x-data에 fuse가 생성되었다.

너무 무쓸모한것 같아서… 방법이 없을까 하고 생각하다가 생각해내서 한 코드는 아래와 같다.

    <div wire:ignore>
        <div
            x-id="fuse-company"
            x-data="{
            fuse: null,
            fetch() {
                if(this.fuse === null) {
                    $wire.call('getCompanyList').then((value) => {
                      this.fuse = new Fuse(value, { includeMatches: true, threshold: 0.1, keys: ['name'] })
                    })
                }
            },
            }"
            @open-event.window="fetch"></div>
    </div>

collection x-data에 저장하던 녀석이랑 같은데,
모달이 열리면, 차라리 fuse-company 로 명칭한 x-data 녀석에 아예 데이터를 받아와 fuse로 생성해서 보관하고 있는 것이다.

또한, 바뀐것은 laravel 페이지요청이 아닌, livewire 해당 모달 내에 method 를 호출함으로서… 관리용이하게 바꿔두었다.

$wire.call('getCompanyList')

toArray() 형태로 데이터를 받아와서, 바로 fuse로 생성해둔다.

this.options = $component('fuse-company').fuse.search(this.input, { limit: 25 })

그렇게, 기존의 search() 부분에서 직접 fuse-company의 fuse로 요청을 보내, return 값을 해당 select box 결과 값에 저장시켜서 작동되게 했다.

테스트 해본 결과, 문제없이 잘됨. 굿 !