Asynchronous custom validation issue - html

I have created an asynchronous custom validation for my angular form. The validator checks an array of emails to see if the email that the user has entered is already registered or not (like you would get on a generic log in screen). I have done this asynchronously to simply mimic the retrieval of data from a server.
The issue I am having is that when an unregistered email address is entered by the user, although the error message is correctly displayed on the UI by checking whether the error message is present in my form object, an error message is also logged to the console: "TypeError: Cannot read property 'emailIsNotRegistered' of null".
It's almost as if the promise is returning the correct key value pair {'emailIsNotRegistered': true}, but is then also returning 'null'.
Here is the custom validator:
unregisteredEmail(control: FormControl): Promise<any> | Observable<any> {
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
if(this.registeredEmails.indexOf(control.value) === -1) {
resolve({'emailIsNotRegistered': true})
} else {
resolve(null);
}
}, 1500)
});
return promise;
}
Here is where the validator is added to the email form control:
ngOnInit() {
this.logInForm = new FormGroup({
'email': new FormControl(null, [Validators.required, Validators.email], this.unregisteredEmail.bind(this)),
'password': new FormControl(null, [Validators.required])
})
}
The email input tag from html:
<input class="input" type="text" id="email" name="email" formControlName="email">
This html element that is added if the user enters an email address which isn't already registered:
<span *ngIf="logInForm.get('email').errors['emailIsNotRegistered'] && logInForm.get('email').touched">No account found with this email address. Register below!</span>
An finally, the registeredEmails array:
registeredEmails = ['52pbailey#gmail.com', 'sammie-midnight#hotmail.co.uk'];

Related

Chrome extension messaging error: Could not establish connection. Receiving end does not exist

I'm building an extension where when the extension first starts (browser is started/extension is updated) a window is opened with a html file containing a form asking for a master password. When this master password does not match a certain string, a message is sent through chrome.runtime.sendMessage. A message is also sent the same way when the modal window is closed through the chrome.windows.onRemoved listener.
Here is my service worker:
/// <reference types="chrome-types"/>
(async () => {
console.log('Extension started. Modal opened...');
const window = await chrome.windows.create({
url: chrome.runtime.getURL("html/index.html"),
type: "popup",
width: 400,
height: 600,
});
chrome.windows.onRemoved.addListener((windowId) => {
if (windowId === window?.id) chrome.runtime.sendMessage({ monitoringEnabled: true, reason: 'tab closed' }).catch(console.log);
});
chrome.runtime.onMessage.addListener((message) => {
if (Object.hasOwn(message, 'monitoringEnabled')) {
console.log(`Monitoring ${message.monitoringEnabled ? 'enabled' : 'disabled'}. ${message.reason ? `Reason: ${message.reason}` : ''}`)
chrome.storage.local.set({ monitoringEnabled: message.monitoringEnabled });
if (window?.id) chrome.windows.remove(window.id);
}
return true;
});
})();
The html file just has a form with a button which when clicked triggers a script:
const MASTER_PASSWORD = 'some_thing_here';
document.getElementById('submit-button').addEventListener("click", (e) => {
const password = document.getElementById('master-password-text-field').value;
if (password !== MASTER_PASSWORD) return chrome.runtime.sendMessage({ monitoringEnabled: true, reason: 'invalid password' })
return chrome.runtime.sendMessage({ monitoringEnabled: false })
})
These are some logs:
The first error is when the modal tab is closed, notice that nothing happens after this (i.e onMessage listener is not triggered). However, in the second case, when a message is sent from the modal script, the onMessage listener is triggered, but the connection error still appears after the code in the listener has processed.
Not sure why this happens. I have checked multiple other threads on the same topic but none of them seem to help me. If you have a better idea on what I can do to achieve what I want right now, please suggest.
In my code, I'm sending a message to the service worker in the server worker itself. I've re-wrote my code by just making a function which is called when the windows.onRemoved event is triggered and also when a message is sent from the modal tab. The seems to have fixed my issue. This is my service worker code for reference:
/// <reference types="chrome-types"/>
console.log('Extension started. Modal opened...');
let windowId: number | null = null;
chrome.windows
.create({
url: chrome.runtime.getURL('html/index.html'),
type: 'popup',
width: 400,
height: 600
})
.then((created) => (windowId = created?.id ?? null));
chrome.windows.onRemoved.addListener((id) => {
if (id === windowId) enableMonitoring('window closed')
});
chrome.runtime.onMessage.addListener((message) => {
if (message.monitoringEnabled) {
enableMonitoring(message.reason);
}
return undefined
})
function enableMonitoring(reason: any) {
console.log('monitoring enabled', reason);
}

Edit form that is pre-filled with API data - React

I am building a form that is pre-populated by the results of an axios get request to a nodejs API that returns an array (stored in MySQL). I asked this question yesterday, implemented a solution that was provided and it worked. The next day I tested it thoroughly and it turns out that on submission only the edited fields were passed but not the values from the unedited fields.
I can get the data to map onto the form, but i cannot edit the form. The idea is for it to be an "edit user" form. I suspect the issue is in the onChange portion of the input field.
The form is accessed from a table that is also mapped with the results of a get request. Upon clicking the edit button, the userID from the table row is passed to the edit form through useNavigate and useLocation (I can add this portion of code if needed).
Backend
Here is the API controller that queries the MySQL database and then sends to the frontend:
export const edit = async (req, res) => {
db.query(
"SELECT * FROM userIndex WHERE userID = ?",
[req.params.userID],
(err, rows) => {
if (!err) {
res.send(rows);
} else {
console.log(err).res.send({ alert: err });
}
}
);
};
Here's an example result of the query above:
[
{
"userID": 143,
"firstName": "Kenan",
"lastName": "Erasmus",
"role": "student",
"email": "kenan#gmail.com",
"password": "$2b$12$v3s0D6cNkGwM3/IWXPdv..TRfRZLmDNuZBfrWlUCt4vKnyRi75jWe",
"createdAt": "06/10/2022, 13:56:51",
"updatedAt": "07/10/2022, 19:45:46",
"lastLogin": null
}
]
Frontend
Here is the portion of code that performs the request to the API:
useEffect(() => {
const config = {
headers: { "x-auth-token": token },
};
const fetchData = async () => {
const results = await api.get("/users/edituser/" + userID, config);
setRows(results.data);
setFirstName(rows.firstName)
};
fetchData();
}, [setRows, userID, token]);
State for "rows" (set on API response):
const [rows, setRows] = useState([]);
const [firstName, setFirstName] = useState("");
And finally, an example input field:
<input
type="text"
className="form-control"
id="inputEmail4"
placeholder="First Name"
name="firstName"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
></input>
I have only included the "firstName" input as an example. In total, this form has about 6 fields.
I would greatly appreciate any assistance with this. Let me know if I can supply any more code.
Found a workaround that seems quite logical to me. I initialised new blank states for each of the input fields
const [firstName, setFirstName] = useState("");
Then mapped the form with "rows" and set each field value to its correspond state (as seen in the useEffect below)
Frontend useEffect:
useEffect(() => {
const config = {
headers: { "x-auth-token": token },
};
const fetchData = async () => {
const results = await api.get("/users/edituser/" + userID, config);
setRows(results.data);
setFirstName(results.data[0].firstName);
setLastName(results.data[0].lastName);
setEmail(results.data[0].email);
setPassword(results.data[0].password);
setRole(results.data[0].role);
};
fetchData();
}, [setRows, userID, token]);
Example input field:
<input
type="text"
className="form-control"
id="inputEmail4"
placeholder="First Name"
name="firstName"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
></input>
By doing it this way, the form maps through "rows", and then each input field immediately gets a new state once typing occurs.
I know it's ugly, but it is effective. I'm new to coding so I don't know all the ins and outs of React yet - but this solution works for me. Hope this helps anyone looking for a solution to the same issue!

(saved) event is getting triggered twice in HTML

I am working on ASP.Net project. My frontend page has 1 editable field.
When user edits data in the field, saved event trigger method. It is getting triggered twice so data entry is getting added twice in database.
.html -
<cool-inline-edit-field
name="homePhone"
[ngModel]="user.homePhone"
(saved)="saveUserHomePhoneAsync($event)"
required
pattern="[0-9]{10}"
>
</cool-inline-edit-field>
component.ts
saveUserHomePhoneAsyc($event: Event) {
this.showSpinner();
debugger;
const oldVal = this.user.homePhone;
const newVal = $event as unknown as keyof any;
debugger;
this.accountService.setBorrowerPreferencesChangeData(this.borrowerIdentityData.fullName, "HomePhone",
oldVal, newVal.toString()).subscribe(
(data) => {
this.hideSpinner();
this.user.homePhone = newVal.toString();
this.snackbarService.show("Value saved");
},
(error) => {
this.user.homePhone = oldVal;
this.hideSpinner();
this.showError('Failed to save home phone. Please try again');
console.log(error);
}
);
}
I am not getting why it is getting called twice? .ts method should run only once.

Socket.io emitting undefined variable React

I've currently got a very simple app that sends and receives data to a server via a websocket connection. Essentially, the app contains two text boxes and each time a user clicks on either of the textboxes to focus or click away, a notification is sent to the server and then the latter broadcasts that message to all connected instances.
This is my code below:
App.js (client)
import React, { useState, useEffect } from 'react'
import io from 'socket.io-client'
import TextField from '#material-ui/core/TextField'
import './App.css'
import 'carbon-components/css/carbon-components.min.css';
import { TextArea } from 'carbon-components-react';
const socket = io.connect('http://localhost:4000')
function App() {
const [myUser, setMyUser] = useState(null)
const handleButtonClick = (e) => {
e.preventDefault();
setMyUser(e.target.value)
}
useEffect(() => {
socket.on("message", ({ user, id, focus }) => {
console.log(user, "clicked on", id, "with focus", focus)
})
}, [])
function setFocusTrue(id) {
const focus = true
console.log("emitting: ", myUser, id, focus)
socket.emit('message', { myUser, id, focus })
}
function setFocusFalse(id) {
const focus = false
console.log("emitting: ", myUser, id, focus)
socket.emit('message', { myUser, id, focus })
}
const Main = () => {
return (
<div>
<h1>Hello {myUser}</h1>
<TextArea
cols={50}
helperText="Optional helper text here; if message is more than one line text should wrap (~100 character count maximum)"
id="text1"
invalidText="Invalid error message."
labelText="Text area label"
placeholder="Placeholder text"
rows={4}
onFocus={() => setFocusTrue('text1')}
onBlur={() => setFocusFalse('text1')}
/>
<TextArea
cols={50}
helperText="Optional helper text here; if message is more than one line text should wrap (~100 character count maximum)"
id="text2"
invalidText="Invalid error message."
labelText="Text area label"
placeholder="Placeholder text"
rows={4}
onFocus={() => setFocusTrue('text2')}
onBlur={() => setFocusFalse('text2')}
/>
</div>
)
}
return (
<div>
{myUser === null ?
[
<div>
<button onClick={handleButtonClick} value="user1">User 1</button>
<button onClick={handleButtonClick} value="user2">User 2</button>
</div>
]
: <Main />
}
</div>
)
}
export default App
index.js (server)
const app = require('express')()
const http = require('http').createServer(app)
const io = require('socket.io')(http)
io.on('connection', socket => {
socket.on('message', ({ user, id, focus }) => {
console.log('received user', user, 'id', id, 'focus', focus)
io.emit('message', { user, id, focus })
})
})
http.listen(4000, function() {
console.log('listening on port 4000')
})
One thing to note is that I'm testing this with two different users (running on two incognito browsers), hence the conditional rendering for each user. For this example, when either user clicks on or away from a textbox, I can see that the textbox id and focus are both valid, but for some reason, the user is undefined. I can see that the instance emits the variables as expected, but they are not being received in the server correctly.
In this particular case, user 1 clicks on the first textbox (thus rendering the focus to true). Each of the variables to emit to the server are set (as can be seen by the console log). The server receives the focus and id variables, but for an odd reason, the user is undefined, even though it exists. Finally, the broadcasted message is sent to both instances but as mentioned, id and focus exist, but user is undefined. See below screenshots.
Client side
Server side console
Instead of emitting the object like socket.emit('message', { myUser, id, focus }), I instead provided the user object as such socket.emit('message', { user: myUser, id, focus }) which seemed to work fine.

How do I use MVC Validation to display a warning message without making the field required?

I have a asp.net MVC website where the user enters a social security number (SSN). The client wants to display a warning message if the SSN is already used, but doesn't want to force them to change the SSN (there are conditions under which multiple records can have the same SSN).
The MVC code I inherited has a validation to check if the SSN has already been used. The code works great. If the user enters a SSN that is already used, a message appears saying "SSN already exists" and prevents the form from being submitted. How can I change this so that the message is displayed, but so it doesn't prevent the form from being submitted?
ModelApplication.cs
[StringLength(9, MinimumLength = 9, ErrorMessage = "Please Enter 9 Digit SSN No")]
[Remote("IsSSNExist", "Admin", HttpMethod = "GET")]
public string ApplicantSSN { get; set; }
AdminController.cs
[HttpGet]
public JsonResult IsSSNExist(string ApplicantSSN)
{
if (Session["viewapp"] == null)
{
if (obj_BllApp.IsSSNExist(ApplicantSSN))
return Json("SSN already exists.", JsonRequestBehavior.AllowGet);
else
return Json(true, JsonRequestBehavior.AllowGet);
}
else
{
return Json(true, JsonRequestBehavior.AllowGet);
}
}
Application.cshtml
<label>
SSN
</label>
#Html.TextBoxFor(m => m.ApplicantSSN, new { #class = "input-small", #maxlength = "9", #onkeypress = "return ValidateNumberKeyPress(this, event);" })<br />
#Html.HiddenFor(m => m.ApplicantSSNID, new { id = "hdnApplicantSSN" })
span id="spAppSSn" class="SSNmsg">#Html.ValidationMessageFor(m => m.ApplicantSSN)</span>
UPDATE
I also tried using the response header like another poster suggested, but I couldn't get it to work.
This code didn't return anything and broke other javascript I had:
$(function () {
$("#txtApplicantSSN").change(function (xhr) {
alert("Hello");
var req = new XMLHttpRequest();
req.open('GET', document.location, false);
req.send(null);
var headers = req.getResponseHeader("SSN-DUPLICATED").toLowerCase();
alert(headers);
alert("Goodbye");
});
});
Using the same concept, I tried another way of getting the request header, but I never got a value. It looked like the validation that set the header value was being called after the javascript.
$(function () {
$("#txtApplicantSSN").change(function () {
var req = new XMLHttpRequest();
req.open('GET', document.location, false);
req.send(null);
var headers = req.getResponseHeader('SSN-DUPLICATED');
$("#remoteMessage").text(headers);
});
});
I tried something similar using session variables, but again the session variable seem to be getting set after the javascript code.
$(function () {
$("#txtApplicantSSN").change(function () {
var someSessionVariable = '#Request.RequestContext.HttpContext.Session["SSNExists"]';
alert(someSessionVariable);
$("#remoteMessage").text(someSessionVariable);
});
});
My current thought is to try to disable the validation when the submit button is clicked, but I haven't found a way to do it. I tried this
HtmlHelper.ClientValidationEnabled = false;
in the controller but it never hits the server side code. I get the validation error before it hits the controller.
Update #2
I disabled the validation when the submit button is clicked using the cancel class:
<input id="Submit1" type="submit" class="btn btn-primary cancel" value="Save" onclick="javascript: return ValidatonCoApplication();" />
This fixes the problem for this field, but disables validation for all other fields. Can someone suggest another way to do what I want without turning off validation?
In summary, this is asp.net MVC with Razor. After the user enters a SSN in a text box, I need a message to appear on the screen saying whether or not the SSN is valid. Currently I have a validation attribute in the model, but this is not only showing the message, it is declaring the model invalid and therefore not letting the user proceed to the next page. I want the validation message to appear, but do not want the model invalid. I'd appreciate any help you could give me. Thank you.
Since you only want to display a message based on the value of ApplicantSSN (not invalidate the model), remove the [Remote] attribute, and instead handle the .change() event of the textbox to call a conroller method and return an appropriate message.
Controller
public JsonResult IsSSNExist(string ApplicantSSN)
{
bool isValid = // your logic
if (isValid)
{
return Json(null, JsonRequestBehavior.AllowGet);
}
else
{
return Json(true, JsonRequestBehavior.AllowGet);
}
}
View
#Html.TextBoxFor(m => m.ApplicantSSN) // remove the onkeypress attribute
// add a placeholder for the message
<span class="field-validation-error"><span id="ssn-message">The SSN already exists.</span></span>
css
#ssn-message {
display: none;
}
Script
var url = '#Url.Action("IsSSNExist")';
var ssnMessage = $('#ssn-message');
$('#ApplicantSSN').change(function() {
$.getJSON(url, { ApplicantSSN: $(this).val() }, function(response) {
if(response) {
ssnMessage.show();
} else {
ssnMessage.hide();
}
});
});
Note: If the user enters an invalid value and tabs out of the control, the message will be displayed. You may want additional logic to hide the message if the user then starts typing in the textbox again in whichcase you would alo need to handle the keyup event
You can put validation in response header and then show it using jQuery as follows
[HttpGet]
public JsonResult IsSSNExist(string ApplicantSSN)
{
if (Session["viewapp"] == null)
{
if (obj_BllApp.IsSSNExist(ApplicantSSN)){
Response.AddHeader("SSN-DUPLICATED", "SSN already exists. ");
return Json(true, JsonRequestBehavior.AllowGet);
}
else
return Json(true, JsonRequestBehavior.AllowGet);
}
else
{
return Json(true, JsonRequestBehavior.AllowGet);
}
}
And add a span to display the remote message, and some jQuery in your view like following
<label>
SSN
</label>
#Html.TextBoxFor(m => m.ApplicantSSN,
new { #class = "input-small", #maxlength = "9", #onkeypress = "return ValidateNumberKeyPress(this, event);" })
<br />
#Html.HiddenFor(m => m.ApplicantSSNID, new { id = "hdnApplicantSSN" })
<span id="spAppSSn" class="SSNmsg">
#Html.ValidationMessageFor(m =>m.ApplicantSSN)</span>
<span id="remoteMessage" class="SSNmsg">
#Html.ValidationMessageFor(m =>m.ApplicantSSN)</span>
$(function () {
$('#ApplicantSSNID').rules()
.remote.complete = function (xhr) {
var responseMessage=xhr.getResponseHeader('SSN-DUPLICATED');
if (xhr.status == 200 && xhr.responseText === 'true') {
jQuery('#remoteMessage')[0].innerHTML =
(responseMessage||'');
}
};
});
Note: please don't use Session in MVC. It's not a good practice.
You cannot really do what you want in the way you want. Validation will pass or fail! What you'll have to do is remove the [Remote] validation attribute and do the check in the action you are submitting the form to. If the SSN exists then you'll have to pass this message out once you have completed the processing of the form.
Or use javascript and Ajax to check the SSN when the SSN textbox loses focus, then you can display the warning before the user submits the form