How to fetch user input for login in Single Page Application - javascript

I'm a new developer and am currently asked to set up a login / signup page for a Single Page Application in JavaScript. This SPA is in the early stages, since the published version is a multipage application.
Our database already contains the user information. I suppose I would have to work with post and get requests in order to authenticate users?
So far, I have managed to fetch data from the database in json, but I don't know how to handle user input and send it to the database.
A general overview: Our Single Page Application is written in JavaScript. A separate Python file retrieves data from the database, creates specific routes (urls) (which we access in our js files) and stores the data in json.
P.S. the code might be pretty janky, and any suggestions on how to improve or simplify the code are appreciated
rest_server.py:
#app.route(url_prefix + '/json/team/<int:team_id>')
def get_team(team_id):
team = adapter.team_info(team_id)
resp = flask.jsonify(team)
resp.headers['Access-Control-Allow-Origin'] = '*'
return resp
#app.route(url_prefix + '/json/team_ids')
def get_team_ids():
team_ids = adapter.team_ids()
resp = flask.jsonify(team_ids)
resp.headers['Access-Control-Allow-Origin'] = '*'
return resp
main.js (reduced version here, I hope it makes sense):
async function renderPage(path) {
if (!path) {
path = window.location.pathname;
}
console.log(`render page ${path}`);
let renderedPath = await pathHandler.createContent(path);
if (renderedPath && renderedPath != path) {
// if page was rendered under a different name, override history
console.log(`different rendered path ${renderedPath}`);
window.history.replaceState(renderedPath,'', renderedPath);
}
}
/**
* handles back button events on window object
* #param {Event} event event object emitted by window.
*/
function onBackButton(event) {
event.preventDefault();
renderPage();
}
/**
* handles click events on the document interface
* #param {Event} event object on the document interface
*/
function onBodyClick(event) {
var tag = event.target;
// use default behavior if this was not a hyperlink
if (tag.tagName != 'A' || !tag.href || event.button != 0) {
return;
}
// use default behavior if link points to other server
if (tag.origin != window.location.origin) {
return;
}
// use default behavior if path is not supported
if (!pathHandler.isSupported(tag.pathname)) {
return;
}
event.preventDefault();
redirect(tag.pathname);
}
/**
* determines the path prefix, the page is loaded with. at the moment, a path prefix requires an
* indicator string. in future, the path could be transmitted within the HTML page and extracted here
* #returns {String|null} path prefix or null if there is none.
*/
function determinePathPrefix() {
let pathPrefixIndicator = '/sp/';
let path = window.location.pathname;
let index = path.lastIndexOf(pathPrefixIndicator);
if (index >= 0) {
return path.slice(0, index + pathPrefixIndicator.length - 1);
}
return null;
}
function displayTeams() {
contentCreator.createDisplayTeams();
}
const contentElement = document.querySelector('.content');
const headerElement = document.querySelector('.details');
const teamsLi = document.querySelector('#teams');
const baseElements = {
headerElement: headerElement,
contentElement: contentElement
};
const functions = {
displayTeams: displayTeams
}
const pathPrefix = determinePathPrefix();
console.log(`path prefix: ${pathPrefix}`);
const pathHandler = new PathHandler(pathPrefix);
const contentCreator = new ContentCreator(pathHandler, baseElements, functions);
const createDisplayTeams = contentCreator.createDisplayTeams.bind(contentCreator);
const createTeams = contentCreator.createTeams.bind(contentCreator);
pathHandler.addPath('createTeams', '/team_<int:itemId>', createTeams);
pathHandler.addPath('teams', '/teams', createDisplayTeams);
document.body.addEventListener('click', onBodyClick);
teamsLi.addEventListener('click', displayTeams);
window.addEventListener('popstate', onBackButton);
renderPage();
Then there is a PathHandler.js file, which might not be necessary to show here.
Finally, ContentCreator.js, which takes care of the UI (also reduced):
class ContentCreator {
#pathHandler;
#baseElements;
#functions;
/**
* #param {PathHandler} pathHandler path handler to be used for generating links.
* #param {Object} baseElements base HTML elements of which children can be replaced.
* #param {Object} functions functions for reloading or redirecting.
*/
constructor(pathHandler, baseElements, functions) {
this.#pathHandler = pathHandler;
this.#baseElements = baseElements;
this.#functions = functions;
}
// the displayTeams function in main.js calls this function
async createDisplayTeams() {
let paragraphElement = document.createElement('p');
paragraphElement.setAttribute('class', 'teamsList');
const response = await fetch('http://localhost:5000/json/teams');
const teams = await response.json();
for (let i = 0; i < teams.length; i++) {
const id = teams[i].TeamId;
const li = document.createElement('li');
const link = document.createElement('a');
const name = teams[i].Name;
link.innerHTML = name;
li.appendChild(link);
li.setAttribute('class', 'teamLi');
paragraphElement.appendChild(li);
link.setAttribute('href', this.#pathHandler.generatePath('createTeams', {itemId: id}));
}
this.#baseElements.contentElement.replaceChildren(paragraphElement);
}
async createTeams(args) {
const id = args['itemId'];
const infoResponse = await fetch(`http://localhost:5000/json/team/${id}`);
const teamInfo = await infoResponse.json();
const roleInfo = teamInfo['RoleInfoList'];
const members = document.createElement('ul');
const container = document.createElement('div');
container.setAttribute('class', 'teamContainer');
const header = document.createElement('h2');
header.innerHTML = teamInfo['Name'];
container.appendChild(header);
const paragraphElement = document.createElement('p');
if (teamInfo['Email']) {
paragraphElement.innerHTML = teamInfo['Email'];
}
for (let p = 0; p < roleInfo.length; p++) {
const member = document.createElement('li');
member.innerHTML = `${roleInfo[p]['Person Name']}`;
members.appendChild(member);
}
container.appendChild(paragraphElement);
container.appendChild(members);
this.#baseElements.contentElement.replaceChildren(container);
}
}
Now I would like to create a form in ContentCreator.js, which somehow fetches the user input and then makes a database request in order to authenticate the user. The UI code won't be a problem, but how could I fetch and send the data?
Thank you!!
Mina

Related

Javascript - how can I make this recursive?

I have a function to render comments, in which each comment is stored as an object in an array. Comments can have reply comments, in which they have the exact same html and data to render, just their styling is different (via a CSS modifier class).
How can I make this function recursive? The renderReplies(comment.replies) calls a function that is the exact same as renderComments function, just without the mentioned function call renderReplies (as a reply to a reply is the exact same also in styling terms).
const renderComments = (comments) => {
commentsElement.innerHTML = '';
comments.forEach(comment => {
commentsElement.innerHTML += html;
// data-id attribute
const liElements = commentsElement.querySelectorAll('.comment');
const liElement = liElements.item(liElements.length - 1);
liElement.setAttribute('data-id', comment.id);
// author
liElement.querySelector('.comment__author').innerHTML = comment.user.username;
// avatar src & alt attributes
const avatar = liElement.querySelector('.comment__avatar');
avatar.setAttribute('src', comment.user.image.png);
avatar.setAttribute('alt', comment.user.username);
// time since posted
// content
const p = liElement.querySelector('.comment__text');
p.appendChild(document.createTextNode(comment.content));
// score
liElement.querySelector('.comment__score b').innerHTML = comment.score;
// replies
renderReplies(comment.replies);
});
};
Check whether there's a replies property. If it exists, call the function recursively. Since replies won't have replies of their own, you'll stop there.
const renderComments = (comments) => {
commentsElement.innerHTML = '';
comments.forEach(comment => {
commentsElement.innerHTML += html;
// data-id attribute
const liElements = commentsElement.querySelectorAll('.comment');
const liElement = liElements.item(liElements.length - 1);
liElement.setAttribute('data-id', comment.id);
// author
liElement.querySelector('.comment__author').innerHTML = comment.user.username;
// avatar src & alt attributes
const avatar = liElement.querySelector('.comment__avatar');
avatar.setAttribute('src', comment.user.image.png);
avatar.setAttribute('alt', comment.user.username);
// time since posted
// content
const p = liElement.querySelector('.comment__text');
p.appendChild(document.createTextNode(comment.content));
// score
liElement.querySelector('.comment__score b').innerHTML = comment.score;
// replies
if (comment.hasOwnProperty("replies")) {
renderComments(comment.replies);
}
});
};

Routing function not being called

I am working on a simple restaurant web app which uses a mongo-db database to store the menu items. My issue is that I have a client js file that will use a routing function that then accesses the database to return all the menu items of a certain restaurant. My issue is that my endpoint for the url isn't being recognized:
Client.js
function readMenu(rest){
(async () => {
// const newURL = url + "/menus/"+rest
const resp = await fetch(url+"/menus/"+rest)
const j = await resp.json();
itemlist = j["items"]
var element = document.getElementById("menu")
var i;
for (i = 0; i < itemlist.length; i++) {
var para = document.createElement("p")
item = itemList[i]
text = item["name"]+" | "+item["cost"]+" | "+item["descr"] +"<br>";
var node = document.createTextNode(text)
para.appendChild(node)
element.appendChild(para)
}
})
}
Server-routing.ts (Routings):
this.router.get("/menus", this.getResturants.bind(this))
this.router.post("/menus", this.addResturaunt.bind(this))
this.router.get("/menus/:rest", this.getResturauntItems.bind(this))
this.router.delete("/menus/:rest",this.deleteResturaunt.bind(this))
this.router.get("/menus/:rest/:item",[this.errorHandler.bind(this),this.getItem.bind(this)])
this.router.post("/menus/:rest",this.addItem.bind(this))
this.router.delete("/menus/:rest/:item",this.deleteItem.bind(this))
Server-routing.ts (function):
public async getResturauntItems(request, response) : Promise<void> {
console.log("Getting Restaurant Items")
let rest = request.params.rest
let obj = await this.theDatabase.getResturauntItems(rest)
console.log(obj)
response.status(201).send(JSON.stringify(obj))
response.end()
}
So, what should happen is a button calls readMenu(), it then makes a GET fetch request to localhost:8080/api/menus/ and then the menu items from the collection should be returned. The issue is that when I click the button, nothing happens. I know it is not being redirected to some other function as they all have "console.log()" to keep track of them and none of them where called. I used the "inspect" tool to see if the request was being sent or received anywhere and nothing. I am unsure of what the issue happens to be. If anyone can help, it would be really appreciated.
you just never called your function, you declared the async function inside your function but never called it.
function readMenu(rest){
(async () => {
// const newURL = url + "/menus/"+rest
const resp = await fetch(url+"/menus/"+rest)
const j = await resp.json();
itemlist = j["items"]
var element = document.getElementById("menu")
var i;
for (i = 0; i < itemlist.length; i++) {
var para = document.createElement("p")
item = itemList[i]
text = item["name"]+" | "+item["cost"]+" | "+item["descr"] +"<br>";
var node = document.createTextNode(text)
para.appendChild(node)
element.appendChild(para)
}
})();
}
you need to add () after creating the functions to call it.

javascript sets - adding value to array in object inside a set

I'm trying to make an array of sets to make something like this
{
'user1': ["value#1", "value#2",..."value#N"],
'user2': ["value#2",..."value#N"],
'userN': [..."value#N"]
}
and then remove the value#x after 5 seconds (for example).
here is my code:
var myset = new Set();
var ran = myset[USERID] = commandNumber;
//i'm trying to make "if myset contains userNumber AND commandName" return,
//if its not, run someFunction() and continue
if (myset.has(ran)) return;
someFunction();
myset.add(ran);
setTimeout(() => {
myset.delete(ran);
}, 5000);
instead of getting output like the first code, i get this output instead
Set { 'command1', 'command2',
'USER1': 'command3',
'USER2': 'command4'
'USERN': 'commandN'
}
Feel free to comment if you have a question, so sorry if my question is hard to understand
A Set for this purpose is not necessary but I did a small POC that could help you to implement the solution you need:
'use strict';
const mySet = new Set();
const mySetMetadata = {};
const removeFromSet = (userKey, commandName) => {
const commands = mySetMetadata[userKey] || [];
if (commands.includes(commandName)) {
mySetMetadata[userKey] = commands.filter(c => c !== commandName);
if (mySetMetadata[userKey].length === 0) {
mySet.delete(userKey);
mySetMetadata[userKey] = undefined;
}
}
};
/**
* Add relation between an userKey and a command
* #param {String} userKey
* #param {Array} commands Array of commands
*/
const addToSet = (userkey, commands) => {
mySet.add(userkey);
if (typeof mySetMetadata[userkey] === 'undefined') {
mySetMetadata[userkey] = commands;
} else {
mySetMetadata[userKey] = [...mySetMetadata[userKey], ...commands]
}
}
// Populate with demo data
addToSet('user1', ['value#1', 'value#2', 'value#N']);
addToSet('user2', ['value#2', 'value#N']);
addToSet('user3', ['value#N']);
// Set up a timeout for a given user + key
setTimeout(() => {
removeFromSet('user1', 'value#2');
}, 5000);

How can I get the raw download size of a request using Puppeteer?

That is, the total amount of data downloaded across all resources (including video/media), similar to that returned by Chrome DevTools' Network tab.
There doesn't seem to be any way to do this as of January 2018 that works with all resource types (listening for the response event fails for videos), and that correctly counts compressed resources.
The best workaround seems to be to listen for the Network.dataReceived event, and process the event manually:
const resources = {};
page._client.on('Network.dataReceived', (event) => {
const request = page._networkManager._requestIdToRequest.get(
event.requestId
);
if (request && request.url().startsWith('data:')) {
return;
}
const url = request.url();
// encodedDataLength is supposed to be the amount of data received
// over the wire, but it's often 0, so just use dataLength for consistency.
// https://chromedevtools.github.io/devtools-protocol/tot/Network/#event-dataReceived
// const length = event.encodedDataLength > 0 ?
// event.encodedDataLength : event.dataLength;
const length = event.dataLength;
if (url in resources) {
resources[url] += length;
} else {
resources[url] = length;
}
});
// page.goto(...), etc.
// totalCompressedBytes is unavailable; see comment above
const totalUncompressedBytes = Object.values(resources).reduce((a, n) => a + n, 0);
The solution of #mjs works perfectly even in 2021. Just need to replace:
page._networkManager -> page._frameManager._networkManager
Full example that works for me:
const resources = {};
page._client.on('Network.dataReceived', (event) => {
const request = page._frameManager._networkManager._requestIdToRequest.get(
event.requestId
);
if (request && request.url().startsWith('data:')) {
return;
}
const url = request.url();
const length = event.dataLength;
if (url in resources) {
resources[url] += length;
} else {
resources[url] = length;
}
});
await page.goto('https://stackoverflow.com/questions/48263345/how-can-i-get-the-raw-download-size-of-a-request-using-puppeteer');
const totalUncompressedBytes = Object.values(resources).reduce((a, n) => a + n, 0);
console.log(totalUncompressedBytes);
If you are using puppeteer, you have server side node... Why not pipe the request through a stream, or streams and then calculate the content size?
Also there is https://github.com/watson/request-stats
Also you may want to call page.waitForNavigation as you may be wrestling with async timing issues
const imgaes_width = await page.$$eval('img', anchors => [].map.call(anchors, img => img.width));
const imgaes_height = await page.$$eval('img', anchors => [].map.call(anchors, img => img.height));

Javascript function does not exit the function wehn the condtion failed

I have following code which process a queue and I need to exist the function when there are no messages in the queue and there is no enough time to process more messages. My problem is, it doesn't jump out of the function upon failing the condition and I think it's due to that this a recursive function but I cannot figure it out.
/**
* Check if there is enough time to process more messages
*
* #param {} context
* #returns {boolean}
*/
async function enoughTimeToProcess(context) {
return context.getRemainingTimeInMillis() > 230000;
}
/**
* Consume the queue and increment usages
*
* #param context
*
* #returns {boolean}
*/
async function process(context) {
const messagesPerRequest = queueConst.messagesPerRequest;
const messagesToBeDeleted = [];
const queue = new queueClient();
const messages = await queue.getMessages(messagesPerRequest);
if (messages === undefined) {
if (await enoughTimeToProcess(context) === true) {
await process(context);
} else {
return false;
}
}
const responses = messages.map(async(messageItem) => {
const messageBody = JSON.parse(messageItem.Body);
const parsedMessage = JSON.parse(messageBody.Message);
const accountId = parsedMessage[0].context.accountId;
let code = parsedMessage[0].context.code;
// Our DB support only lowercase characters in the path
code = code.toLowerCase();
const service = parsedMessage[0].name;
const count = parsedMessage[0].increment;
const storageResponse = await incrementUsage(
{ storageClient: storage, code, accountId, service, count }
);
if (storageResponse) {
messagesToBeDeleted.push({
Id: messageItem.MessageId,
ReceiptHandle: messageItem.ReceiptHandle,
});
}
return 1;
});
const processedMessages = await Promise.all(responses);
const processedMessagesCount = processedMessages.length;
if (messagesToBeDeleted.length > 0) {
console.log(`${processedMessagesCount} messages processed.`);
await queue.deleteMessageBatch(messagesToBeDeleted);
}
if (await enoughTimeToProcess(context) === true) {
await process(context);
}
return true;
}
I think the problem can be when messages are undefined and there is still enough time, because the recursive function is going to be called infinite times, because it always accomplishes both conditions, and probably it exceeds the available resources.
Try to sleep some time before calling process function again, just to be sure it is the problem

Categories