Skip to content

Commit 5de9f14

Browse files
committed
fix v-model for IE edge cases
1 parent 0f505bc commit 5de9f14

File tree

3 files changed

+65
-34
lines changed

3 files changed

+65
-34
lines changed

changes.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -289,12 +289,12 @@ computed: {
289289

290290
When used on a `<select>` element, `v-model` will check for an `options` attribute, which should be an keypath/expression that points to an Array to use as its options. The Array can contain plain strings, or contain objects.
291291

292-
The object can be in the format of `{label:'', value:''}`. This allows you to have the option displayed differently from its underlying value:
292+
The object can be in the format of `{text:'', value:''}`. This allows you to have the option text displayed differently from its underlying value:
293293
294294
``` js
295295
[
296-
{ label: 'A', value: 'a' },
297-
{ label: 'B', value: 'b' }
296+
{ text: 'A', value: 'a' },
297+
{ text: 'B', value: 'b' }
298298
]
299299
```
300300

@@ -307,7 +307,7 @@ computed: {
307307
</select>
308308
```
309309

310-
Alternatively, the object can contain an `options` Array. In this case it will be rendered as an `<optgroup>`:
310+
Alternatively, the object can be in the format of `{ label:'', options:[...] }`. In this case it will be rendered as an `<optgroup>`:
311311

312312
``` js
313313
[
@@ -321,12 +321,12 @@ computed: {
321321
``` html
322322
<select>
323323
<optgroup label="A">
324-
<option>a</option>
325-
<option>b</option>
324+
<option value="a">a</option>
325+
<option value="b">b</option>
326326
</optgroup>
327327
<optgroup label="B">
328-
<option>c</option>
329-
<option>d</option>
328+
<option value="c">c</option>
329+
<option value="d">d</option>
330330
</optgroup>
331331
</select>
332332
```

src/directives/model/select.js

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ function initOptions (expression) {
5858
var self = this
5959
function optionUpdateWatcher (value) {
6060
if (_.isArray(value)) {
61-
self.el.innerHTML = value.map(formatOption).join('')
61+
self.el.innerHTML = ''
62+
buildOptions(self.el, value)
6263
if (self._watcher) {
6364
self.update(self._watcher.value)
6465
}
@@ -76,28 +77,32 @@ function initOptions (expression) {
7677
}
7778

7879
/**
79-
* Format an option element from the "options" param.
80-
* An object indicates an <optgroup> which should include:
80+
* Build up option elements. IE9 doesn't create options
81+
* when setting innerHTML on <select> elements, so we have
82+
* to use DOM API here.
8183
*
82-
* - label: a string for the label attribute
83-
* - options: an array of option strings
84-
*
85-
* @param {Object|String} op
84+
* @param {Element} parent - a <select> or an <optgroup>
85+
* @param {Array} options
8686
*/
8787

88-
function formatOption (op) {
89-
if (typeof op !== 'string') {
90-
if (op.value) {
91-
return '<option value="' + op.value + '">' +
92-
op.label +
93-
'</option>'
94-
} else if (op.options) {
95-
return '<optgroup label="' + op.label + '">' +
96-
op.options.map(formatOption).join('') +
97-
'</optgroup>'
88+
function buildOptions (parent, options) {
89+
var op, el
90+
for (var i = 0, l = options.length; i < l; i++) {
91+
op = options[i]
92+
if (!op.options) {
93+
el = document.createElement('option')
94+
if (typeof op === 'string') {
95+
el.text = el.value = op
96+
} else {
97+
el.text = op.text
98+
el.value = op.value
99+
}
100+
} else {
101+
el = document.createElement('optgroup')
102+
el.label = op.label
103+
buildOptions(el, op.options)
98104
}
99-
} else {
100-
return '<option>' + op + '</option>'
105+
parent.appendChild(el)
101106
}
102107
}
103108

test/unit/specs/directives/model_spec.js

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,34 @@
11
var _ = require('../../../../src/util')
22
var Vue = require('../../../../src/vue')
33

4+
/**
5+
* Mock event helper
6+
*/
7+
48
function trigger (target, event, process) {
59
var e = document.createEvent('HTMLEvents')
610
e.initEvent(event, true, true)
711
if (process) process(e)
812
target.dispatchEvent(e)
913
}
1014

15+
/**
16+
* setting <select>'s value in IE9 doesn't work
17+
* we have to manually loop through the options
18+
*/
19+
20+
function updateSelect (el, value) {
21+
/* jshint eqeqeq: false */
22+
var options = el.options
23+
var i = options.length
24+
while (i--) {
25+
if (options[i].value == value) {
26+
options[i].selected = true
27+
break
28+
}
29+
}
30+
}
31+
1132
if (_.inBrowser) {
1233
describe('v-model', function () {
1334

@@ -112,7 +133,7 @@ if (_.inBrowser) {
112133
_.nextTick(function () {
113134
expect(el.firstChild.value).toBe('c')
114135
expect(el.firstChild.childNodes[2].selected).toBe(true)
115-
el.firstChild.value = 'a'
136+
updateSelect(el.firstChild, 'a')
116137
trigger(el.firstChild, 'change')
117138
expect(vm.test).toBe('a')
118139
done()
@@ -209,14 +230,14 @@ if (_.inBrowser) {
209230
})
210231
})
211232

212-
it('select + options + label', function () {
233+
it('select + options + text', function () {
213234
var vm = new Vue({
214235
el: el,
215236
data: {
216237
test: 'b',
217238
opts: [
218-
{ label: 'A', value: 'a' },
219-
{ label: 'B', value: 'b' }
239+
{ text: 'A', value: 'a' },
240+
{ text: 'B', value: 'b' }
220241
]
221242
},
222243
template: '<select v-model="test" options="opts"></select>'
@@ -244,10 +265,10 @@ if (_.inBrowser) {
244265
})
245266
expect(el.firstChild.innerHTML).toBe(
246267
'<optgroup label="A">' +
247-
'<option>a</option><option>b</option>' +
268+
'<option value="a">a</option><option value="b">b</option>' +
248269
'</optgroup>' +
249270
'<optgroup label="B">' +
250-
'<option>c</option>' +
271+
'<option value="c">c</option>' +
251272
'</optgroup>'
252273
)
253274
var opts = el.firstChild.options
@@ -378,7 +399,7 @@ if (_.inBrowser) {
378399
})
379400
})
380401

381-
it('text + compositionevents', function () {
402+
it('text + compositionevents', function (done) {
382403
var vm = new Vue({
383404
el: el,
384405
data: {
@@ -404,6 +425,11 @@ if (_.inBrowser) {
404425
trigger(input2, 'input')
405426
expect(vm.test).toBe('ccc')
406427
expect(vm.test2).toBe('ccc')
428+
// IE complains about "unspecified error" when calling
429+
// setSelectionRange() on an input element that's been
430+
// removed from the DOM, so we wait until the
431+
// selection range callback has fired to end this test.
432+
_.nextTick(done)
407433
})
408434

409435
it('textarea', function () {

0 commit comments

Comments
 (0)