Formular per Ajax versenden
![](/uploads/default_avatar/thumb/missing.png)
- javascript
0 1unitedpower0 heinetz
0 heinetz
0 pl0 1unitedpower0 heinetz
0 heinetz
0 1unitedpower0 heinetz
0 heinetz
0 1unitedpower0 heinetz
1 1unitedpower0 pl
Hallo Forum,
ich habe in der Vergangenheit immer mal wieder Formulare per Ajax versendet, die Response ausgewertet und per JS darauf reagiert ... aber mein Development-Stack war ein LAMP-System und ich habe dazu jQuery und u.U. jQ-Plugins verwendet. Nun heisst meine Entwicklungsumgebung NODE.js und ich will kein jQuery verwenden, sondern ES6 und u.U. NODE-Module. Ich fühle mich in der Umgebung noch nicht wirklich sicher und bin bis hierher gekommen:
import BaseModule from '../../../../javascripts/helpers/baseModule';
export default class Commentsform extends BaseModule {
constructor(element) {
super(element, element.getAttribute('data-js-module'));
this.getElements([
{ id: 'messageSubmit', selector: 'messageSubmit' }
]);
this.bindEvents();
}
bindEvents() {
this.elements.messageSubmit.addEventListener('click', this.submitForm.bind(this));
}
submitForm(event) {
console.log('submitForm()');
event.preventDefault();
const request = url => {
const ajax = new XMLHttpRequest();
ajax.open('GET', url, true);
ajax.send();
ajax.onload = () => {
console.log(ajax);
};
};
request('/html/index.html');
}
}
Was damit funktioniert:
Der Click auf den Submit-Button löst einen Ajax-Request aus und das Ajax-Object wird in der Console ausgegeben. Darin enthalten die Response …
Was fehlt:
Grundsätzlich...
... stelle ich mir die Frage, ob ich auf dem richtigen Weg bin. Sicher lässt sich das komplett "zu Fuss" machen. Z.B. werde ich das Formular ja serialisieren müssen.
Kann mir jemand dabei helfen, das sinnvoll zu Ende zu entwickeln?
danke und
gruss, heinetz
Kann mir jemand dabei helfen, das sinnvoll zu Ende zu entwickeln?
Ich würde nicht mit XMLHttpRequest
, sondern mit fetch
arbeiten und statt das Formular manuell zu serialisieren, würde ich FormData
benutzen. Ohne das ganze objektorientierte Gedöns drumherum, würde das etwa so aussehen:
function submit (form) {
const {action, method} = form;
const body = new FormData(form);
return fetch(action, {method, body});
}
Danke
Ich hatte auch schon von fetch gelesen, aber war davon abgekommen, wegen dem fehlenden IE11-Support ... hab aber nun einen Polyfill gefunden und werd's mal probieren.
gruss, heinetz
@@heinetz
Ich hatte auch schon von fetch gelesen, aber war davon abgekommen, wegen dem fehlenden IE11-Support
Gähn. Der Anteil der Nutzer ohne JavaScript dürfte inzwischen höher sein als der Anteil der IE-Nutzer.
Progressive enhancement: Die Anwendung sollte auch ohne JavaScript funktionieren (Formular normal absenden, serverseitig verarbeiten und Ergebnisseite ausliefern), Verbesserung der UX bei Ausführung des JavaScripts.
Wenn das Formular ohne JavaScript funktioniert, hast du auch gleich den Fallback für ältere Browser. Ob es sich lohnt, für IE noch weiteren Aufwand zu betreiben, obwohl die Seite ja funktioniert, musst du wissen.
LLAP 🖖
... ich hab's mal ausprobiert:
submitForm(event) {
event.preventDefault();
const { action, method } = this.elements.commentForm;
const body = new FormData(this.elements.commentForm);
console.log(fetch(action, { method, body }));
//return fetch(action, {method, body});
}
Ich versuche, den Code zu interpretieren:
Die Console gibt zwei Zeilen aus:
Promise {<pending>}
POST http://localhost:8000/html/templates/[object%20HTMLInputElement] 404 (Not Found)
Ich glaube, ich habe den (ersten) Fehler gefunden:
Der POST-Request wird ja an eine etwas 'eigenartige' URL gesendet. Nachdem in der Konstante 'method' erwartungsgemäss der Wert aus dem Attribut 'method' stand, wunderte ich mich, warum das nicht analog mit dem Attribut 'action' funktionierte ... bis ich herausgefunden habe, dass das Formular ein Input-Feld vom type="hidden" mit dem Namen 'action' enthält.
gruss, heinetz
hi,
POST http://localhost:8000/html/templates/[object%20HTMLInputElement] 404 (Not Found)
404 muss ja nicht sein: Sende den POST an die Seite selbst. Hierzu lass das action-Attribut ganz einfach weg.
bis ich herausgefunden habe, dass das Formular ein Input-Feld vom type="hidden" mit dem Namen 'action' enthält.
Da liegt höchstens eine gedankliche Kollision vor. Technisch sind die Namen der inputfelder nicht relevant.
MfG
Der POST-Request wird ja an eine etwas 'eigenartige' URL gesendet. Nachdem in der Konstante 'method' erwartungsgemäss der Wert aus dem Attribut 'method' stand, wunderte ich mich, warum das nicht analog mit dem Attribut 'action' funktionierte ... bis ich herausgefunden habe, dass das Formular ein Input-Feld vom type="hidden" mit dem Namen 'action' enthält.
WTF‽ Dachte erst das wäre ein Browser-Bug, denn die Spec ist bei action
unmissverständlich was drin zu stehen hat. Anscheinend ignorieren Browser hier aber die Spec, aus Gründen der Abwärtskompatibilität.
Die sauberste Lösung, die ich auf die Schnelle finden konnte, benutzt eine Kopie des Formulars ohne die Kind-Elemente:
const {action, method} = form.cloneNode(false);
Nicht hübsch, aber sauber ;)
danke für den Tipp und
gruss, heinetz
Hello,
const {action, method} = form;
Die Notation war mir nicht bekannt. Auf diese Weise stehen mir also hinterher zwei Konstanten zur Verfügung, deren Werte den Attribut-Werte von form entsprechen.
Das lässt sich doch sicher auch so formulieren, dass die Werte hinterher in einem Object:
options = {
action : 'value',
method : 'value'
}
... stehen, oder?
gruss, heinetz
const {action, method} = form;
Die Notation war mir nicht bekannt. Auf diese Weise stehen mir also hinterher zwei Konstanten zur Verfügung, deren Werte den Attribut-Werte von form entsprechen.
Richtig, das ist äquivalent zu:
const action = form.action;
const method = form.method;
Das lässt sich doch sicher auch so formulieren, dass die Werte hinterher in einem Object:
options = { action : 'value', method : 'value' }
... stehen, oder?
Ja, zum Beispiel mit
const options = {action, method};
oder
const options = {
action: form.action,
method: form.method
}
Ja, zum Beispiel mit
const options = { action: form.action, method: form.method }
Das ist klar.
const options = {action, method};
Damit stehen in options aber nicht die Argumente aus dem form.
gruss, heinetz
const options = {action, method};
Damit stehen in options aber nicht die Argumente aus dem form.
Das war missverständlich von mir, die Zeile sollte nicht für sich allein stehen, sondern nach der Konstanten-Deklaration für action
und method
.
Meine Frage hat nur indirekt mit der fetch-API zu tun. Aber ich bleib dennoch im Thread. Ich habe das nun wie folgt umgesetzt und das funktioniert soweit auch. Aber unabhängig davon, dass ich das nicht sehr elegant finde, krieg ich Mecker von eslint wegen der unnamed function in der Methode handleResponse. Kann mir jemand verraten, wie ich mich "besser ausdrücken" kann?
import BaseModule from '../../../../javascripts/helpers/baseModule';
export default class Commentsform extends BaseModule {
constructor(element) {
super(element, element.getAttribute('data-js-module'));
this.getElements([
{ id: 'messageSubmit', selector: 'messageSubmit' }
]);
this.bindEvents();
}
bindEvents() {
this.elements.messageSubmit.addEventListener('click', this.submitForm.bind(this));
}
submitForm(event) {
event.preventDefault();
const { action, method } = this.elements.commentForm.cloneNode(false);
const body = new FormData(this.elements.commentForm);
const headers = {
'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8'
};
fetch(action, { method, headers, body }).then(this.handleResponse.bind(this));
}
handleResponse(response) {
if (response.status === 200) {
response.json().then(
function(json) {
this.response = json;
this.showMessage();
}.bind(this)
);
} else {
this.response = {
state: 'error',
message: 'networkerror'
};
this.showMessage();
}
}
showMessage() {
console.log('showMessage()');
console.log(this.response);
}
}
danke und
gruss, heinetz
Meine Frage hat nur indirekt mit der fetch-API zu tun. Aber ich bleib dennoch im Thread. Ich habe das nun wie folgt umgesetzt und das funktioniert soweit auch. Aber unabhängig davon, dass ich das nicht sehr elegant finde, krieg ich Mecker von eslint wegen der unnamed function in der Methode handleResponse. Kann mir jemand verraten, wie ich mich "besser ausdrücken" kann?
Die quick-and-dirty-Lösung wären Fat-Arrow-Functions zu verwenden wo this
nicht dynamisch sein soll. Statt function(){}.bind(this)
also () => {}
zu benutzen.
Also hieraus:
response.json().then(
function(json) {
this.response = json;
this.showMessage();
}.bind(this)
);
könntest du das hier machen:
response.json().then(json => {
this.response = json;
this.showMessage();
});
Schöner wäre es, den Kontrollfluss mit Promises zu entwirren und allen Handler-Funktionen aussagekräftige Namen zu geben. Folgenden Code hab ich mangels Zeit nicht getestet, aber er sollte die Funktionsweise verdeutlichen, Fehler kann ich gerade nicht ganz ausschließen:
function submitAndHandleResponse(form) {
return submit(form)
.then(handleHttpResponse)
.then(handleHttpSuccess)
.then(handleJsonSuccess);
}
function submit(form) {
const { action, method } = form.cloneNode(false);
const body = new FormData(form);
return fetch(action, { method, body });
}
function handleHttpResponse(response) {
return response.ok
? Promise.resolve(response);
: Promise.reject(response);
}
function handleHttpSuccess(response) {
return response.json();
}
function handleJsonSuccess(json) {
console.dir(json);
return Promise.resolve(json);
}
In der Praxis wird das noch etwas komplexer, weil du zu jedem Promise auch noch einen Error-Handler haben möchtest.
Vielen Dank! Ich musst mir das einige Male durchlesen, die Funktionsweise wird deutlich aber es erfordert doch einiges umdenken. Ich hab die Vorlage mal zu Ende und eingebaut:
submitAndHandleResponse(event) {
event.preventDefault();
return this.submitForm()
.then(this.handleHttpResponse)
.then(this.handleHttpSuccess, this.handleHttpError)
.then(this.handleJsonSuccess)
.then(this.showMessage.bind(this));
}
submitForm() {
const { action, method } = this.elements.commentForm.cloneNode(false);
const body = new FormData(this.elements.commentForm);
const headers = {
'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8'
};
return fetch(action, { method, headers, body });
}
handleHttpResponse(response) {
return response.ok ? Promise.resolve(response) : Promise.reject(response);
}
handleHttpSuccess(response) {
return response.json();
}
handleHttpError(response) {
return {
state: 'error'
};
}
handleJsonSuccess(json) {
return Promise.resolve(json);
}
showMessage(json) {
if (json.message) {
this.elements.messageBox.innerHTML = json.message;
}
this.elements.self.dataset.state = json.state;
}
}
So tut's genau das selbe, wie die Quick&Dirty-Lösung. Dennoch erschliesste sich mir der Mehrwert nicht.
Gruss, heinetz
- Das Formular wird zur Zeit mit der HTML5-Funktionalität des Browsers (required-Attribut etc.) validiert. Sobald ich das Verhalten des buttons per preventDefault abschalte, funktioniert die Validierung nicht. Lässt sich das vernünftig lösen?
Ja, mit form.reportValidity()
hi,
ich habe in der Vergangenheit immer mal wieder Formulare per Ajax versendet, die Response ausgewertet und per JS darauf reagiert ... aber mein Development-Stack war ein LAMP-System und ich habe dazu jQuery und u.U. jQ-Plugins verwendet. Nun heisst meine Entwicklungsumgebung NODE.js und ich will kein jQuery verwenden, sondern ES6 und u.U. NODE-Module.
Dann schau doch mal, ob da ein Serializer dabei ist.
Ich möchte das Formular per POST versenden.
ajax.open('POST',url);
und
ajax.send(serializedData);
Wobei Du das Letztere auch selbst zusammenstellen kannst, das sind ja nur name=value Paare der Eingabefelder. Wobei beim Aneinanderhängen ein encodeURIComponent(value)
erfolgen muss.
MfG