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 결과 값에 저장시켜서 작동되게 했다.
테스트 해본 결과, 문제없이 잘됨. 굿 !