diff --git a/app/resources/views/form/components/selectAsyncDependent.blade.php b/app/resources/views/form/components/selectAsyncDependent.blade.php
index 0d7761e0..21608e1c 100644
--- a/app/resources/views/form/components/selectAsyncDependent.blade.php
+++ b/app/resources/views/form/components/selectAsyncDependent.blade.php
@@ -32,6 +32,21 @@
@{{ form.$all }}
+
+
+
+
+ @{{ form.$all }}
+
+
+
+
+
+
+ @{{ form.$all }}
+
+
+
@endsection
\ No newline at end of file
diff --git a/app/resources/views/form/components/selectAsyncNested.blade.php b/app/resources/views/form/components/selectAsyncNested.blade.php
index 0869963c..0caf7b1d 100644
--- a/app/resources/views/form/components/selectAsyncNested.blade.php
+++ b/app/resources/views/form/components/selectAsyncNested.blade.php
@@ -32,6 +32,20 @@
@{{ form.$all }}
+
+
+
+
+ @{{ form.$all }}
+
+
+
+
+
+
+ @{{ form.$all }}
+
+
@endsection
\ No newline at end of file
diff --git a/app/resources/views/navigation/nav.blade.php b/app/resources/views/navigation/nav.blade.php
index d20930a3..d925d33d 100644
--- a/app/resources/views/navigation/nav.blade.php
+++ b/app/resources/views/navigation/nav.blade.php
@@ -11,6 +11,7 @@
AwayViaFacade
AwayViaLink
Lazy
+ Auth
Confirm to two
browse(function (Browser $browser) {
+ $browser->visit('/navigation/two')
+ ->waitForText('NavigationTwo')
+ ->click('@auth')
+ ->waitForRoute('navigation.one')
+ ->assertSee('NavigationOne');
+ });
+ }
+}
diff --git a/app/tests/Browser/Form/SelectDependentTest.php b/app/tests/Browser/Form/SelectDependentTest.php
index b730f1d0..7c5e9c1b 100644
--- a/app/tests/Browser/Form/SelectDependentTest.php
+++ b/app/tests/Browser/Form/SelectDependentTest.php
@@ -112,4 +112,49 @@ public function it_restores_the_placeholder_on_choices_multiple_select($url)
});
});
}
+
+ /**
+ * @dataProvider dependentUrls
+ *
+ * @test
+ */
+ public function it_can_select_the_first_remote_option($url)
+ {
+ $this->browse(function (Browser $browser) use ($url) {
+ $browser->visit($url)
+ ->waitForText('FormComponents')
+ ->waitUntilMissing('svg')
+ ->within('@select-first', function (Browser $browser) {
+ $browser
+ ->waitUntilMissing('svg')
+ ->assertSeeIn('@all', '{ "country": "BE", "province": "BE-VAN" }')
+ ->select('@country', 'NL')
+ ->waitUntilMissing('svg')
+ ->assertSeeIn('@all', '{ "country": "NL", "province": "NL-AW" }');
+ });
+ });
+ }
+
+ /**
+ * @dataProvider dependentUrls
+ *
+ * @test
+ */
+ public function it_can_reset_the_select_option_on_a_remote_url_change($url)
+ {
+ $this->browse(function (Browser $browser) use ($url) {
+ $browser->visit($url)
+ ->waitForText('FormComponents')
+ ->waitUntilMissing('svg')
+ ->within('@select-reset', function (Browser $browser) {
+ $browser
+ ->waitUntilMissing('svg')
+ ->select('@province', 'BE-VAN')
+ ->assertSeeIn('@all', '{ "country": "BE", "province": "BE-VAN" }')
+ ->select('@country', 'NL')
+ ->waitUntilMissing('svg')
+ ->assertSeeIn('@all', '{ "country": "NL", "province": "" }');
+ });
+ });
+ }
}
diff --git a/lib/Components/Select.vue b/lib/Components/Select.vue
index 6e44bd55..0f0ad960 100644
--- a/lib/Components/Select.vue
+++ b/lib/Components/Select.vue
@@ -80,6 +80,18 @@ export default {
required: false,
default: null,
},
+
+ selectFirstRemoteOption: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+
+ resetOnNewRemoteUrl: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
emits: ["update:modelValue"],
@@ -167,100 +179,121 @@ export default {
},
methods: {
- /*
- * Loads the options from a remote URL. It removes all current options from the select
- * element, and then adds the new options. If the components uses Choices.js,
- * it will first destroy the instance and then re-initialize it.
- */
- loadRemoteOptions() {
- if(!this.remoteUrl) {
- return;
+ async setOptionsFromRemote(data) {
+ // Cleanup previous choices instance.
+ this.destroyChoicesInstance();
+
+ if(this.resetOnNewRemoteUrl) {
+ this.$emit("update:modelValue", this.multiple ? [] : "");
}
- this.loading = true;
+ let options = [];
- Axios({
- url: this.remoteUrl,
- method: "GET",
- headers: {
- Accept: "application/json",
- },
- })
- .then((response) => {
- // Cleanup previous choices instance.
- this.destroyChoicesInstance();
+ // Start with the the placeholder.
+ if(this.placeholder) {
+ options.push(this.placeholder);
+ }
- let options = [];
+ // Normalize the response.
+ options = this.normalizeOptions(data, options);
- // Start with the the placeholder.
- if(this.placeholder) {
- options.push(this.placeholder);
- }
+ var index;
+ var currentOptionsCount = this.element.options.length - 1;
- // Normalize the response.
- options = this.normalizeOptions(this.remoteRoot ? get(response.data, this.remoteRoot) : response.data, options);
+ for(index = currentOptionsCount; index >= 0; index--) {
+ // Remove all current options.
+ this.element.remove(index);
+ }
- var index;
- var currentOptionsCount = this.element.options.length - 1;
+ let hasSelectedOption = false;
- for(index = currentOptionsCount; index >= 0; index--) {
- // Remove all current options.
- this.element.remove(index);
- }
+ forOwn(options, (option) => {
+ // Add the new options.
+ var optionElement = document.createElement("option");
- let hasSelectedOption = false;
+ optionElement.value = option.value;
+ optionElement.text = option.label;
- forOwn(options, (option) => {
- // Add the new options.
- var optionElement = document.createElement("option");
+ if(option.value === `${this.modelValue}` && option.value !== "") {
+ // The current value is in the new options, we use this later on
+ // to set the value on the select element and Choices instance.
+ hasSelectedOption = true;
+ }
- optionElement.value = option.value;
- optionElement.text = option.label;
+ if(option.disabled) {
+ optionElement.disabled = option.disabled;
+ }
- if(option.value === `${this.modelValue}`) {
- // The current value is in the new options, we use this later on
- // to set the value on the select element and Choices instance.
- hasSelectedOption = true;
- }
+ if(option.placeholder) {
+ optionElement.placeholder = option.placeholder;
+ }
- if(option.disabled) {
- optionElement.disabled = option.disabled;
- }
+ // Add the option to the select element.
+ this.element.appendChild(optionElement);
+ });
- if(option.placeholder) {
- optionElement.placeholder = option.placeholder;
- }
- // Add the option to the select element.
- this.element.appendChild(optionElement);
- });
+ if(!hasSelectedOption && this.selectFirstRemoteOption) {
+ const firstOption = this.placeholder ? options[1] : options[0];
- if(!hasSelectedOption) {
- // The current value is not in the new options, we set the value to null.
- this.$emit("update:modelValue", this.multiple ? [] : "");
- }
+ if(firstOption){
+ this.$emit("update:modelValue", this.multiple ? [firstOption.value] : firstOption.value);
+ await this.$nextTick();
+ }
- if(this.choices) {
- // Re-initialize the Choices instance.
- return this.initChoices(this.element).then(() => {
- this.loading = false;
- });
- }
+ hasSelectedOption = true;
+ }
- if(hasSelectedOption) {
- // The current value is in the new options, we set the value on the select element.
- this.element.value = this.modelValue;
- } else {
- // The current value is not in the new options, we set the value to null.
- this.$nextTick(() => {
- this.element.selectedIndex = 0;
- });
- }
+ if(!hasSelectedOption) {
+ // The current value is not in the new options, we set the value to null.
+ this.$emit("update:modelValue", this.multiple ? [] : "");
+ }
+ if(this.choices) {
+ // Re-initialize the Choices instance.
+ return this.initChoices(this.element).then(() => {
this.loading = false;
+ });
+ }
+
+ if(hasSelectedOption) {
+ // The current value is in the new options, we set the value on the select element.
+ this.element.value = this.modelValue;
+ } else {
+ // The current value is not in the new options, we set the value to null.
+ this.$nextTick(() => {
+ this.element.selectedIndex = 0;
+ });
+ }
+ },
+
+ /*
+ * Loads the options from a remote URL. It removes all current options from the select
+ * element, and then adds the new options. If the components uses Choices.js,
+ * it will first destroy the instance and then re-initialize it.
+ */
+ loadRemoteOptions() {
+ if(!this.remoteUrl) {
+ return;
+ }
+ this.loading = true;
+
+
+ Axios({
+ url: this.remoteUrl,
+ method: "GET",
+ headers: {
+ Accept: "application/json",
+ },
+ })
+ .then((response) => {
+ this.setOptionsFromRemote(this.remoteRoot ? get(response.data, this.remoteRoot) : response.data);
})
.catch(() => {
+ this.setOptionsFromRemote([]);
+ })
+ .finally(() => {
this.loading = false;
});
},
@@ -407,6 +440,10 @@ export default {
// The Headless UI Dialog blocks the events on the Choices.js
// instance, so we put an event listener on the portal root.
vm.headlessListener = function(e) {
+ if(!vm.choicesInstance) {
+ return;
+ }
+
const isActive = vm.choicesInstance.dropdown.isActive;
if(!isActive && e.target === selectElement) {
diff --git a/resources/views/form/select.blade.php b/resources/views/form/select.blade.php
index f86831b0..f7158e9f 100644
--- a/resources/views/form/select.blade.php
+++ b/resources/views/form/select.blade.php
@@ -10,6 +10,8 @@
:remote-root="@js($remoteRoot ?: null)"
:option-value="@js($optionValue)"
:option-label="@js($optionLabel)"
+ :select-first-remote-option="@js($selectFirstRemoteOption)"
+ :reset-on-new-remote-url="@js($resetOnNewRemoteUrl)"
>