Skip to content

Commit 2f1d0ee

Browse files
committed
Handle collections of checkboxes as data arrays
1 parent 255d9f9 commit 2f1d0ee

File tree

3 files changed

+41
-10
lines changed

3 files changed

+41
-10
lines changed

src/LiveComponent/assets/dist/live_controller.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -953,14 +953,26 @@ function buildSearchParams(searchParams, data) {
953953
return searchParams;
954954
}
955955

956-
function setDeepData(data, propertyPath, value) {
956+
function setDeepData(data, propertyPath, value, modelValue = null) {
957957
const finalData = JSON.parse(JSON.stringify(data));
958958
let currentLevelData = finalData;
959959
const parts = propertyPath.split('.');
960960
for (let i = 0; i < parts.length - 1; i++) {
961961
currentLevelData = currentLevelData[parts[i]];
962962
}
963963
const finalKey = parts[parts.length - 1];
964+
if (currentLevelData instanceof Array) {
965+
if (null === value) {
966+
const index = currentLevelData.indexOf(modelValue);
967+
if (index > -1) {
968+
currentLevelData.splice(index, 1);
969+
}
970+
}
971+
else {
972+
currentLevelData.push(value);
973+
}
974+
return finalData;
975+
}
964976
if (typeof currentLevelData !== 'object') {
965977
const lastPart = parts.pop();
966978
throw new Error(`Cannot set data-model="${propertyPath}". They parent "${parts.join(',')}" data does not appear to be an object (it's "${currentLevelData}"). Did you forget to add exposed={"${lastPart}"} to its LiveProp?`);
@@ -1137,9 +1149,9 @@ class default_1 extends Controller {
11371149
}
11381150
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.`);
11391151
}
1140-
this.$updateModel(model, value, shouldRender, element.hasAttribute('name') ? element.getAttribute('name') : null);
1152+
this.$updateModel(model, value, shouldRender, element.hasAttribute('name') ? element.getAttribute('name') : null, {}, element.hasAttribute('value') ? element.getAttribute('value') : null);
11411153
}
1142-
$updateModel(model, value, shouldRender = true, extraModelName = null, options = {}) {
1154+
$updateModel(model, value, shouldRender = true, extraModelName = null, options = {}, modelValue = null) {
11431155
const directives = parseDirectives(model);
11441156
if (directives.length > 1) {
11451157
throw new Error(`The data-model="${model}" format is invalid: it does not support multiple directives (i.e. remove any spaces).`);
@@ -1162,9 +1174,10 @@ class default_1 extends Controller {
11621174
modelName,
11631175
extraModelName: normalizedExtraModelName,
11641176
value,
1177+
modelValue
11651178
});
11661179
}
1167-
this.dataValue = setDeepData(this.dataValue, modelName, value);
1180+
this.dataValue = setDeepData(this.dataValue, modelName, value, modelValue);
11681181
directive.modifiers.forEach((modifier => {
11691182
switch (modifier.name) {
11701183
default:
@@ -1481,7 +1494,7 @@ class default_1 extends Controller {
14811494
}
14821495
this.$updateModel(foundModelName, event.detail.value, false, null, {
14831496
dispatch: false
1484-
});
1497+
}, event.detail.modelValue);
14851498
}
14861499
_shouldChildLiveElementUpdate(fromEl, toEl) {
14871500
if (!fromEl.dataset.originalData) {

src/LiveComponent/assets/src/live_controller.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ export default class extends Controller {
232232
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.`);
233233
}
234234

235-
this.$updateModel(model, value, shouldRender, element.hasAttribute('name') ? element.getAttribute('name') : null);
235+
this.$updateModel(model, value, shouldRender, element.hasAttribute('name') ? element.getAttribute('name') : null, {}, element.hasAttribute('value') ? element.getAttribute('value') : null);
236236
}
237237

238238
/**
@@ -251,8 +251,9 @@ export default class extends Controller {
251251
* @param {boolean} shouldRender Whether a re-render should be triggered
252252
* @param {string|null} extraModelName Another model name that this might go by in a parent component.
253253
* @param {Object} options Options include: {bool} dispatch
254+
* @param {any} modelValue Original HTML element value (for handling collection checkbox fields)
254255
*/
255-
$updateModel(model: string, value: any, shouldRender = true, extraModelName: string | null = null, options: any = {}) {
256+
$updateModel(model: string, value: any, shouldRender = true, extraModelName: string | null = null, options: any = {}, modelValue: any = null) {
256257
const directives = parseDirectives(model);
257258
if (directives.length > 1) {
258259
throw new Error(`The data-model="${model}" format is invalid: it does not support multiple directives (i.e. remove any spaces).`);
@@ -283,6 +284,7 @@ export default class extends Controller {
283284
modelName,
284285
extraModelName: normalizedExtraModelName,
285286
value,
287+
modelValue
286288
});
287289
}
288290

@@ -299,7 +301,7 @@ export default class extends Controller {
299301
// Then, then modify the data-model="post.title" field. In theory,
300302
// we should be smart enough to convert the post data - which is now
301303
// the string "4" - back into an array with [id=4, title=new_title].
302-
this.dataValue = setDeepData(this.dataValue, modelName, value);
304+
this.dataValue = setDeepData(this.dataValue, modelName, value, modelValue);
303305

304306
directive.modifiers.forEach((modifier => {
305307
switch (modifier.name) {
@@ -729,7 +731,8 @@ export default class extends Controller {
729731
null,
730732
{
731733
dispatch: false
732-
}
734+
},
735+
event.detail.modelValue
733736
);
734737
}
735738

src/LiveComponent/assets/src/set_deep_data.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// post.user.username
2-
export function setDeepData(data, propertyPath, value) {
2+
export function setDeepData(data, propertyPath, value, modelValue = null) {
33
// cheap way to deep clone simple data
44
const finalData = JSON.parse(JSON.stringify(data));
55

@@ -14,6 +14,21 @@ export function setDeepData(data, propertyPath, value) {
1414
// now finally change the key on that deeper object
1515
const finalKey = parts[parts.length - 1];
1616

17+
// if currentLevelData is an array we are in a collection situation
18+
// we need to handle addition and removal of values from it to send
19+
// back only required data
20+
if (currentLevelData instanceof Array) {
21+
if (null === value) {
22+
const index = currentLevelData.indexOf(modelValue);
23+
if (index > -1) {
24+
currentLevelData.splice(index, 1);
25+
}
26+
} else {
27+
currentLevelData.push(value);
28+
}
29+
return finalData;
30+
}
31+
1732
// make sure the currentLevelData is an object, not a scalar
1833
// if it is, it means the initial data didn't know that sub-properties
1934
// could be exposed. Or, you're just trying to set some deep

0 commit comments

Comments
 (0)