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