From fc3d4ff08da912ee2bece49c15f0be4a477ed32c Mon Sep 17 00:00:00 2001 From: Stowez Date: Tue, 5 Nov 2024 15:24:54 +0000 Subject: [PATCH] Fixed a JS Error, and added option caching --- changelog.md | 6 ++ composer.json | 1 + readme.md | 3 + resources/js/components/FormField.vue | 88 +++++++++++++++++---------- 4 files changed, 65 insertions(+), 33 deletions(-) diff --git a/changelog.md b/changelog.md index 29cca4b..dc98fb2 100644 --- a/changelog.md +++ b/changelog.md @@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## 1.1.0 - 2024-11-05 + +- Fixed javascript error when inside a flexible componet +- Any Ajax Request results are now stored in a cached variable so label isn't lost on search +- Use Nova Placeholder before name on search input + ## 1.0.1 - 2022-05-11 - Added filterable option flag, contributed by https://github.com/nea diff --git a/composer.json b/composer.json index 1c843fa..2a881f3 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,7 @@ { "name": "razorcreations/ajax-field", "description": "A searchable AJAX powered field for Laravel Nova.", + "version": "1.1.0", "authors": [ { "name": "Ross Cooper", diff --git a/readme.md b/readme.md index 31db80e..4a6c79c 100644 --- a/readme.md +++ b/readme.md @@ -15,6 +15,9 @@ For Nova 3 support use ## Usage ```php + + use Razorcreations\AjaxField\AjaxField; + // Inside your resources fields definition AjaxField::make('Foo')->setUrl('/api/ajaxselect/foo'), diff --git a/resources/js/components/FormField.vue b/resources/js/components/FormField.vue index 62a7454..2f54741 100644 --- a/resources/js/components/FormField.vue +++ b/resources/js/components/FormField.vue @@ -9,7 +9,7 @@ v-model="value" class="w-full form-control form-input form-input-bordered" :class="errorClasses" - :placeholder="field.name" + :placeholder="field.placeholder || field.name" :options="availableOptions" :label="labelKey" :multiple="field.multiple" @@ -30,7 +30,6 @@ import _ from 'lodash'; import { isArray } from 'util'; export default { - components: { VueSelect, }, @@ -38,8 +37,9 @@ export default { props: ['resourceName', 'resourceId', 'field'], - data () { + data() { return { + optionsCache: [], options: [], loading: false, labelKey: 'label', @@ -52,16 +52,20 @@ export default { computed: { parentComponent() { - if(!this.field.parent_field) { + if (!this.field.parent_field) { return false; } let targetField = this.field.parent_field; - let currentField = this.field.attribute; + let currentField = this.field.attribute; // If component is inside a flexible, key is prefixed with an id - if( currentField.indexOf('__') ) { - targetField = currentField.substr(0, currentField.indexOf('__')) + '__' + targetField; + if (currentField.indexOf('__')) { + targetField = currentField.substr(0, currentField.indexOf('__')) + '__' + targetField; + } + + if (typeof this.$parent.$children === 'undefined') { + return; } // Find the component the parent value references @@ -71,14 +75,14 @@ export default { }) }, - availableOptions () { - return _.uniq(this.options.concat(this.selectedOptions), 'value'); + availableOptions() { + return _.uniqBy(this.options.concat(this.selectedOptions), 'value'); }, }, - mounted () { + mounted() { this.parseInitialValue(); - if(this.parentComponent) { + if (this.parentComponent) { this.parentComponent.$watch('value', (value) => { this.parentVal = value; this.prepareField(); @@ -91,17 +95,17 @@ export default { methods: { prepareField() { //Check whether any filterable data was set, otherwise stay true - if(this.field.filterable !== 'undefined') { + if (typeof this.field.filterable !== 'undefined') { this.filterable = this.field.filterable; } // If field is not responsive, load initial options - if(!this.field.responsive) { + if (!this.field.responsive) { return this.loadInitialOptions(); } // If field is responsive, do we have any initial values - if(this.field.value ) { + if (this.field.value) { return this.loadInitialOptions(this.field.value); } }, @@ -122,17 +126,18 @@ export default { /* * Load initial Options - */ - loadInitialOptions (value) { + */ + loadInitialOptions(value) { let url = this.buildParamString(null, value); - if(Array.isArray(value) && value.length === 1 && !value[0]) { + if (Array.isArray(value) && value.length === 1 && !value[0]) { this.value = []; return; } - window.Nova.request().get( url ).then(({data}) => { + window.Nova.request().get(url).then(({ data }) => { this.options = data; + this.cacheOptions(data); this.options.forEach(option => { if (isArray(this.value)) { @@ -147,26 +152,39 @@ export default { this.selectedOptions.push(option); } }) - }); + }).catch(error => { + console.error('Error loading initial options:', error); + }); }, /* * Dynamic search with the input value - */ + */ search: window._.debounce((loading, searchVal, vm) => { let url = vm.buildParamString(searchVal) - window.Nova.request().get( url ).then(({data}) => { + window.Nova.request().get(url).then(({ data }) => { vm.options = data; + vm.cacheOptions(data); loading(false); }); }, 350), - + /* + * Cache options to optionsCache + */ + cacheOptions(options) { + options.forEach(option => { + if (!this.optionsCache.find(o => o.value === option.value)) { + this.optionsCache.push(option); + } + }); + }, + /* * When multiselect input changes, determine if ready to query */ - inputChange (input, loading) { - if( input.length < 3 && !/^\d+$/.test(input)) { + inputChange(input, loading) { + if (input.length < 3 && !/^\d+$/.test(input)) { return; } loading(true); @@ -183,15 +201,15 @@ export default { let params = {} let url = this.field.url; - if(this.parentVal) { + if (this.parentVal) { params[this.field.parent_field] = this.parentVal; } - if(this.field.responsive && searchVal) { + if (this.field.responsive && searchVal) { params.search = searchVal } - if(fieldVal) { + if (fieldVal) { params.value = fieldVal } @@ -200,7 +218,7 @@ export default { return url = url + '?' + paramString; }, - parseInitialValue () { + parseInitialValue() { let value = this.field.value ? this.field.value : null; if (!value) { this.value = value; @@ -237,12 +255,16 @@ export default { if (!v) { return; } - const selectedOption = this.options.find(option => option.value === v); - this.selectedOptions.push(selectedOption); + const selectedOption = this.optionsCache.find(option => option.value === v); + if (selectedOption) { + this.selectedOptions.push(selectedOption); + } }); } else { - const selectedOption = this.options.find(option => option.value === value); - this.selectedOptions.push(selectedOption); + const selectedOption = this.optionsCache.find(option => option.value === value); + if (selectedOption) { + this.selectedOptions.push(selectedOption); + } } } }, @@ -257,4 +279,4 @@ export default { .vs__dropdown-toggle { border-color: transparent !important; } - + \ No newline at end of file