From bfca0891e991861f2cd02174f38bd8b728a2448a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20Fie=C3=9Finger?= <41627893+bfiessinger@users.noreply.github.com> Date: Wed, 1 Sep 2021 14:27:47 +0200 Subject: [PATCH] fix cascading livewire select inputs nulled after repainting (multiple select) --- resources/views/default.blade.php | 5 +- resources/views/multiple.blade.php | 128 ++++++++++++++++------------- src/LivewireSelect.php | 48 ++++++----- 3 files changed, 102 insertions(+), 79 deletions(-) diff --git a/resources/views/default.blade.php b/resources/views/default.blade.php index 2655694..7b40484 100644 --- a/resources/views/default.blade.php +++ b/resources/views/default.blade.php @@ -8,7 +8,10 @@ class="{{ $styles['default'] }}" </option> @foreach($options as $option) - <option value="{{ $option['value'] }}"> + <option + value="{{ $option['value'] }}" + {!! (json_decode($initValueEncoded) && $option['value'] == json_decode($initValueEncoded)) ? 'selected' : '' !!} + > {{ $option['description'] }} </option> @endforeach diff --git a/resources/views/multiple.blade.php b/resources/views/multiple.blade.php index 8196909..1a4d99b 100644 --- a/resources/views/multiple.blade.php +++ b/resources/views/multiple.blade.php @@ -1,90 +1,102 @@ <div + wire:model="value" x-data="livewireSelectMultiSelectDropdown($el)" x-init="loadOptions($el.querySelector('.livewire-select-input'))" - @livewireselectoptionsloaded="loadOptions($el.querySelector('.livewire-select-input'))" - class="w-full flex flex-col items-center" + @livewireselectoptionsloaded="loadOptions($el.querySelector('.livewire-select-input'));" > <select - x-show="false" x-cloak - class="livewire-select-input {{ $styles['default'] }}" - multiple="true" name="{{ $name }}" - wire:model="value" + class="livewire-select-input" + x-bind:value="selectedValues()" + multiple="true" > <option value=""> {{ $placeholder }} </option> @foreach($options as $option) - <option value="{{ $option['value'] }}" x-bind:selected="(selectedValues().indexOf('{{ $option['value'] }}') >= 0) ? 'true' : false;"> + <option + value="{{ $option['value'] }}" + {{ (json_decode($initValueEncoded) && in_array($option['value'], json_decode($initValueEncoded))) ? 'selected="selected"' : '' }} + > {{ $option['description'] }} </option> @endforeach </select> - <div class="w-full block relative"> - <div class="flex flex-col items-center relative"> - <div x-on:click="open" class="w-full"> - <div class="p-1 flex border border-gray-200 bg-white rounded"> - <div class="flex flex-auto flex-wrap"> - <template x-for="(option,index) in selected" :key="options[option].value"> - <div class="flex justify-center items-center m-1 font-medium py-1 px-1 bg-white rounded bg-gray-100 border"> - <div class="text-xs font-normal leading-none max-w-full flex-initial x-model=" options[option] x-text="options[option].text"></div> - <div class="flex flex-auto flex-row-reverse"> - <div x-on:click.stop="remove(index,option)"> - <svg class="fill-current h-4 w-4 " role="button" viewBox="0 0 20 20"> - <path d="M14.348,14.849c-0.469,0.469-1.229,0.469-1.697,0L10,11.819l-2.651,3.029c-0.469,0.469-1.229,0.469-1.697,0 + + <div + class="w-full flex flex-col items-center" + > + <div class="w-full block relative"> + <div class="flex flex-col items-center relative"> + <div @click="open" class="w-full"> + <div class="my-2 p-1 flex border border-gray-200 bg-white rounded"> + <div class="flex flex-auto flex-wrap"> + <template x-for="(option,index) in selected" :key="option"> + <div class="flex justify-center items-center m-1 font-medium py-1 px-1 bg-white rounded bg-gray-100 border"> + <div class="text-xs font-normal leading-none max-w-full flex-initial" x-model="options[option]" x-text="options[option].text"></div> + <div class="flex flex-auto flex-row-reverse"> + <div @click.stop="remove(index,option); $dispatch('input', selectedValues())"> + <svg class="fill-current h-4 w-4 " role="button" viewBox="0 0 20 20"> + <path d="M14.348,14.849c-0.469,0.469-1.229,0.469-1.697,0L10,11.819l-2.651,3.029c-0.469,0.469-1.229,0.469-1.697,0 c-0.469-0.469-0.469-1.229,0-1.697l2.758-3.15L5.651,6.849c-0.469-0.469-0.469-1.228,0-1.697s1.228-0.469,1.697,0L10,8.183 l2.651-3.031c0.469-0.469,1.228-0.469,1.697,0s0.469,1.229,0,1.697l-2.758,3.152l2.758,3.15 C14.817,13.62,14.817,14.38,14.348,14.849z" /> - </svg> - </div> - </div> - </div> - </template> - <div x-show="selected.length == 0" class="flex-1"> - <input placeholder="Select a option" class="bg-transparent p-1 px-2 appearance-none outline-none h-full w-full text-gray-800" x-bind:value="selectedValues()"> + </svg> + + </div> </div> </div> - <div class="text-gray-300 w-8 py-1 pl-2 pr-1 border-l flex items-center border-gray-200"> - <button type="button" x-show="isOpen() === true" x-on:click="open" class="cursor-pointer w-6 h-6 text-gray-600 outline-none focus:outline-none"> - <svg version="1.1" class="fill-current h-4 w-4" viewBox="0 0 20 20"> - <path d="M17.418,6.109c0.272-0.268,0.709-0.268,0.979,0s0.271,0.701,0,0.969l-7.908,7.83 - c-0.27,0.268-0.707,0.268-0.979,0l-7.908-7.83c-0.27-0.268-0.27-0.701,0-0.969c0.271-0.268,0.709-0.268,0.979,0L10,13.25 - L17.418,6.109z" /> - </svg> - </button> - <button type="button" x-show="isOpen() === false" @click="close" class="cursor-pointer w-6 h-6 text-gray-600 outline-none focus:outline-none"> - <svg class="fill-current h-4 w-4" viewBox="0 0 20 20"> - <path d="M2.582,13.891c-0.272,0.268-0.709,0.268-0.979,0s-0.271-0.701,0-0.969l7.908-7.83 - c0.27-0.268,0.707-0.268,0.979,0l7.908,7.83c0.27,0.268,0.27,0.701,0,0.969c-0.271,0.268-0.709,0.268-0.978,0L10,6.75L2.582,13.891z - " /> - </svg> - </button> + </template> + <div x-show="selected.length == 0" class="flex-1"> + <input placeholder="Select a option" class="bg-transparent p-1 px-2 appearance-none outline-none h-full w-full text-gray-800"> </div> </div> + <div class="text-gray-300 w-8 py-1 pl-2 pr-1 border-l flex items-center border-gray-200"> + + <button type="button" x-show="isOpen() === true" @click="open" class="cursor-pointer w-6 h-6 text-gray-600 outline-none focus:outline-none"> + <svg version="1.1" class="fill-current h-4 w-4" viewBox="0 0 20 20"> + <path d="M17.418,6.109c0.272-0.268,0.709-0.268,0.979,0s0.271,0.701,0,0.969l-7.908,7.83 + c-0.27,0.268-0.707,0.268-0.979,0l-7.908-7.83c-0.27-0.268-0.27-0.701,0-0.969c0.271-0.268,0.709-0.268,0.979,0L10,13.25 + L17.418,6.109z" /> + </svg> + + </button> + <button type="button" x-show="isOpen() === false" @click="close" class="cursor-pointer w-6 h-6 text-gray-600 outline-none focus:outline-none"> + <svg class="fill-current h-4 w-4" viewBox="0 0 20 20"> + <path d="M2.582,13.891c-0.272,0.268-0.709,0.268-0.979,0s-0.271-0.701,0-0.969l7.908-7.83 + c0.27-0.268,0.707-0.268,0.979,0l7.908,7.83c0.27,0.268,0.27,0.701,0,0.969c-0.271,0.268-0.709,0.268-0.978,0L10,6.75L2.582,13.891z + " /> + </svg> + + </button> + </div> + </div> </div> <div class="w-full px-4"> - <div x-show.transition.origin.top="isOpen()" class="absolute shadow top-100 bg-white z-40 w-full left-0 rounded" x-on:click.away="close"> - <div class="bg-white flex flex-col w-full overflow-y-auto max-h-64"> - <template x-for="(option,index) in options" :key="index" class="overflow-auto"> - <div class="cursor-pointer w-full border-gray-100 rounded-t border-b hover:bg-gray-100" @click="if (option.value) { select(index,$event) }"> - <div class="flex w-full items-center p-2 pl-2 border-transparent border-l-2 relative"> - <div class="w-full items-center flex justify-between"> - <div class="mx-2 leading-6" x-model="option" x-text="option.text" x-bind:class="!option.value ? 'pointer-events-none text-gray-400': false"></div> - <div x-show="option.selected"> - <svg class="fill-current w-4 h-4" viewBox="0 0 20 20"> - <path d="M7.197,16.963H7.195c-0.204,0-0.399-0.083-0.544-0.227l-6.039-6.082c-0.3-0.302-0.297-0.788,0.003-1.087 - C0.919,9.266,1.404,9.269,1.702,9.57l5.495,5.536L18.221,4.083c0.301-0.301,0.787-0.301,1.087,0c0.301,0.3,0.301,0.787,0,1.087 - L7.741,16.738C7.596,16.882,7.401,16.963,7.197,16.963z" /> - </svg> - </div> - </div> - </div> + <div x-show.transition.origin.top="isOpen()" class="absolute shadow top-100 bg-white z-40 w-full left-0 rounded" @click.away="close"> + <div class="bg-white flex flex-col w-full overflow-y-auto h-64"> + <template x-for="(option, index) in options" :key="index" class="overflow-auto"> + <div class="cursor-pointer w-full border-gray-100 rounded-t border-b hover:bg-gray-100" @click="if (option.value) { select(index,$event); $dispatch('input', selectedValues()) }"> + <div class="flex w-full items-center p-2 pl-2 border-transparent border-l-2 relative"> + <div class="w-full items-center flex justify-between"> + <div class="mx-2 leading-6" x-model="option" x-text="option.text"></div> + <div x-show="option.selected"> + <svg class="fill-current w-4 h-4" viewBox="0 0 20 20"> + <path d="M7.197,16.963H7.195c-0.204,0-0.399-0.083-0.544-0.227l-6.039-6.082c-0.3-0.302-0.297-0.788,0.003-1.087 + C0.919,9.266,1.404,9.269,1.702,9.57l5.495,5.536L18.221,4.083c0.301-0.301,0.787-0.301,1.087,0c0.301,0.3,0.301,0.787,0,1.087 + L7.741,16.738C7.596,16.882,7.401,16.963,7.197,16.963z"></path> + </svg> </div> - </template> + </div> + </div> </div> + </template> + </div> </div> </div> + </div> </div> </div> + </div> diff --git a/src/LivewireSelect.php b/src/LivewireSelect.php index 519dda4..e2da1ba 100755 --- a/src/LivewireSelect.php +++ b/src/LivewireSelect.php @@ -270,13 +270,13 @@ public function js($options = null) { 'livewire-select-multiple' => '<script> function livewireSelectMultiSelectDropdown($el) { // Select the node that will be observed for mutations - var innerSelectTargetNode = $el.querySelector(\'.livewire-select-input\'); + var relatedSelectTargetNode = $el.querySelector(\'.livewire-select-input\'); // Options for the observer (which mutations to observe) - var innerSelectMutationObserverConfig = { attributes: true, childList: true, subtree: true }; + var relatedSelectMutationObserverConfig = { attributes: true, childList: true, subtree: true }; // Callback function to execute when mutations are observed - const innerSelectMutationObserverCallback = function(mutationsList, observer) { + const relatedSelectMutationObserverCallback = function(mutationsList, observer) { var hasUpdates = false; @@ -289,15 +289,15 @@ function livewireSelectMultiSelectDropdown($el) { } if (hasUpdates) { - innerSelectTargetNode.dispatchEvent(new CustomEvent(\'livewireselectoptionsloaded\', { bubbles: true })); + relatedSelectTargetNode.dispatchEvent(new CustomEvent(\'livewireselectoptionsloaded\', { bubbles: true })); } }; // Create an observer instance linked to the callback function - var innerSelectMutationObserver = new MutationObserver(innerSelectMutationObserverCallback); + var relatedSelectMutationObserver = new MutationObserver(relatedSelectMutationObserverCallback); // Start observing the target node for configured mutations - innerSelectMutationObserver.observe(innerSelectTargetNode, innerSelectMutationObserverConfig); + relatedSelectMutationObserver.observe(relatedSelectTargetNode, relatedSelectMutationObserverConfig); return { options: [], @@ -307,13 +307,11 @@ function livewireSelectMultiSelectDropdown($el) { close() { this.show = false }, isOpen() { return this.show === true }, select(index, event) { - if (!this.options[index].selected) { - this.options[index].selected = true; - this.options[index].element = event.target; - this.selected.push(index); - + if (this.selected.indexOf(index) === -1) { + this.selected.push(index); + } } else { this.selected.splice(this.selected.lastIndexOf(index), 1); this.options[index].selected = false @@ -322,28 +320,38 @@ function livewireSelectMultiSelectDropdown($el) { remove(index, option) { this.options[option].selected = false; this.selected.splice(index, 1); - - }, - loadOptions(select) { - const options = select.options; + loadOptions(selectEl) { this.options = []; + const options = selectEl.options; for (let i = 0; i < options.length; i++) { + + let isSelected = false; + if (options[i].getAttribute(\'selected\') != null) { + if (this.selected.indexOf(i) === -1) { + this.selected.push(i); + } + isSelected = true; + } + this.options.push({ value: options[i].value, text: options[i].innerText, - selected: options[i].getAttribute(\'selected\') != null ? options[i].getAttribute(\'selected\') : false + selected: isSelected }); } - - }, - selectedValues() { + selectedValues(){ + if (!this.options.length) { + return []; + } + return this.selected.map((option)=>{ return this.options[option].value; - }); + }) } } + } </script>' ];