Skip to content

Commit 1e31c30

Browse files
committed
Move array handling into _updateModelFromElement
1 parent d183645 commit 1e31c30

File tree

3 files changed

+86
-70
lines changed

3 files changed

+86
-70
lines changed

src/LiveComponent/assets/dist/live_controller.js

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

956-
function setDeepData(data, propertyPath, value, modelValue = null) {
956+
function getDeepData(data, propertyPath) {
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-
}
964+
return {
965+
currentLevelData,
966+
finalData,
967+
finalKey,
968+
parts
969+
};
970+
}
971+
function setDeepData(data, propertyPath, value) {
972+
const { currentLevelData, finalData, finalKey, parts } = getDeepData(data, propertyPath);
976973
if (typeof currentLevelData !== 'object') {
977974
const lastPart = parts.pop();
978975
throw new Error(`Cannot set data-model="${propertyPath}". The parent "${parts.join('.')}" data does not appear to be an object (it's "${currentLevelData}"). Did you forget to add exposed={"${lastPart}"} to its LiveProp?`);
@@ -995,10 +992,12 @@ function doesDeepPropertyExist(data, propertyPath) {
995992
}
996993
function normalizeModelName(model) {
997994
return model
995+
.replace(/\[]$/, '')
998996
.split('[')
999997
.map(function (s) {
1000998
return s.replace(']', '');
1001-
}).join('.');
999+
})
1000+
.join('.');
10021001
}
10031002

10041003
function haveRenderedValuesChanged(originalDataJson, currentDataJson, newDataJson) {
@@ -1069,18 +1068,10 @@ class default_1 extends Controller {
10691068
window.removeEventListener('beforeunload', this.markAsWindowUnloaded);
10701069
}
10711070
update(event) {
1072-
let value = this._getValueFromElement(event.target);
1073-
if (event.target.type === 'checkbox' && !event.target.checked) {
1074-
value = null;
1075-
}
1076-
this._updateModelFromElement(event.target, value, true);
1071+
this._updateModelFromElement(event.target, this._getValueFromElement(event.target), true);
10771072
}
10781073
updateDefer(event) {
1079-
let value = this._getValueFromElement(event.target);
1080-
if (event.target.type === 'checkbox' && !event.target.checked) {
1081-
value = null;
1082-
}
1083-
this._updateModelFromElement(event.target, value, false);
1074+
this._updateModelFromElement(event.target, this._getValueFromElement(event.target), false);
10841075
}
10851076
action(event) {
10861077
const rawAction = event.currentTarget.dataset.actionName;
@@ -1141,9 +1132,28 @@ class default_1 extends Controller {
11411132
}
11421133
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.`);
11431134
}
1144-
this.$updateModel(model, value, shouldRender, element.hasAttribute('name') ? element.getAttribute('name') : null, {}, this._getValueFromElement(element));
1135+
if (element instanceof HTMLInputElement && element.type === 'checkbox' && !element.checked) {
1136+
value = null;
1137+
}
1138+
if (/\[]$/.test(model)) {
1139+
const { currentLevelData, finalKey } = getDeepData(this.dataValue, normalizeModelName(model));
1140+
const currentValue = currentLevelData[finalKey];
1141+
if (currentValue instanceof Array) {
1142+
if (null === value) {
1143+
const index = currentValue.indexOf(this._getValueFromElement(element));
1144+
if (index > -1) {
1145+
currentValue.splice(index, 1);
1146+
}
1147+
}
1148+
else {
1149+
currentValue.push(value);
1150+
}
1151+
}
1152+
value = currentValue;
1153+
}
1154+
this.$updateModel(model, value, shouldRender, element.hasAttribute('name') ? element.getAttribute('name') : null, {});
11451155
}
1146-
$updateModel(model, value, shouldRender = true, extraModelName = null, options = {}, modelValue = null) {
1156+
$updateModel(model, value, shouldRender = true, extraModelName = null, options = {}) {
11471157
const directives = parseDirectives(model);
11481158
if (directives.length > 1) {
11491159
throw new Error(`The data-model="${model}" format is invalid: it does not support multiple directives (i.e. remove any spaces).`);
@@ -1165,11 +1175,10 @@ class default_1 extends Controller {
11651175
this._dispatchEvent('live:update-model', {
11661176
modelName,
11671177
extraModelName: normalizedExtraModelName,
1168-
value,
1169-
modelValue
1178+
value
11701179
});
11711180
}
1172-
this.dataValue = setDeepData(this.dataValue, modelName, value, modelValue);
1181+
this.dataValue = setDeepData(this.dataValue, modelName, value);
11731182
directive.modifiers.forEach((modifier => {
11741183
switch (modifier.name) {
11751184
default:
@@ -1485,7 +1494,7 @@ class default_1 extends Controller {
14851494
}
14861495
this.$updateModel(foundModelName, event.detail.value, false, null, {
14871496
dispatch: false
1488-
}, event.detail.modelValue);
1497+
});
14891498
}
14901499
_shouldChildLiveElementUpdate(fromEl, toEl) {
14911500
if (!fromEl.dataset.originalData) {

src/LiveComponent/assets/src/live_controller.ts

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import morphdom from 'morphdom';
33
import { parseDirectives, Directive } from './directives_parser';
44
import { combineSpacedArray } from './string_utils';
55
import { buildFormData, buildSearchParams } from './http_data_helper';
6-
import { setDeepData, doesDeepPropertyExist, normalizeModelName } from './set_deep_data';
6+
import {setDeepData, doesDeepPropertyExist, normalizeModelName, parseDeepData} from './set_deep_data';
77
import { haveRenderedValuesChanged } from './have_rendered_values_changed';
88

99
interface ElementLoadingDirectives {
@@ -104,23 +104,11 @@ export default class extends Controller {
104104
* Called to update one piece of the model
105105
*/
106106
update(event: any) {
107-
let value = this._getValueFromElement(event.target);
108-
109-
if (event.target.type === 'checkbox' && !event.target.checked) {
110-
value = null;
111-
}
112-
113-
this._updateModelFromElement(event.target, value, true);
107+
this._updateModelFromElement(event.target, this._getValueFromElement(event.target), true);
114108
}
115109

116110
updateDefer(event: any) {
117-
let value = this._getValueFromElement(event.target);
118-
119-
if (event.target.type === 'checkbox' && !event.target.checked) {
120-
value = null;
121-
}
122-
123-
this._updateModelFromElement(event.target, value, false);
111+
this._updateModelFromElement(event.target, this._getValueFromElement(event.target), false);
124112
}
125113

126114
action(event: any) {
@@ -201,7 +189,7 @@ export default class extends Controller {
201189
return element.dataset.value || (element as any).value;
202190
}
203191

204-
_updateModelFromElement(element: HTMLElement, value: string, shouldRender: boolean) {
192+
_updateModelFromElement(element: HTMLElement, value: string|null, shouldRender: boolean) {
205193
const model = element.dataset.model || element.getAttribute('name');
206194

207195
if (!model) {
@@ -214,7 +202,31 @@ export default class extends Controller {
214202
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.`);
215203
}
216204

217-
this.$updateModel(model, value, shouldRender, element.hasAttribute('name') ? element.getAttribute('name') : null, {}, this._getValueFromElement(element));
205+
if (element instanceof HTMLInputElement && element.type === 'checkbox' && !element.checked) {
206+
value = null;
207+
}
208+
209+
// HTML form elements with name ending with [] require array as data
210+
// we need to handle addition and removal of values from it to send
211+
// back only required data
212+
if (/\[]$/.test(model)) {
213+
const {currentLevelData, finalKey} = parseDeepData(this.dataValue, normalizeModelName(model))
214+
215+
const currentValue = currentLevelData[finalKey];
216+
if (currentValue instanceof Array) {
217+
if (null === value) {
218+
const index = currentValue.indexOf(this._getValueFromElement(element));
219+
if (index > -1) {
220+
currentValue.splice(index, 1);
221+
}
222+
} else {
223+
currentValue.push(value);
224+
}
225+
}
226+
value = currentValue;
227+
}
228+
229+
this.$updateModel(model, value, shouldRender, element.hasAttribute('name') ? element.getAttribute('name') : null, {});
218230
}
219231

220232
/**
@@ -233,9 +245,8 @@ export default class extends Controller {
233245
* @param {boolean} shouldRender Whether a re-render should be triggered
234246
* @param {string|null} extraModelName Another model name that this might go by in a parent component.
235247
* @param {Object} options Options include: {bool} dispatch
236-
* @param {any} modelValue Original HTML element value (for handling collection checkbox fields)
237248
*/
238-
$updateModel(model: string, value: any, shouldRender = true, extraModelName: string | null = null, options: any = {}, modelValue: any = null) {
249+
$updateModel(model: string, value: any, shouldRender = true, extraModelName: string | null = null, options: any = {}) {
239250
const directives = parseDirectives(model);
240251
if (directives.length > 1) {
241252
throw new Error(`The data-model="${model}" format is invalid: it does not support multiple directives (i.e. remove any spaces).`);
@@ -265,8 +276,7 @@ export default class extends Controller {
265276
this._dispatchEvent('live:update-model', {
266277
modelName,
267278
extraModelName: normalizedExtraModelName,
268-
value,
269-
modelValue
279+
value
270280
});
271281
}
272282

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

288298
directive.modifiers.forEach((modifier => {
289299
switch (modifier.name) {
@@ -710,8 +720,7 @@ export default class extends Controller {
710720
null,
711721
{
712722
dispatch: false
713-
},
714-
event.detail.modelValue
723+
}
715724
);
716725
}
717726

src/LiveComponent/assets/src/set_deep_data.ts

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

65
let currentLevelData = finalData;
@@ -14,20 +13,17 @@ export function setDeepData(data, propertyPath, value, modelValue = null) {
1413
// now finally change the key on that deeper object
1514
const finalKey = parts[parts.length - 1];
1615

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;
16+
return {
17+
currentLevelData,
18+
finalData,
19+
finalKey,
20+
parts
3021
}
22+
}
23+
24+
// post.user.username
25+
export function setDeepData(data, propertyPath, value) {
26+
const {currentLevelData, finalData, finalKey, parts} = parseDeepData(data, propertyPath)
3127

3228
// make sure the currentLevelData is an object, not a scalar
3329
// if it is, it means the initial data didn't know that sub-properties
@@ -79,9 +75,11 @@ export function doesDeepPropertyExist(data, propertyPath) {
7975
*/
8076
export function normalizeModelName(model) {
8177
return model
78+
.replace(/\[]$/, '')
8279
.split('[')
8380
// ['object', 'foo', 'bar', 'ya']
8481
.map(function (s) {
8582
return s.replace(']', '')
86-
}).join('.')
83+
})
84+
.join('.')
8785
}

0 commit comments

Comments
 (0)