Skip to content

Commit f342a7a

Browse files
committed
Introduce getArrayValue with unit tests
1 parent 7580a73 commit f342a7a

File tree

3 files changed

+104
-16
lines changed

3 files changed

+104
-16
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* Resolve multiple value data from changed HTML element.
3+
*
4+
* @param element Current HTML element
5+
* @param value Resolved value of a single HTML element (.value or [data-value])
6+
* @param currentValue Current data value
7+
*/
8+
export function getArrayValue(
9+
element: HTMLElement,
10+
value: string|null,
11+
currentValue: any
12+
): Array<string>|null {
13+
// Enforce returned value is an array
14+
if (!(currentValue instanceof Array)) {
15+
currentValue = [];
16+
}
17+
18+
if (element instanceof HTMLInputElement && element.type === 'checkbox') {
19+
const index = currentValue.indexOf(value);
20+
21+
if (element.checked) {
22+
// Add value to an array if it's not in it already
23+
if (index === -1) {
24+
currentValue.push(value);
25+
}
26+
} else {
27+
// Remove value from an array
28+
if (index > -1) {
29+
currentValue.splice(index, 1);
30+
}
31+
}
32+
} else if (element instanceof HTMLSelectElement) {
33+
// Select elements with `multiple` option require mapping chosen options to their values
34+
currentValue = Array.from(element.selectedOptions).map(el => el.value);
35+
}
36+
37+
// When no values are selected for collection no data should be sent over the wire
38+
return currentValue.length ? currentValue : null;
39+
}

src/LiveComponent/assets/src/live_controller.ts

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {setDeepData, doesDeepPropertyExist, normalizeModelName, parseDeepData} f
66
import { haveRenderedValuesChanged } from './have_rendered_values_changed';
77
import { normalizeAttributesForComparison } from './normalize_attributes_for_comparison';
88
import { cloneHTMLElement } from './clone_html_element';
9+
import {getArrayValue} from "./get_array_value";
910

1011
interface ElementLoadingDirectives {
1112
element: HTMLElement,
@@ -199,28 +200,22 @@ export default class extends Controller {
199200
throw new Error(`The update() method could not be called for "${clonedElement.outerHTML}": the element must either have a "data-model" or "name" attribute set to the model name.`);
200201
}
201202

202-
if (element instanceof HTMLInputElement && element.type === 'checkbox' && !element.checked) {
203-
value = null;
204-
}
205-
206203
// HTML form elements with name ending with [] require array as data
207204
// we need to handle addition and removal of values from it to send
208205
// back only required data
209206
if (/\[]$/.test(model)) {
207+
// Get current value from data
210208
const {currentLevelData, finalKey} = parseDeepData(this.dataValue, normalizeModelName(model))
211-
212209
const currentValue = currentLevelData[finalKey];
213-
if (currentValue instanceof Array) {
214-
if (null === value) {
215-
const index = currentValue.indexOf(this._getValueFromElement(element));
216-
if (index > -1) {
217-
currentValue.splice(index, 1);
218-
}
219-
} else {
220-
currentValue.push(value);
221-
}
222-
}
223-
value = currentValue;
210+
211+
value = getArrayValue(element, value, currentValue);
212+
} else if (
213+
element instanceof HTMLInputElement
214+
&& element.type === 'checkbox'
215+
&& !element.checked
216+
) {
217+
// Unchecked checkboxes in a single value scenarios should map to `null`
218+
value = null;
224219
}
225220

226221
this.$updateModel(model, value, shouldRender, element.hasAttribute('name') ? element.getAttribute('name') : null, {});
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import {getArrayValue} from "../src/get_array_value";
2+
3+
4+
describe('getArrayValue', () => {
5+
it('Correctly adds data from checkbox', () => {
6+
const input = document.createElement('input');
7+
input.type = 'checkbox';
8+
input.checked = true;
9+
10+
expect(getArrayValue(input, 'foo', null))
11+
.toEqual(['foo']);
12+
expect(getArrayValue(input, 'foo', []))
13+
.toEqual(['foo']);
14+
expect(getArrayValue(input, 'foo', ['bar']))
15+
.toEqual(['bar', 'foo']);
16+
})
17+
18+
it('Correctly removes data from checkbox', () => {
19+
const input = document.createElement('input');
20+
input.type = 'checkbox';
21+
input.checked = false;
22+
23+
expect(getArrayValue(input, 'foo', null))
24+
.toEqual(null);
25+
expect(getArrayValue(input, 'foo', ['foo']))
26+
.toEqual(null);
27+
expect(getArrayValue(input, 'foo', ['bar']))
28+
.toEqual(['bar']);
29+
expect(getArrayValue(input, 'foo', ['foo', 'bar']))
30+
.toEqual(['bar']);
31+
})
32+
33+
it('Correctly sets data from select multiple', () => {
34+
const select = document.createElement('select');
35+
select.multiple = true;
36+
const fooOption = document.createElement('option');
37+
fooOption.value = 'foo';
38+
select.add(fooOption);
39+
const barOption = document.createElement('option');
40+
barOption.value = 'bar';
41+
select.add(barOption);
42+
43+
expect(getArrayValue(select, '', null))
44+
.toEqual(null);
45+
46+
fooOption.selected = true;
47+
expect(getArrayValue(select, '', null))
48+
.toEqual(['foo']);
49+
50+
barOption.selected = true;
51+
expect(getArrayValue(select, '', null))
52+
.toEqual(['foo', 'bar']);
53+
})
54+
});

0 commit comments

Comments
 (0)