Skip to content

Commit e132ad6

Browse files
Send JS command values on phx-submit (#3688)
* Send JS command values on phx-submit Alternative implementation for #3674. Co-Authored-By: João Otávio Biondo <[email protected]> * clarify and test precedence --------- Co-authored-by: João Otávio Biondo <[email protected]>
1 parent 84e256d commit e132ad6

File tree

5 files changed

+247
-27
lines changed

5 files changed

+247
-27
lines changed

assets/js/phoenix_live_view/view.js

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ export let prependFormDataKey = (key, prefix) => {
6767
return baseKey
6868
}
6969

70-
let serializeForm = (form, metadata, onlyNames = []) => {
71-
const {submitter, ...meta} = metadata
70+
let serializeForm = (form, opts, onlyNames = []) => {
71+
const {submitter} = opts
7272

7373
// We must inject the submitter in the order that it exists in the DOM
7474
// relative to other inputs. For example, for checkbox groups, the order must be maintained.
@@ -133,8 +133,6 @@ let serializeForm = (form, metadata, onlyNames = []) => {
133133
submitter.parentElement.removeChild(injectedElement)
134134
}
135135

136-
for(let metaKey in meta){ params.append(metaKey, meta[metaKey]) }
137-
138136
return params.toString()
139137
}
140138

@@ -1197,12 +1195,13 @@ export default class View {
11971195
], phxEvent, "change", opts)
11981196
}
11991197
let formData
1200-
let meta = this.extractMeta(inputEl.form)
1201-
if(inputEl instanceof HTMLButtonElement){ meta.submitter = inputEl }
1198+
let meta = this.extractMeta(inputEl.form, {}, opts.value)
1199+
let serializeOpts = {}
1200+
if(inputEl instanceof HTMLButtonElement){ serializeOpts.submitter = inputEl }
12021201
if(inputEl.getAttribute(this.binding("change"))){
1203-
formData = serializeForm(inputEl.form, {_target: opts._target, ...meta}, [inputEl.name])
1202+
formData = serializeForm(inputEl.form, serializeOpts, [inputEl.name])
12041203
} else {
1205-
formData = serializeForm(inputEl.form, {_target: opts._target, ...meta})
1204+
formData = serializeForm(inputEl.form, serializeOpts)
12061205
}
12071206
if(DOM.isUploadInput(inputEl) && inputEl.files && inputEl.files.length > 0){
12081207
LiveUploader.trackFiles(inputEl, Array.from(inputEl.files))
@@ -1213,6 +1212,7 @@ export default class View {
12131212
type: "form",
12141213
event: phxEvent,
12151214
value: formData,
1215+
meta: {_target: opts._target, ...meta},
12161216
uploads: uploads,
12171217
cid: cid
12181218
}
@@ -1326,22 +1326,24 @@ export default class View {
13261326
if(LiveUploader.inputsAwaitingPreflight(formEl).length > 0){
13271327
return this.undoRefs(ref, phxEvent)
13281328
}
1329-
let meta = this.extractMeta(formEl)
1330-
let formData = serializeForm(formEl, {submitter, ...meta})
1329+
let meta = this.extractMeta(formEl, {}, opts.value)
1330+
let formData = serializeForm(formEl, {submitter})
13311331
this.pushWithReply(proxyRefGen, "event", {
13321332
type: "form",
13331333
event: phxEvent,
13341334
value: formData,
1335+
meta: meta,
13351336
cid: cid
13361337
}).then(({resp}) => onReply(resp))
13371338
})
13381339
} else if(!(formEl.hasAttribute(PHX_REF_SRC) && formEl.classList.contains("phx-submit-loading"))){
1339-
let meta = this.extractMeta(formEl)
1340-
let formData = serializeForm(formEl, {submitter, ...meta})
1340+
let meta = this.extractMeta(formEl, {}, opts.value)
1341+
let formData = serializeForm(formEl, {submitter})
13411342
this.pushWithReply(refGenerator, "event", {
13421343
type: "form",
13431344
event: phxEvent,
13441345
value: formData,
1346+
meta: meta,
13451347
cid: cid
13461348
}).then(({resp}) => onReply(resp))
13471349
}

assets/test/js_test.js

Lines changed: 133 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,93 @@ describe("JS", () => {
565565
JS.exec(event, "change", form.getAttribute("phx-change"), view, input, args)
566566
})
567567

568+
test("form change event with phx-value and JS command value", done => {
569+
let view = setupView(`
570+
<div id="modal" class="modal">modal</div>
571+
<form id="my-form"
572+
phx-change='[["push", {"event": "validate", "_target": "username", "value": {"command_value": "command","nested":{"array":[1,2]}}}]]'
573+
phx-submit="submit"
574+
phx-value-attribute_value="attribute"
575+
>
576+
<input type="text" name="username" id="username" phx-click=''></div>
577+
</form>
578+
`)
579+
let form = document.querySelector("#my-form")
580+
let input = document.querySelector("#username")
581+
view.pushWithReply = (refGen, event, payload) => {
582+
expect(payload).toEqual({
583+
"cid": null,
584+
"event": "validate",
585+
"type": "form",
586+
"value": "_unused_username=&username=",
587+
"meta": {
588+
"_target": "username",
589+
"command_value": "command",
590+
"nested": {
591+
"array": [1, 2]
592+
},
593+
"attribute_value": "attribute"
594+
},
595+
"uploads": {}
596+
})
597+
return Promise.resolve({resp: done()})
598+
}
599+
let args = ["push", {_target: input.name, dispatcher: input}]
600+
JS.exec(event, "change", form.getAttribute("phx-change"), view, input, args)
601+
})
602+
603+
test("form change event prefers JS.push value over phx-value-* over input value", (done) => {
604+
let view = setupView(`
605+
<form id="my-form" phx-value-name="value from phx-value param" phx-change="[[&quot;push&quot;,{&quot;value&quot;:{&quot;name&quot;:&quot;value from push opts&quot;},&quot;event&quot;:&quot;change&quot;}]]">
606+
<input id="textField" name="name" value="input value" />
607+
</form>
608+
`)
609+
let form = document.querySelector("#my-form")
610+
let input = document.querySelector("#textField")
611+
view.pushWithReply = (refGen, event, payload) => {
612+
expect(payload).toEqual({
613+
"cid": null,
614+
"event": "change",
615+
"type": "form",
616+
"value": "_unused_name=&name=input+value",
617+
"meta": {
618+
"_target": "name",
619+
"name": "value from push opts"
620+
},
621+
"uploads": {}
622+
})
623+
return Promise.resolve({resp: done()})
624+
}
625+
let args = ["push", {_target: input.name, dispatcher: input}]
626+
JS.exec(event, "change", form.getAttribute("phx-change"), view, input, args)
627+
})
628+
629+
test("form change event prefers phx-value-* over input value", (done) => {
630+
let view = setupView(`
631+
<form id="my-form" phx-value-name="value from phx-value param" phx-change="change">
632+
<input id="textField" name="name" value="input value" />
633+
</form>
634+
`)
635+
let form = document.querySelector("#my-form")
636+
let input = document.querySelector("#textField")
637+
view.pushWithReply = (refGen, event, payload) => {
638+
expect(payload).toEqual({
639+
"cid": null,
640+
"event": "change",
641+
"type": "form",
642+
"value": "_unused_name=&name=input+value",
643+
"meta": {
644+
"_target": "name",
645+
"name": "value from phx-value param"
646+
},
647+
"uploads": {}
648+
})
649+
return Promise.resolve({resp: done()})
650+
}
651+
let args = ["push", {_target: input.name, dispatcher: input}]
652+
JS.exec(event, "change", form.getAttribute("phx-change"), view, input, args)
653+
})
654+
568655
test("form change event with string event", done => {
569656
let view = setupView(`
570657
<div id="modal" class="modal">modal</div>
@@ -589,7 +676,8 @@ describe("JS", () => {
589676
event: "validate",
590677
type: "form",
591678
uploads: {},
592-
value: "_unused_username=&username=&_unused_other=&other=&_target=username"
679+
value: "_unused_username=&username=&_unused_other=&other=",
680+
meta: {"_target": "username"}
593681
})
594682
return Promise.resolve({resp: done()})
595683
}
@@ -620,7 +708,8 @@ describe("JS", () => {
620708
event: "username_changed",
621709
type: "form",
622710
uploads: {},
623-
value: "_unused_username=&username=&_target=username"
711+
value: "_unused_username=&username=",
712+
meta: {"_target": "username"}
624713
})
625714
return Promise.resolve({resp: done()})
626715
}
@@ -651,7 +740,8 @@ describe("JS", () => {
651740
event: "username_changed",
652741
type: "form",
653742
uploads: {},
654-
value: "_unused_username=&username=&_target=username"
743+
value: "_unused_username=&username=",
744+
meta: {"_target": "username"}
655745
})
656746
return Promise.resolve({resp: done()})
657747
}
@@ -670,7 +760,46 @@ describe("JS", () => {
670760
let form = document.querySelector("#my-form")
671761

672762
view.pushWithReply = (refGen, event, payload) => {
673-
expect(payload).toEqual({"cid": null, "event": "save", "type": "form", "value": "username=&desc="})
763+
expect(payload).toEqual({
764+
"cid": null,
765+
"event": "save",
766+
"type": "form",
767+
"value": "username=&desc=",
768+
"meta": {}
769+
})
770+
return Promise.resolve({resp: done()})
771+
}
772+
JS.exec(event, "submit", form.getAttribute("phx-submit"), view, form, ["push", {}])
773+
})
774+
775+
test("submit event with phx-value and JS command value", done => {
776+
let view = setupView(`
777+
<div id="modal" class="modal">modal</div>
778+
<form id="my-form"
779+
phx-change="validate"
780+
phx-submit='[["push", {"event": "save", "value": {"command_value": "command","nested":{"array":[1,2]}}}]]'
781+
phx-value-attribute_value="attribute"
782+
>
783+
<input type="text" name="username" id="username" />
784+
<input type="text" name="desc" id="desc" phx-change="desc_changed" />
785+
</form>
786+
`)
787+
let form = document.querySelector("#my-form")
788+
789+
view.pushWithReply = (refGen, event, payload) => {
790+
expect(payload).toEqual({
791+
"cid": null,
792+
"event": "save",
793+
"type": "form",
794+
"value": "username=&desc=",
795+
"meta": {
796+
"command_value": "command",
797+
"nested": {
798+
"array": [1, 2]
799+
},
800+
"attribute_value": "attribute"
801+
}
802+
})
674803
return Promise.resolve({resp: done()})
675804
}
676805
JS.exec(event, "submit", form.getAttribute("phx-submit"), view, form, ["push", {}])

0 commit comments

Comments
 (0)