Skip to content

Commit 36778a3

Browse files
committed
Handle collections of checkboxes as data arrays
1 parent df64d0c commit 36778a3

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?`);
@@ -1126,9 +1138,9 @@ class default_1 extends Controller {
11261138
}
11271139
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.`);
11281140
}
1129-
this.$updateModel(model, value, shouldRender, element.hasAttribute('name') ? element.getAttribute('name') : null);
1141+
this.$updateModel(model, value, shouldRender, element.hasAttribute('name') ? element.getAttribute('name') : null, {}, element.hasAttribute('value') ? element.getAttribute('value') : null);
11301142
}
1131-
$updateModel(model, value, shouldRender = true, extraModelName = null, options = {}) {
1143+
$updateModel(model, value, shouldRender = true, extraModelName = null, options = {}, modelValue = null) {
11321144
const directives = parseDirectives(model);
11331145
if (directives.length > 1) {
11341146
throw new Error(`The data-model="${model}" format is invalid: it does not support multiple directives (i.e. remove any spaces).`);
@@ -1151,9 +1163,10 @@ class default_1 extends Controller {
11511163
modelName,
11521164
extraModelName: normalizedExtraModelName,
11531165
value,
1166+
modelValue
11541167
});
11551168
}
1156-
this.dataValue = setDeepData(this.dataValue, modelName, value);
1169+
this.dataValue = setDeepData(this.dataValue, modelName, value, modelValue);
11571170
directive.modifiers.forEach((modifier => {
11581171
switch (modifier.name) {
11591172
default:
@@ -1470,7 +1483,7 @@ class default_1 extends Controller {
14701483
}
14711484
this.$updateModel(foundModelName, event.detail.value, false, null, {
14721485
dispatch: false
1473-
});
1486+
}, event.detail.modelValue);
14741487
}
14751488
_shouldChildLiveElementUpdate(fromEl, toEl) {
14761489
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
@@ -216,7 +216,7 @@ export default class extends Controller {
216216
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.`);
217217
}
218218

219-
this.$updateModel(model, value, shouldRender, element.hasAttribute('name') ? element.getAttribute('name') : null);
219+
this.$updateModel(model, value, shouldRender, element.hasAttribute('name') ? element.getAttribute('name') : null, {}, element.hasAttribute('value') ? element.getAttribute('value') : null);
220220
}
221221

222222
/**
@@ -235,8 +235,9 @@ export default class extends Controller {
235235
* @param {boolean} shouldRender Whether a re-render should be triggered
236236
* @param {string|null} extraModelName Another model name that this might go by in a parent component.
237237
* @param {Object} options Options include: {bool} dispatch
238+
* @param {any} modelValue Original HTML element value (for handling collection checkbox fields)
238239
*/
239-
$updateModel(model: string, value: any, shouldRender = true, extraModelName: string | null = null, options: any = {}) {
240+
$updateModel(model: string, value: any, shouldRender = true, extraModelName: string | null = null, options: any = {}, modelValue: any = null) {
240241
const directives = parseDirectives(model);
241242
if (directives.length > 1) {
242243
throw new Error(`The data-model="${model}" format is invalid: it does not support multiple directives (i.e. remove any spaces).`);
@@ -267,6 +268,7 @@ export default class extends Controller {
267268
modelName,
268269
extraModelName: normalizedExtraModelName,
269270
value,
271+
modelValue
270272
});
271273
}
272274

@@ -283,7 +285,7 @@ export default class extends Controller {
283285
// Then, then modify the data-model="post.title" field. In theory,
284286
// we should be smart enough to convert the post data - which is now
285287
// the string "4" - back into an array with [id=4, title=new_title].
286-
this.dataValue = setDeepData(this.dataValue, modelName, value);
288+
this.dataValue = setDeepData(this.dataValue, modelName, value, modelValue);
287289

288290
directive.modifiers.forEach((modifier => {
289291
switch (modifier.name) {
@@ -713,7 +715,8 @@ export default class extends Controller {
713715
null,
714716
{
715717
dispatch: false
716-
}
718+
},
719+
event.detail.modelValue
717720
);
718721
}
719722

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)