最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to prevent a select form from being changed until dialog completion in Vue - Stack Overflow

programmeradmin1浏览0评论

I have a select field with various options. When the user clicks the field to change the current selection, I need to launch a prompt to have the user confirm that they wish to continue with the change, since that will require them to redo a long process. If they cancel the change, it needs to prevent the selected option from ever changing as even a quick temporary change will trigger an autosave on the client. Because of this, it seems that other solutions don't work as they save the original value, let the change go through, and then revert the change if necessary.

I'm not sure if this is the "proper" approach, but I decided to create a function that will run each time the select field is clicked. I successfully solved this issue by using the native confirm method in the following code. I believe it works because confirm's synchronous nature allows me to revert the change before it is ever received by any event listeners, so it's basically like the change never happened(please correct me if I'm wrong). Unfortunately, I am not allowed to use confirm due to patibility reasons.

// This works, but I need to be able to do it without using 'confirm'
handlePotentialOptionChange(e){
  if (this.currentOption !== e.target.value){
    if (!confirm(`Are you sure you want to change the option from ${this.currentOption} to ${e.target.value}? You will be required to redo multiple fields.`)){
      this.selectModel = this.currentOption // If user cancels the change, revert it to the original option. 
    }
  }
}

Since I cannot use confirm, I'm using a dialog ponent from Buefy. However, it runs asynchronously which means that when the user attempts to select a different option, the option change will go through before they even answer the dialog. The code below will revert the change if canceled, but by that point it is too late to be of any use.

handlePotentialOptionChange(e){
  if (this.currentOption !== e.target.value){
    this.$dialog.confirm({
      message: `Are you sure you want to change the option from ${this.currentOption} to ${e.target.value}? You will be required to redo multiple fields.`,
      onConfirm: () => this.selectModel = e.target.value,
      onCancel: () => this.selectModel = this.currentOption
    })
  }
}

Is it possible to let the user see the option dropdown, but disable any kind of option changes until the prompt is answered, so that I can change the option accordingly inside the async onConfirm and onCancel functions? Or should I be using some sort of entirely different approach? Thank you.

I have a select field with various options. When the user clicks the field to change the current selection, I need to launch a prompt to have the user confirm that they wish to continue with the change, since that will require them to redo a long process. If they cancel the change, it needs to prevent the selected option from ever changing as even a quick temporary change will trigger an autosave on the client. Because of this, it seems that other solutions don't work as they save the original value, let the change go through, and then revert the change if necessary.

I'm not sure if this is the "proper" approach, but I decided to create a function that will run each time the select field is clicked. I successfully solved this issue by using the native confirm method in the following code. I believe it works because confirm's synchronous nature allows me to revert the change before it is ever received by any event listeners, so it's basically like the change never happened(please correct me if I'm wrong). Unfortunately, I am not allowed to use confirm due to patibility reasons.

// This works, but I need to be able to do it without using 'confirm'
handlePotentialOptionChange(e){
  if (this.currentOption !== e.target.value){
    if (!confirm(`Are you sure you want to change the option from ${this.currentOption} to ${e.target.value}? You will be required to redo multiple fields.`)){
      this.selectModel = this.currentOption // If user cancels the change, revert it to the original option. 
    }
  }
}

Since I cannot use confirm, I'm using a dialog ponent from Buefy. However, it runs asynchronously which means that when the user attempts to select a different option, the option change will go through before they even answer the dialog. The code below will revert the change if canceled, but by that point it is too late to be of any use.

handlePotentialOptionChange(e){
  if (this.currentOption !== e.target.value){
    this.$dialog.confirm({
      message: `Are you sure you want to change the option from ${this.currentOption} to ${e.target.value}? You will be required to redo multiple fields.`,
      onConfirm: () => this.selectModel = e.target.value,
      onCancel: () => this.selectModel = this.currentOption
    })
  }
}

Is it possible to let the user see the option dropdown, but disable any kind of option changes until the prompt is answered, so that I can change the option accordingly inside the async onConfirm and onCancel functions? Or should I be using some sort of entirely different approach? Thank you.

Share Improve this question edited Apr 15, 2019 at 20:59 Dan Mandel asked Apr 15, 2019 at 20:44 Dan MandelDan Mandel 7471 gold badge9 silver badges30 bronze badges 0
Add a ment  | 

2 Answers 2

Reset to default 7

I would create a new ponent that bines the select element and the confirmation dialog and have it emit an 'input' event (only when the new selection has been confirmed) and receive a 'value' prop so the parent can use it with v-model.

Run the code snippet and read through the example below.

Vue.ponent('select-confirm', {
  props: ['value'],

  data: function () {
    return {
      showConfirmationDialog: false,
      unconfirmedValue: 'a'
    }
  },

  methods: {
    cancelSelection () {
      this.showConfirmationDialog = false
    },
    
    confirmSelection () {
      this.$emit('input', this.unconfirmedValue)
      this.showConfirmationDialog = false
    },
    
    showConfirmation (e) {
      this.unconfirmedValue = e.currentTarget.value
      this.showConfirmationDialog = true
    }
  },

  template: `
    <div class="select-confirm">
      <select :value="value" @change="showConfirmation">
        <option value="a">A</option>
        <option value="b">B</option>
      </select>
      <div v-if="showConfirmationDialog" class="confirmation-dialog">
        <p>Are you sure you want to change your selection to '{{ this.unconfirmedValue }}'?</p>
        <button @click="confirmSelection">Yes</button>
        <button @click="cancelSelection">No</button>
      </div>
    </div>
  `
})

new Vue({
  el: '#app',
  data () {
    return {
      confirmedValue: 'a'
    }
  }
})
<script src="https://unpkg./[email protected]/dist/vue.min.js"></script>
<div id="app">
  <select-confirm v-model="confirmedValue"></select-confirm>
  <p>Confirmed value is '{{ confirmedValue }}'.</p>
</div>

I'd like to propose a solution using $refs and one directional v-bind.

<template>
    <div>
        <select ref="mySelect" v-bind:value="storedChoice" @input="vOn">
            <option
                v-for="option in selectOptions"
                :value="option.value"
                :key="option.value"
            >
                {{ option.name }}
            </option>
        </select>
    </div>
</template>
<script>
export default {
    data() {
        return {
            selectOptions: [
                { value: 1, name: "Choice 1" },
                { value: 2, name: "Choice 2" },
                { value: 3, name: "Choice 3" },
            ],
            storedChoice: 3,
        };
    },
    methods: {
        vOn(event) {
            const newValue = event.target.value;
            const indexPriorToChange = this.selectOptions.findIndex(
                (el) => el.value === this.storedChoice
            );
            this.$refs.mySelect.options.selectedIndex = indexPriorToChange; // this resets the index to the one resulting from value
            let confirmDialog = new Promise((resolve) => {
                // this promise is a mockup for an asycnc dialog returning true / false
                setTimeout(() => {
                    resolve(true); // resolve true or false
                }, 1500);
            });
            confirmDialog.then((res) => {
                if (res) {
                    this.storedChoice = newValue;
                } else console.log("No changes");
            });
        },
    },
};
</script>

The idea is to use v-bind insted of v-model with the select field in order to block automatic change of stored data. At the same time we listen with the v-on to an input event and when it happens we immediatelly access the select field and set it's index (already changed by the browser as result of user's action) to the index resulting from the current 'real' data. Then we launch an async validation and if the validation is positive, we change the stored data and the select field updates itself (if not we might set the index manually, but it worked for me without it, just using the Vue's reactivity).

This operation might trigger Vue warning about direct manipulation of the DOM (for some reason the above code did not do it for me though), however I think this is a justified use, because we are not trying to display data overriding our data model - on the contrary - we are preventing the browser from displaying data that are not yet stored in the model.

You can see here my alternative solution using getter/setter approach for BootstrapVue's select ponent.

发布评论

评论列表(0)

  1. 暂无评论