網站搜尋

TypeScript - Bootstrap Vue 預防/取消事件


我有一個頁面,其中包含來自 Bootstrap Vue 的 b-form-select 控件。當我更改這個控件的值時,我想執行一個可能取消此事件並且不改變值的函數。

更複雜的是,我的 select 在子元件中,而我要執行的函式在父元件中。

子元件

public values: any[] = [
  { name: 'default'},
  { name: 'forbidden'},
  { name: 'other option' },
]

<b-form-select :value="property" @change="$emit('onPropertyChange', arguments[0])">
   <option v-for="(val, key) in values" :value="val" :key="key">{{val.Name}}</option>
</b-form-select>

父元件:

this.property = { name: 'default' }
public onPropertyChange(newValue) {
  if (newValue.name === 'forbidden') {
    // Not changing this.property
  } else {
    // Changing it
    this.property = newValue
  }     
}

<child :property="property" @onPropertyChange="onPropertyChange"></child>

當我在下拉選單中選擇『forbidden』時,我看到下拉選單已經變成這個值,但子元件的屬性以及父元件的屬性仍然是舊值,這正是我想要的。我該如何也讓下拉選單保持舊值呢?

Bootstrap Vue 似乎在 change 事件上沒有 prevent 修飾符。我也嘗試過使用原生事件,但也有相同的問題。

我做錯了什麼嗎?

答案#1

禁用禁止選項可能會比神秘地讓它們無法選擇選中的值提供更好的使用者體驗,但你可以在變更事件處理程序中通過保存舊值並在 nextTick 上恢復它來獲得那種行為。

請注意,你的 options 結構不適用於 b-form-select。我已經建立了一個計算屬性,它生成正確的結構並處理 disabled 的設置,並在 b-form-select 中使用了它。

new Vue({
  el: '#app',
  data: {
    selected: 'default',
    options: [{
        name: 'default',
      },
      {
        name: 'forbidden'
      },
      {
        name: 'other option'
      },
      {
        name: 'what I had before'
      },
    ]
  },
  computed: {
    selectOptions() {
      return this.options.map((opt) => ({
        text: opt.name,
        value: opt.name,
        disabled: opt.name === 'forbidden'
      }));
    }
  },
  methods: {
    onChange(newValue) {
      const oldValue = this.selected;

      if (newValue === 'what I had before') {
        this.$nextTick(() => {
          this.selected = oldValue;
        });
      }
    }
  }
});
<script src="https://unpkg.com/vue@latest/dist/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.js"></script>
<div id="app">

  <b-form-select v-model="selected" :options="selectOptions" @change="onChange">
  </b-form-select>

  <div>Selected: <strong>{{selected}}</strong></div>

</div>

答案#2

我想提出一個基於 $refs 和計算屬性 getter/setter 的解決方案(但也可以透過 v-bind/v-on 達成)。這種功能有其合理的使用案例,例如要求使用者確認。

<template>
    <div>
        <b-form-select
            ref="mySelect"
            v-model="choiceHandler"
            :options="selectOptions"
        >
        </b-form-select>
    </div>
</template>
<script>
export default {
    data() {
        return {
            selectOptions: [
                { value: 1, text: "Choice 1" },
                { value: 2, text: "Choice 2" },
                { value: 3, text: "Choice 3" },
            ],
            storedChoice: 3,
        };
    },
    computed: {
        choiceHandler: {
            get() {
                return this.storedChoice;
            },
            set(newValue) {
                this.$refs.typeSelect.localValue = this.storedChoice; // this reverts displayed value to the one resulting from stored value
                let confirmDialog = new Promise((resolve) => {
                    // this promise is a mockup for an asycnc dialog returning true / false
                    setTimeout(() => {
                        resolve(true);
                    }, 1500);
                });
                confirmDialog.then((res) => {
                    if (res) {
                        this.storedChoice = newValue;
                    } else console.log("No changes");
                });
            },
        },
    },
};
</script>

我們需要使用 setter 來防止使用者的更改被傳遞到資料模型/存儲。在驗證之前,我們將 b-form-select 的 localValue 設置為使用者操作前的值。如果驗證通過,我們會更新資料模型中的值,這會讓 Vue 重新整理 b-form-select。如果驗證未通過,我們則不做任何操作。

此操作可能會觸發 Vue 警告,關於直接操作 DOM(取決於你在這裡具體做了什麼),然而我認為這是合理的使用,因為我們並不是想顯示覆蓋我們數據模型的數據——相反——我們是防止瀏覽器顯示尚未存儲在模型中的數據。

你也可以在這裡查看我使用純 <select> 標籤(不是 BootstrapVue——它的運作方式略有不同)以及 v-bind/v-on 而不是 getter/setter 方法來解決同樣問題的解法。