Refresh section of a page using EJS and Express - node.js

I have a checkbox that when pressed call a function that does a GET request. Based on the selection I want to display extra checkboxes on the same page. At the moment this is the code I have so far:
Client side
function selectedHouse(house)
{
if(house.checked){
$.get('/', {data: house.value});
}
}
Server side
var routing = function (nav, houses) {
router.route('/')
.get(function (req, res) {
var rooms = [];
rooms = getRooms(req.query.data);
console.log(rooms);
res.render('index', {
title: 'Independent cleaner',
nav: nav,
houses: houses,
roomsForHouse: rooms
});
});
return router;
};
The first time the page loads, it loads with the correct title, nav and houses. When the function is executed on client side, I get back the related rooms for the house and I'm trying to populate the roomsForHouse variable which I'm displaying on the view.
The problem is that the view doesn't render the roomsForHouse variable. So the GET request is called once the page loads and a second time when the function executes. Can this be achieved?

It's a bit more complex. You'll need to use ajax for this. EJS is server side templates (as you are using them) so you'll want to use jQuery to make the call and update your already rendered page.
Server
Your server will need a route that delivers JSON data. Right now you are rendering the entire page. So:
app.get('/rooms/:id', function (req, res) {
// Get the house info from database using the id as req.params.id
...
// Return the data
res.json({
rooms: 2
...
});
});
Client
Using jQuery make a call to your json route once the user selects the house.
function selectedHouse(house)
{
if(house.checked){
// Pass some identifier to use for your database
$.ajax({
type: 'GET',
url: '/rooms/' + house.id,
success: function(data) {
// Update the element - easiet is to use EJS to make sure each house has an id/class with the id in it
// Given an ID of 2 this says find the div with class house_2 and updates its div with class room to the number of rooms
$('.house_' + house.id + ' .rooms').text(data.rooms);
});
}
}
This is more of pseudo code then anything but should put you on the right track.

res.render can't rerender view, for refresh page in second time you need use javascript to replace html. This is not good solution
$.get("/",function(html){
document.open();
document.write(html);
document.close();
});
For better you should use another router to render DOM you want to change

Related

Best way to implement notification alerts in Node/Express app?

I have inherited a Node/Express code base and my task is to implement a notification alert in the navigation menu. The app database has a table of 'pending accounts', and the alert needs to expose the number of these pending accounts. When an account is 'approved' or 'denied', this notification alert needs to update and reflect the new total of pending accounts.
I know how to do the styling and html here, my question is how best to instantiate, maintain and pass a global dynamic variable that reflects the number of pending accounts, and how to get this variable exposed in the header view which contains the navbar where the notification is to be displayed.
This project a pretty standard Node/Express app, however it uses the view engine Pug. At the root of the view hierarchy is a layout.pug file, which loads most of the scripts and stylesheets, and this layout view in turn loads the header Pug view. When this header view loads, and every time it loads, I need this updated 'pending accounts count' value available to insert into the header view. This is what I am at a bit of a loss on how to go about.
Below is the layout.pug markup with the inclusion of the header pug view. Everything else in the project is pretty straightforward vanilla Node/Express I believe, but I am not very experienced with this stack so if any other code is needed please don't hesitate to ask and I will post. Thanks.
doctype html
html(lang="en")
head
meta(charset='utf-8')
meta(http-equiv='X-UA-Compatible', content='IE=edge')
meta(name='viewport', content='width=device-width, initial-scale=1.0, shrink-to-fit=no')
meta(name='theme-color', content='#4DA5F4')
meta(name='csrf-token', content=_csrf)
script(src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js")
block head
body
include partials/header
I tried including a script in my header.pug view, which contains the navbar element that I want to append the notification too...
link(href='/css/header/header.css' rel='stylesheet')
script(src='/js/registration/registrationsTable.js')
...
Which would run the following function on DOM load....
function getNumberOfPendingRegistrations() {
axios({
method: 'get',
url: '/admin/getNumberOfPendingRegistrations'
}).then((response) => {
if (response.status === 200) {
const { numPendingUsers } = response.data;
console.log(response.data);
} else {
console.log(response.status);
}
})
.catch((error) => {
console.error(error);
});
}
(() => {
document.addEventListener('DOMContentLoaded', () => {
getNumberOfPendingRegistrations();
which would then call the following express function....
exports.getNumberOfPendingRegistrations = (req, res) => {
RegisteredUser.find({ status: 'PENDING' }, (err, allPendingUsers) => {
if (!err) {
let numPendingUsers = 0;
allPendingUsers.forEach(() => {
numPendingUsers++;
});
return res.send(200, { numPendingUsers });
}
console.log('error');
throw err;
});
};
which would then return numPendingUsers to the axios then() function and make that variable available to the header.pug view here....
li.nav-item
a.nav-link(href='/admin/registeredUsers')
span.fa-solid.fa-pen-to-square(style="font-size: 0.7em;")
span(style="margin-left: 0.1em;") Registrations
span.notification=numPendingUsers
NumPendingUsers returns correctly to the axios .then() promise, but is somehow never made available in the header.pug view. It is always undefined. I'm not sure if its a timing issue w when the DOM is loaded, or if I'm making the variable available in .then() incorrecly or what. And also I feel like there must be a simpler way to accomplish all of this.
Figured out that I simply needed to implement a middleware to pass this data to every route. Duh.

Error: Can't set headers after they are sent - Ajax, Node, Express

I'm trying to using Airtable, node.js, express.js and jquery to create a simple user authentication functionality but I'm fairly new at this and I'm running into a problem I can't seem to fix and the articles I've read I can't seem to grasp or adapt to my particular situation.
I have this Ajax call in my html doc:
$("#checkUser").submit(function(e) {
var studentID = $('input[name="student"]').val()
e.preventDefault(); // avoid to execute the actual submit of the form.
var form = $(this);
var url = form.attr('action');
$.ajax({
type: "POST",
url: url,
data: form.serialize(), // serializes the form's elements.
success: function(data) {
$(window).attr("location", window.location.href + 'Dashboard?student=' + studentID);
},
error: function(data){
console.log("User not found. Try again");
}
});
});
This call sends the inputted username and data to the server which then processes it in the following way:
app.post('/checkUser', urlencodedParser, function(request,response){
var user = JSON.stringify(request.body);
user = JSON.parse(user);
base('RegisteredStudents').select({
filterByFormula: '{UserID} = ' + user.student,
view: "Grid view"
}).eachPage(function page(records, fetchNextPage) {
records.forEach(function(record) {
response.sendStatus(200);
});
fetchNextPage();
}, function done(error) {
response.sendStatus(404);
});
});
If the user exists in the database of Airtable, it should send '200' which the Ajax then reacts by redirecting accordingly to the user's profile. Otherwise, if the user does not exist, the server should respond with '404', which the Ajax call should react to by printing a statement in the console. While it does do these two things well, the server breaks down when, after a student puts in the wrong user ID and the Ajax prints the statement, the student tries to put once more a userID. I get the " Can't set headers after they are sent. " message. Please, how can I solve this?
Thank you!
You have two response.send..., you can only send data once. Either make sure only one runs with some conditional or add return before all response.send... so if any of them runs, the program will return and the other response.send.. will not run.

Send variable from mongoose query to page without reload on click

I have a link on my site. When clicked it'll call a function that does a mongoose query.
I'd like the results of that query to be sent to the same page in a variable without reloading the page. How do I do that? Right now it is just rendering the page again with new query result data.
// List comments for specified chapter id.
commentController.list = function (req, res) {
var chapterId = req.params.chapterId;
var query = { chapterId: chapterId };
Chapter.find({ _id: chapterId }).then(function (chapter) {
var chapter = chapter;
Comment.find(query).then(function (data) {
console.log(chapter);
Chapter.find().then(function(chapters){
return res.render({'chapterlinks', commentList: data, user: req.user, chapterq: chapter, chapters:chapters });
})
});
})
};
You just need to make that request from your browser via AJAX:
https://www.w3schools.com/xml/ajax_intro.asp
This would be in the code for your client (browser), not the code for your server (nodejs).
UPDATE:
Here's a simple example, which uses jQuery to make things easier:
(1) create a function that performs and handles the ajax request
function getChapterLinks(chapterId) {
$.ajax({
url: "/chapterLinks/"+chapterId,
}).done(function(data) {
//here you should do something with data
console.log(data);
});
}
(2) bind that function to a DOM element's click event
$( "a#chapterLinks1" ).click(function() {
getChapterLinks(1);
});
(3) make sure that DOM element is somewhere in you html
<a id="chapterLinks1">Get ChapterLinks 1</a>
Now when this a#chapterLinks1 element is clicked, it will use AJAX to fetch the response of /chaptersLink/1 from your server without reloading the page.
references:
http://api.jquery.com/jquery.ajax/
http://api.jquery.com/jquery.click/

Return view and send data to react after passport.js oauth

First let me say that sorry if this is a simple question, I'm new to react and express and couldn't find the answer on SO.
I'm trying to pass data to a react object as well as render a view based off of a passport.js return route.
I have the object i need to pass I just can't figure out how to pass it.
First the user hits the auth route
router.get('/steam',
passport.authenticate('steam'),
(req, res) => {
//Not used
});
Then after they login through steam they're returned to this route:
router.get('/steam/return',
passport.authenticate('steam', { failureRedirect: '/' }),
(req, res) => {
res.redirect('/dashboard');
});
From here I'm passing them to the /dashboard route where im taking the user object and creating another call to grab their library and then sending the data to the view:
router.get('/', (req, res, next) => {
var OwnedGamesReqUri = 'http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?key=' + process.env.STEAM_API + '&steamid=' + req.user.steamid + '&format=json&include_appinfo=1';
request(OwnedGamesReqUri, (error, response, body) => {
if (!error && response.statusCode == 200) {
var resObj = JSON.parse(body);
//name, playtime_forever, playtime_2weeks, img_icon_url, img_logo_url
res.render('dashboard', { user: req.user, games: resObj.response.games});
}
});
});
I'm able to grab all of the data I need in the dashboard view via handlebars but the problem I'm facing is what I can do to pass the ownedgames data to a react object in another JS file. I have a react component set up in another file that is loaded to the dashboard via a bundle file. I'm assuming this needs to go to the server but I'm just not exactly sure how to achieve this.
Your question isn't about reactjs or passportjs. It's basically how we can pass a variable in server-side javascript to client-side javascript. So you can use that variable in whatever the framework you use in the client-side.
The simplest way to achieve this is rendering your value as variable declaration within an inline script tag in your template. So your client js will have a global variable with that value, which can access from anywhere in your client side scripts.
As an example, if have a variable with string value as follows
var test = 'abc';
You can render it in your template as a variable declaration within a script tag.
res.render('template', test)
template file:
<script>
var test = '{{test}}';
</script>
Now this template will generate an HTML page as follows.
<script>
var test = 'abc';
</script>
So test variable will be a global client side javascript variable with value 'abc', which can access from any other javascript code on the same page.
Even though this is simple as above for a string variable, things get complicated when you are trying to send an object. Because object variables render in the template as [Object object]. To avoid this, you need to send stringify version of your object before sending it the handlebar template.
var data = JSON.stringify({ user: req.user, games: resObj.response.games});
res.render('dashboard', data);
Then your dashboard template needs to have script tag with the variable declaration in addition to its original content.
//...
<script>
var data = {{data}};
</script>
//...
Now, data will be a global variable in your client side and you can use it in your reactjs code to access user and games.
There are several ways to do this, depending on how your React component manages its state.
Reactive state (RxJS, mobx, nx-observe):
The easiest to deal with, your component as well as the non-js code can both access these stores and catch up on the updates together. This provides a very loose coupling between the data provider and consumer.
Component with a flux pattern:
If your component is using Redux, Reflux or any other flux based library to manage state, you can export corresponding actions/functions from your flux code which any JavaScript code can call to update your component's state.
State maintained within the component:
Somewhat messy. In this case your component can export a callback which your script can call to send it new data.

How to apply post redirect and get pattern in node.js?

I am testing with a very simple application in node.js where I create and save an application. I show the post form with the newPost function and I receive the post with the data in the savePost method. In the latter one I do a validation (with iform module) and I want to go show again the same page as before but filling the form with the data sent by the user and also with the errors found.
I have a similar code like this one. In it I render the same jade page if I find any error. It works though I want to apply the pattern redirect and get there as I don't want to send again the post request when the user presses F5.
So, how is the usual way to make a post redirect and get from the post method passing them all the parameters I have received adding the errors? Is there any module which can help to do so?
var prepareObject = function(req, res){
var errors = {};
if('iform' in req){
errors = req.iform.errors;
}
return {title: 'Nuevo Post', body:req.body, errors: errors};
};
// mapped as /newPost (type GET)
exports.newPost = function(req, res){
//show form to create post
res.render('newPost', prepareObject(req, res));
}
// mapped as /savePost (type POST)
exports.savePost = function(req, res){
if(req.iform.errors) {
//there are errors: show form again to correct errors
res.render('newPost', prepareObject(req, res));
}else{
//no errors: show posts
res.redirect('/posts');
}
}
You can redirect to GET "/newPost" instead of rendering the "newPost" template.
To have autocomplete working, you may either add the data to the redirect query (faster) and render it, or add the data to the session (don't forget to delete it after rendering), but the later option requires a session store.

Resources