Browse Source

Date picker refactor dates selection: fix 12323 (#12347)

* date-picker: refactor type='dates'

fix issue #12323
{month, year} table highlights all selected dates
nuke selectedDates to provide cleaner data flow

* doc: correct date-picker's array usage

empty value must be falsy (empty array should not be used)
Jiewei Qian 7 năm trước cách đây
mục cha
commit
02176e26f4

+ 4 - 4
examples/docs/en-US/date-picker.md

@@ -66,8 +66,8 @@
         value10: '',
         value11: '',
         value12: '',
-        value13: [],
-        value14: []
+        value13: '',
+        value14: ''
       };
     }
   };
@@ -232,7 +232,7 @@ You can choose week, month, year or multiple dates by extending the standard dat
         value3: '',
         value4: '',
         value5: '',
-        value14: []
+        value14: ''
       };
     }
   };
@@ -463,7 +463,7 @@ When picking a date range, you can assign the time part for start date and end d
   export default {
     data() {
       return {
-        value13: []
+        value13: ''
       };
     }
   };

+ 4 - 4
examples/docs/es/date-picker.md

@@ -66,8 +66,8 @@
         value10: '',
         value11: '',
         value12: '',
-        value13: [],
-        value14: []
+        value13: '',
+        value14: ''
       };
     }
   };
@@ -234,7 +234,7 @@ You can choose week, month, year or multiple dates by extending the standard dat
         value3: '',
         value4: '',
         value5: '',
-        value14: []
+        value14: ''
       };
     }
   };
@@ -466,7 +466,7 @@ Al seleccionar un intervalo de fechas, puede asignar la hora para la fecha de in
   export default {
     data() {
       return {
-        value12: []
+        value12: ''
       };
     }
   };

+ 5 - 5
examples/docs/zh-CN/date-picker.md

@@ -66,8 +66,8 @@
         value10: '',
         value11: '',
         value12: '',
-        value13: [],
-        value14: []
+        value13: '',
+        value14: ''
       };
     }
   };
@@ -89,7 +89,7 @@
       border-right: none;
     }
   }
-  
+
   .demo-date-picker .container {
     flex: 1;
     border-right: solid 1px #EFF2F6;
@@ -232,7 +232,7 @@
         value3: '',
         value4: '',
         value5: '',
-        value14: []
+        value14: ''
       };
     }
   };
@@ -418,7 +418,7 @@
   export default {
     data() {
       return {
-        value13: []
+        value13: ''
       };
     }
   };

+ 18 - 22
packages/date-picker/src/basic/date-table.vue

@@ -35,6 +35,7 @@
   import { getFirstDayOfMonth, getDayCountOfMonth, getWeekNumber, getStartDateOfMonth, nextDate, isDate } from '../util';
   import { hasClass } from 'element-ui/src/utils/dom';
   import Locale from 'element-ui/src/mixins/locale';
+  import { arrayFindIndex, arrayFind, coerceTruthyValueToArray } from 'element-ui/src/utils/util';
 
   const WEEKS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
   const clearHours = function(time) {
@@ -43,6 +44,14 @@
     return cloneDate.getTime();
   };
 
+  // remove the first element that satisfies `pred` from arr
+  // return a new array if modification occurs
+  // return the original array otherwise
+  const removeFromArray = function(arr, pred) {
+    const idx = typeof pred === 'function' ? arrayFindIndex(arr, pred) : arr.indexOf(pred);
+    return idx >= 0 ? [...arr.slice(0, idx), ...arr.slice(idx + 1)] : arr;
+  };
+
   export default {
     mixins: [Locale],
 
@@ -75,10 +84,6 @@
 
       disabledDate: {},
 
-      selectedDate: {
-        type: Array
-      },
-
       minDate: {},
 
       maxDate: {},
@@ -135,7 +140,7 @@
 
         const startDate = this.startDate;
         const disabledDate = this.disabledDate;
-        const selectedDate = this.selectedDate || this.value;
+        const selectedDate = this.selectionMode === 'dates' ? coerceTruthyValueToArray(this.value) : [];
         const now = clearHours(new Date());
 
         for (let i = 0; i < 6; i++) {
@@ -188,10 +193,9 @@
               }
             }
 
-            let newDate = new Date(time);
-            cell.disabled = typeof disabledDate === 'function' && disabledDate(newDate);
-            cell.selected = Array.isArray(selectedDate) &&
-              selectedDate.filter(date => date.toString() === newDate.toString())[0];
+            let cellDate = new Date(time);
+            cell.disabled = typeof disabledDate === 'function' && disabledDate(cellDate);
+            cell.selected = arrayFind(selectedDate, date => date.getTime() === cellDate.getTime());
 
             this.$set(row, this.showWeekNumber ? j + 1 : j, cell);
           }
@@ -483,19 +487,11 @@
             date: newDate
           });
         } else if (selectionMode === 'dates') {
-          let selectedDate = this.selectedDate;
-
-          if (!cell.selected) {
-            selectedDate.push(newDate);
-          } else {
-            selectedDate.forEach((date, index) => {
-              if (date.toString() === newDate.toString()) {
-                selectedDate.splice(index, 1);
-              }
-            });
-          }
-
-          this.$emit('select', selectedDate);
+          const value = this.value || [];
+          const newValue = cell.selected
+            ? removeFromArray(value, date => date.getTime() === newDate.getTime())
+            : [...value, newDate];
+          this.$emit('pick', newValue);
         }
       }
     }

+ 2 - 1
packages/date-picker/src/basic/month-table.vue

@@ -51,6 +51,7 @@
   import Locale from 'element-ui/src/mixins/locale';
   import { isDate, range, getDayCountOfMonth, nextDate } from '../util';
   import { hasClass } from 'element-ui/src/utils/dom';
+  import { arrayFindIndex, coerceTruthyValueToArray } from 'element-ui/src/utils/util';
 
   const datesInMonth = (year, month) => {
     const numOfDays = getDayCountOfMonth(year, month);
@@ -80,7 +81,7 @@
         style.disabled = typeof this.disabledDate === 'function'
           ? datesInMonth(year, month).every(this.disabledDate)
           : false;
-        style.current = this.value.getFullYear() === year && this.value.getMonth() === month;
+        style.current = arrayFindIndex(coerceTruthyValueToArray(this.value), date => date.getFullYear() === year && date.getMonth() === month) >= 0;
         style.today = today.getFullYear() === year && today.getMonth() === month;
         style.default = this.defaultValue &&
           this.defaultValue.getFullYear() === year &&

+ 2 - 1
packages/date-picker/src/basic/year-table.vue

@@ -46,6 +46,7 @@
 <script type="text/babel">
   import { hasClass } from 'element-ui/src/utils/dom';
   import { isDate, range, nextDate, getDayCountOfYear } from '../util';
+  import { arrayFindIndex, coerceTruthyValueToArray } from 'element-ui/src/utils/util';
 
   const datesInYear = year => {
     const numOfDays = getDayCountOfYear(year);
@@ -80,7 +81,7 @@
         style.disabled = typeof this.disabledDate === 'function'
           ? datesInYear(year).every(this.disabledDate)
           : false;
-        style.current = this.value.getFullYear() === year;
+        style.current = arrayFindIndex(coerceTruthyValueToArray(this.value), date => date.getFullYear() === year) >= 0;
         style.today = today.getFullYear() === year;
         style.default = this.defaultValue && this.defaultValue.getFullYear() === year;
 

+ 7 - 14
packages/date-picker/src/panel/date.vue

@@ -91,19 +91,17 @@
             <date-table
               v-show="currentView === 'date'"
               @pick="handleDatePick"
-              @select="handleDateSelect"
               :selection-mode="selectionMode"
               :first-day-of-week="firstDayOfWeek"
-              :value="new Date(value)"
+              :value="value"
               :default-value="defaultValue ? new Date(defaultValue) : null"
               :date="date"
-              :disabled-date="disabledDate"
-              :selected-date="selectedDate">
+              :disabled-date="disabledDate">
             </date-table>
             <year-table
               v-show="currentView === 'year'"
               @pick="handleYearPick"
-              :value="new Date(value)"
+              :value="value"
               :default-value="defaultValue ? new Date(defaultValue) : null"
               :date="date"
               :disabled-date="disabledDate">
@@ -111,7 +109,7 @@
             <month-table
               v-show="currentView === 'month'"
               @pick="handleMonthPick"
-              :value="new Date(value)"
+              :value="value"
               :default-value="defaultValue ? new Date(defaultValue) : null"
               :date="date"
               :disabled-date="disabledDate">
@@ -333,12 +331,6 @@
         }
       },
 
-      handleDateSelect(value) {
-        if (this.selectionMode === 'dates') {
-          this.selectedDate = value;
-        }
-      },
-
       handleDatePick(value) {
         if (this.selectionMode === 'day') {
           this.date = this.value
@@ -347,6 +339,8 @@
           this.emit(this.date, this.showTime);
         } else if (this.selectionMode === 'week') {
           this.emit(value.date);
+        } else if (this.selectionMode === 'dates') {
+          this.emit(value, true); // set false to keep panel open
         }
       },
 
@@ -373,7 +367,7 @@
 
       confirm() {
         if (this.selectionMode === 'dates') {
-          this.emit(this.selectedDate);
+          this.emit(this.value);
         } else {
           // value were emitted in handle{Date,Time}Pick, nothing to update here
           // deal with the scenario where: user opens the picker, then confirm without doing anything
@@ -506,7 +500,6 @@
         visible: false,
         currentView: 'date',
         disabledDate: '',
-        selectedDate: [],
         firstDayOfWeek: 7,
         showWeekNumber: false,
         timePickerVisible: false,

+ 5 - 11
packages/date-picker/src/picker.vue

@@ -419,7 +419,6 @@ export default {
       handler(val) {
         if (this.picker) {
           this.picker.value = val;
-          this.picker.selectedDate = Array.isArray(val) ? val : [];
         }
       }
     },
@@ -705,15 +704,11 @@ export default {
     handleClose() {
       if (!this.pickerVisible) return;
       this.pickerVisible = false;
-      const {
-        type,
-        valueOnOpen,
-        valueFormat,
-        rangeSeparator
-      } = this;
-      if (type === 'dates' && this.picker) {
-        this.picker.selectedDate = parseAsFormatAndType(valueOnOpen, valueFormat, type, rangeSeparator) || valueOnOpen;
-        this.emitInput(this.picker.selectedDate);
+
+      if (this.type === 'dates') {
+        // restore to former value
+        const oldValue = parseAsFormatAndType(this.valueOnOpen, this.valueFormat, this.type, this.rangeSeparator) || this.valueOnOpen;
+        this.emitInput(oldValue);
       }
     },
 
@@ -828,7 +823,6 @@ export default {
       this.picker.selectionMode = this.selectionMode;
       this.picker.unlinkPanels = this.unlinkPanels;
       this.picker.arrowControl = this.arrowControl || this.timeArrowControl || false;
-      this.picker.selectedDate = Array.isArray(this.value) && this.value || [];
       this.$watch('format', (format) => {
         this.picker.format = format;
       });

+ 26 - 0
src/utils/util.js

@@ -84,3 +84,29 @@ export const valueEquals = (a, b) => {
 };
 
 export const escapeRegexpString = (value = '') => String(value).replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
+
+// TODO: use native Array.find, Array.findIndex when IE support is dropped
+export const arrayFindIndex = function(arr, pred) {
+  for (let i = 0; i !== arr.length; ++i) {
+    if (pred(arr[i])) {
+      return i;
+    }
+  }
+  return -1;
+};
+
+export const arrayFind = function(arr, pred) {
+  const idx = arrayFindIndex(arr, pred);
+  return idx !== -1 ? arr[idx] : undefined;
+};
+
+// coerce truthy value to array
+export const coerceTruthyValueToArray = function(val) {
+  if (Array.isArray(val)) {
+    return val;
+  } else if (val) {
+    return [val];
+  } else {
+    return [];
+  }
+};

+ 3 - 14
test/unit/specs/date-picker.spec.js

@@ -1517,25 +1517,14 @@ describe('DatePicker', () => {
       const td = vm.$refs.compo.picker.$el.querySelector('.el-date-table__row .available');
       td.click();
       setTimeout(_ => {
-        expect(vm.$refs.compo.picker.selectedDate).to.exist;
+        expect(vm.$refs.compo.value).to.be.an('array');
+        expect(vm.$refs.compo.value.length).to.equal(1);
+        expect(vm.$refs.compo.value[0]).to.be.a('number');
         expect(vm.value.length).to.equal(1);
         done();
       }, DELAY);
     });
 
-    it('value format', done => {
-      const td = vm.$refs.compo.picker.$el.querySelector('.el-date-table__row .available');
-      td.click();
-      setTimeout(_ => {
-        vm.$refs.compo.picker.$el.querySelector('.el-button--default').click();
-        setTimeout(() => {
-          expect(vm.$refs.compo.picker.selectedDate).to.exist;
-          expect(vm.value.length).to.equal(1);
-          done();
-        }, DELAY);
-      }, DELAY);
-    });
-
     it('restore value when cancel', done => {
       const td = vm.$refs.compo.picker.$el.querySelector('.el-date-table__row .available');
       td.click();