Submit an HTML form by javascript and prevent default - javascript

i am using a virtual keyboard built with javascript for a user interface running on an embedded system.
Source code for the virtual keyboard can be found here : https://codepen.io/dcode-software/pen/KYYKxP
I only changed a single part in the source code. Keyboard.js file line 141-151:
case "enter":
keyElement.classList.add("keyboard__key--wide");
keyElement.innerHTML = createIconHTML("Enter");
keyElement.addEventListener("click", () => {
this.properties.value += "\n";
this._triggerEvent("oninput");
/**
* The part that i modified
*/
document.querySelector("form").submit();
});
break;
Basically, what I'm trying to do here is to submit the form on the current page when the "enter" key is pressed on the virtual keyboard. (Which works fine)
But i also want to prevent default on this submit event. (e.preventDefault()) So how can i achieve that? Thanks for your help.
Edit : I already added e.preventDefault() to the submit function. Here is the code which i handle the login form submit:
//Login
const loginForm = document.querySelector("#login-form");
loginForm.addEventListener("submit", async (e) => {
e.preventDefault();
const errorContainer = document.querySelector("#login-form-message");
errorContainer.innerHTML = "";
try {
//Submitting form
} catch (error) {
//Handle error
}
});

I found it after reading the MDN documentation.
This is the HTMLFormElement.submit() documentation: https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/submit
This document explains quite well why I have this problem.
To summarize, I need to use the requestSubmit () function instead of the submit () function since the submit() function does not trigger a submit event.
HTMLFormElement.requestSubmit() documentation: https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/requestSubmit

Related

Tracking form submission

I'm developing tracking script which tracks some events (page view, link click, custom, ...) and sends them to API. Problem is I'm little bit stuck with form submission. So far I have following...
At first I add event listeners to every form on page:
const forms = document.getElementsByTagName("form");
for (let i = 0; i < forms.length; i++) {
forms[i].addEventListener("submit", trackFormSubmit);
}
}
And then trackFormSubmit function:
const trackFormSubmit = function(evt) {
if (evt.preventDefault) {
evt.preventDefault();
}
const form = evt.target || evt.srcElement;
const formElements = form.elements;
// parse form data payload here, not important to show
const event = {
...
};
Helper.sendEvent(event)
.then(() => {
form.submit();
})
.catch(error => {
console.log(error);
form.submit();
});
};
Helper.sendEvent function sends the event to the API. This solution works well for non-SPA websites, however it's not working great for SPA. I've tested it in React where I had a form using Redux Form library - the form submission was successfully tracked but I've received console error:
Form submission canceled because the form is not connected
The evt.preventDefault() doesn't work as expected in this case. Have someone an idea how this can be implemented?
Well I have to clarify the above statement: evt.preventDefault is called by Redux Form library so you have to handle onSubmit by your function (send data somewhere + redirect). I guess it's same in other Single-Page-Apps, there's no action where the user is sent ... default form behaviour is prevented initially.
First, make sure your ES6 arrow function trackFormSubmit is defined before your for loop that sets the event listener to every form. This could cause the code to work but without listening to the form submit.
Here is an example of setting up the event listener for every form in the document and handling their submit.
const trackFormSubmit = function(event) {
event.preventDefault();
const form = event.target || event.srcElement;
const formElements = form.elements;
alert(`Form ${event.target.name} was submited.`);
};
const forms = document.querySelectorAll("form");
forms.forEach(f=>{
f.addEventListener("submit",trackFormSubmit);
});
Here is a fiddle so you can view how this code works:
https://jsfiddle.net/k3llydev/fgL5owdj/
Also, I changed the way of setting the event listeners to a forEach way. Which is a lot more readable.
Hope this gives you at least an idea of how it would work.
Checking value of event.isDefaultPrevented solved the issue :-)

check if user has already installed PWA to homescreen on Chrome?

I'm trying to create an "Add To Home Screen" button on my progressive web app, as described in Chrome's documentation.
I'm generally following the prescribed pattern, where I have some hidden button which is displayed when Chrome's beforeinstallprompt event fires.
I capture the event once it fires, and then use the event to begin the native install dialogue once my own install button is clicked. The sample code is below:
let deferredPrompt;
window.addEventListener('beforeinstallprompt', (e) => {
// Prevent Chrome 67 and earlier from automatically showing the prompt
e.preventDefault();
// Stash the event so it can be triggered later.
deferredPrompt = e;
// Update UI notify the user they can add to home screen
btnAdd.style.display = 'block';
});
btnAdd.addEventListener('click', (e) => {
// hide our user interface that shows our A2HS button
btnAdd.style.display = 'none';
// Show the prompt
deferredPrompt.prompt();
// Wait for the user to respond to the prompt
deferredPrompt.userChoice
.then((choiceResult) => {
if (choiceResult.outcome === 'accepted') {
console.log('User accepted the A2HS prompt');
} else {
console.log('User dismissed the A2HS prompt');
}
deferredPrompt = null;
});
});
The issue I'm running into is that I don't want to show my install button (btnAdd) if the user has already installed the web app to thier home screen, and I'm having trouble figuring out how to check for that scenario.
I was hoping to modify the above code as follows:
window.addEventListener('beforeinstallprompt', (e) => {
// Prevent Chrome 67 and earlier from automatically showing the prompt
e.preventDefault();
// Stash the event so it can be triggered later.
deferredPrompt = e;
// If the user has not already installed...
deferredPrompt.userChoice
.then(choiceResult => {
if (choiceResult === undefined) {
// Update UI notify the user they can add to home screen
btnAdd.style.display = 'block';
}
});
});
So that the install button won't be displayed if the user has already installed. But this doesn't seem to work. It appears that if they haven't made a choice already, accessing userChoice just prompts the user directly with the native dialogue.
I'm not really sure how the beforeinstallevent works, so this might not even be a good strategy. Ideally I was hoping this would work something like something like navigator.serviceWorker.ready(), which returns a Promise rather than using browser events to try and figure out when stuff is ready.
In any case, are there any ideas on how I can check that the user has installed to home screen before I show my own home screen install button?
Edit: As Mathias has commented, checking for the event before showing the button should be sufficient. I believe the issue I was having is a result of using localhost, which appears to continually fire the beforeinstallprompt event even after installation, which is not the intended behavior. Hosting the code solved the issue.
Perhaps, don't show the button until you intercept the automatic pop-up?
or
In your code, check to see if the window is standalone
If it is, you need not show the button
if (window.matchMedia('(display-mode: standalone)').matches) {
// do things here
// set a variable to be used when calling something
// e.g. call Google Analytics to track standalone use
}
My example tester here
https://a2hs.glitch.me
Source code for my tester
https://github.com/ng-chicago/AddToHomeScreen
To answer original question. With latest versions of Chrome you can use window.navigator.getInstalledRelatedApps(). It returns a promise with an array of installed apps that your web app specifies as related in the manifest.json. To enable this to work you need to add related_applications field to manifest.json
"related_applications": [{
"platform": "webapp",
"url": "https://app.example.com/manifest.json"
}]
And then you can use it like:
//check if browser version supports the api
if ('getInstalledRelatedApps' in window.navigator) {
const relatedApps = await navigator.getInstalledRelatedApps();
relatedApps.forEach((app) => {
//if your PWA exists in the array it is installed
console.log(app.platform, app.url);
});
}
Source: API docs
Now you can display some elements depending if your app is installed. E.g: you can display "Open app" button and redirect user to PWA. But remember to disable it when the user is already in the app using #Mathias's answer and checking (display-mode: standalone)
However, regarding your use case. You should display install button only when beforeinstallprompt is intercepted. Browser does not fire this event if the PWA is already installed on the device. And when prompt is fired and choiceResult.outcome === 'accepted' you hide the button again.
I don't see how this is the correct answer, because this is basically a check if user uses the App already, but the behavior we wan't is "When the user is on the web and tries to install the app again to tell him that he already has the app in his device". Upon me this is not an answer that solves this.
What we can do is:
1. When the user clicks install but has the application on his device
In this case the beforeinstallprompt event WON'T BE fired so this event will return null. We store the result in global variable and when the result is null we show this to user that he already has the app installed.
2. When the user clicks install but doesn't have the application on his device
In this case the beforeinstallprompt event WILL be fired so this event will return access to show the prompt.
We can store the result in global variable and if it is not NULL (which won't be) because beforeinstallprompt will be fired if the user don't have the app on his device we show the prompt() to the user.
I doubt if mine solution is good too but I think that the Question and the correct answer don't have nothing in common
window.addEventListener("beforeinstallprompt", event => {
window.deferedPrompt = event;
});
handleButtonClick = () => {
const promptEvent = window.deferedPrompt;
if(!promptEvent){
// DO SOMETHING
}
//Show the add to home screen prompt
promptEvent.prompt()
promptEvent.userChoice.then((result: any) => {
// Reset the deferred prompt variable, since
// prompt() can only be called once.
window.deferedPrompt = null;.
});
}
<button onClick={handleButtonClick}>Install</button>
Here is the simple function that tells you, this app is open in browser or in pwa.
original source link
function getPWADisplayMode() {
const isStandalone = window.matchMedia('(display-mode: standalone)').matches;
if (document.referrer.startsWith('android-app://')) {
return 'twa';
} else if (navigator.standalone || isStandalone) {
return 'standalone';
}
return 'browser';
}
HTML
<!-- start with hidden button -->
<button id="install" style="display:none;">install</button>
JAVASCRIPT
// variable store event
window.deferredPrompt = {};
// get button with id
const install_button = document.querySelector('#install');
// if the app can be installed emit beforeinstallprompt
window.addEventListener('beforeinstallprompt', e => {
// this event does not fire if the application is already installed
// then your button still hidden ;)
// show button with display:block;
install_button.style.display = 'block';
// prevent default event
e.preventDefault();
// store install avaliable event
window.deferredPrompt = e;
// wait for click install button by user
install_button.addEventListener('click', e => {
window.deferredPrompt.prompt();
window.deferredPrompt.userChoice.then(choiceResult => {
if (choiceResult.outcome === 'accepted') {
// user accept the prompt
// lets hidden button
install_button.style.display = 'none';
} else {
console.log('User dismissed the prompt');
}
window.deferredPrompt = null;
});
});
});
// if are standalone android OR safari
if (window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone === true) {
// hidden the button
install_button.style.display = 'none';
}
// do action when finished install
window.addEventListener('appinstalled', e => {
console.log("success app install!");
});
Another way I've found to do this is to use IndexedDB. I import "idb-keyval" (https://www.npmjs.com/package/idb-keyval) to make it simple to get/set to the IndexedDb. Then I store a value that gets checked on the next page load. One difference with this method, is that it will let you check if your application is installed already if you visit the application webpage from the browser instead of the installed app shortcut.
import * as IDB from "idb-keyval";
let deferredPropt;
// Get the stored value from the IndexedDb
IDB.get("isInstalled").then((val) => {
if (val) {
// If it exists, run code based on an installed pwa
} else {
log({ isInstalled: false });
}
});
window.addEventListener("beforeinstallprompt", (e) => {
e.preventDefault();
deferredPrompt = e;
document.getElementById("installApp").addEventListener("click", async () => {
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
if (outcome == "accepted") {
// Set the value into the IndexedDb on installation of the PWA
IDB.set("isInstalled", true);
}
});
});

JQuery validation resetForm is not working

I have a very simple issue: There is a form with a clear form button. When I click on it, all fields reset. But I also have an extra validation rule: at least one additional field should be filled. After clearing, they all are empty, of course, but I don't want to see these validation messages. I want it to clear the entire form, hide all validation messages and so on. Here is my code:
$("a[data-clear]").click(function (event) {
var now = new Date();
$("#report_search section:gt(0) input").val("");
$("#includeDerived").prop("checked", true);
$("#includeApp").prop("checked", true);
$("#includeOrg").prop("checked", false);
$("input[name='FromDate']").datepicker().datepicker("setDate", now.dateAdd("year", -1));
$("input[name='ToDate']").datepicker().datepicker("setDate", now);
$("form").validate().resetForm();
event.preventDefault();
});
I have only one form on the page so multiple forms is not an issue.
Desired result: form is cleared, validation messages are not shown.
Actual result: form is cleared, validation messages persist.
Sample rule which is checking if fields are filled:
jQuery.validator.addMethod("isSufficientInfo",
function (value, element, params) {
var hasPersonInfo = $("input[name='LastName']").val() && $("input[name='FirstName']").val();
if (hasPersonInfo) {
return true;
}
var hasDocInfo = $("select[name='D']").val() && $("input[name='C']").val() && $("input[name='E']").val();
if (hasDocInfo) {
return true;
}
return $("input[name='A']").val() || $("input[name='B']").val();
}, "File some fields");
$("#IsEnoughInfo").rules("add", { isSufficientInfo: "" });
If you're still looking for the answer,
I suspect that $("form").validate() creates a new validator instance.
What you need is the previously created instance:
$("form").data("validator").resetForm()
Or store the validator in a variable:
var v = $("form").validate()
v.resetForm()
Reason for error
Your event for click event is getting propagated from button to window (inside-out). This is causing your form to trigger validation and thus you are getting the same error message, even after you call the resetForm. If you debug the validation library step by step and get to this.hideErrors then you will see that when this.hideErrors gets executed, the error messages are gone. Since the validation script hasn't finished yet, it continues with other statements, and at the end the event that got propagated is handled by the window from inside-out. The error messages are again shown as this event triggers the request on the form.
Solution
The solution is to move your call to event.preventDefault() to the top, like as shown below:
$("a[data-clear]").click(function (event) {
event.preventDefault(); // Move to top
var now = new Date();
$("#report_search section:gt(0) input").val("");
$("#includeDerived").prop("checked", true);
$("#includeApp").prop("checked", true);
$("#includeOrg").prop("checked", false);
$("input[name='FromDate']").datepicker().datepicker("setDate", now.dateAdd("year", -1));
$("input[name='ToDate']").datepicker().datepicker("setDate", now);
$("form").validate().resetForm(); // this should work now
});
Also see the updated jsfiddle sample
Give it a try and let me know if this works for you or not. I did the step-by-step debug and got to this conclusion.

setTimeout(x, 0) not delaying in Chrome

I have a web page with a form on it; I want to warn the user if they move away. I haven't implemented dirty checking so my onbeforeunload code is simply
window.onbeforeunload = function () {
return 'message'
};
I don't want this warning when they submit the form, so I remove the event on submit. However, the submit may fail, so I need to put the event back afterwards (so if they then navigate away they still get warned). To that end I have done the following:
var tempDisableBeforeUnload = function() {
var obu = window.onbeforeunload;
if (obu) {
window.onbeforeunload = null;
setTimeout(function() {
window.onbeforeunload = obu;
}, 0);
}
}
$("form.noWarnOnSubmit").submit(tempDisableBeforeUnload);
This works fine in Firefox and IE - the setTimeout(x, 0) defers the restoration of the event handler until after the submit has decided whether it is successful or not.
However, in Chrome, the timeout appears to occur immediately, and I get the warning when I submit my form.
How can I make this work on Chrome, or how else can I achieve the end of "don't warn on submit, but put the warning back if the submit fails"?
Cant you do the following:
var unloadActive = true;
window.onbeforeunload = function () {
if(unloadActive) return 'message'
};
$("form.noWarnOnSubmit").submit(function(event){
unloadActive = false;
// do all your checks and things, whatever, and if the checks fail:
unloadActive = true;
});
Then you don't need to do all that function juggling which might cause issues. I haven't tested this, but this is how I would do it.
I have overcome my immediate issue by
Binding my event to fire after the jQuery validation event, by deferring my submit event binding:
$(function() {
$("form.noWarnOnSubmit").submit(disableBeforeUnloadIfNotCancelled);
});
Changing my event handler to only unbind the onbeforeunload event if the submit event is cancelled.
var disableBeforeUnloadIfNotCancelled = function (e) {
if (!e.isDefaultPrevented()) {
window.onbeforeunload = null;
}
}

How do I catch a form submit in Javascript onload?

I have a 3rd party system, where I can add articles (with (raw) html). One page contains a search form, which does a post to itself. Upon load (after post) it will add a javascript submit form using javascript - e.g.
function redir() {
document.myform.submit();
}
.... //some other stuff here
redir();
I'll not go into details on the design (I know why it is designed this way but cannot change it - its crappy design, I know!).
Is there someway to catch this "onload" submit?
I have tried this:
var sform = document.getElementsByName('myform');
try {
sform.addEventListener("submit", alert('Gotcha!'));
} catch(e) {
sform.attachEvent("submit", alert('Gotcha!')); //Internet Explorer
}
Also jQuery:
$('myform').submit(function() {
alert('Gotcha!');
return false;
});
I cannot seem to catch the event. I can override the method:
redir = function(){
//Empty - override
}
...but the submit action is trigged before my script is loaded or it will override my overridding method.
I hope this makes sense.
Your jQuery was close. You just need to use a # before your id based selector, just like in CSS.
$('#myform').submit(function() {
alert('Gotcha!');
return false;
});
Did you override the function in the correct place? The following works for me and stops the submit.
function redir() {
document.myform.submit();
}
redir = function(){}
redir();
Demo

Categories

Resources