Generating an array in the view and passing it into the controller - node.js

It's a little strange but I cannot think of a better way to get it done.
First of all this is my code:
The router to get the view in the first place
router.get('/', function(req, res, next) {
Account.findOne(
{
_id: req.user._id,
},
function(err, acc) {
if (err) {
console.log(err);
}
// console.log(acc.websites);
res.render('reports/index', {
title: 'Reports!',
websites: acc.websites,
user: req.user,
});
}
);
});
The view:
<% include ./../partials/header.ejs %>
<h1 class="text-center">This is your report page</h1>
<form method="post">
<% for(let i=0; i<websites.length; i++){ let website = websites[i]; %>
<fieldset>
<label for="website<%=i%>" class="col-sm-2">Website <%=i+1%></label>
<input name="website<%=i%>" id="website<%=i%>" value="<%=website%>" type="text" />
</fieldset>
<% } %>
Generate report
</form>
<% include ./../partials/footer.ejs %>
The router, that's supposed to fire up after the on click.
router.get('/reports', function(req, res, next) {
if (req.user.isPremium == false) {
// Free user - Single report
var builtWithCall = `https://api.builtwith.com/free1/api.json?KEY=00000000-0000-0000-0000-000000000000&LOOKUP=${website}`;
let website = req.body.website0;
console.log(website);
}
});
How it works: The controller finds the account, grabs an array from inside of it, sends it to the view. The view prints it out and allows to make some changes to the values.
And now is where the problems begin. I need to gather the new values into an array and send it to the next router, which will then use them to call a bunch of APIs and print out the data. How do I gather the array and pass it to the controller? Also, should I use GET or POST?

With your logic, the name attribute will have the following form: name=website[0...n]. With that in mind, we can filter out the keys to gather all the website[n] into an array you seek:
const example = {
website0: 'example',
website1: 'example',
website2: 'example',
website3: 'example',
shouldBeIgnored: 'ignoreMe',
ignore: 'shouldIgnore'
}
const websites = Object.keys(example).filter(key => key.startsWith('website'))
console.log(websites)
So you're controller can be:
router.get('/reports', (req, res, next) => {
if (!req.user.isPremium) {
// Free user - Single report
const builtWithCall = `https://api.builtwith.com/free1/api.json?KEY=00000000-0000-0000-0000-000000000000&LOOKUP=${website}`;
const websites = Object.keys(req.body).filter(key => key.startsWith('website'));
console.log(websites);
}
});

Related

Updating array of objects in mongoose based off key in objects value

I have two ejs forms that when hit, make an HTTP post request to my /api/users/makePicks/:id route. This route hits my controller which updates the Users model in my mongodb with the NFL picks they submitted in the EJS form.
I need this route to create the picks object for each route if they do not exist for that particular week, and if they do exist it needs to update the picks that are already there. The picks are being stored in my User model in an array, this array contains objects for each weeks picks. Currently the code, with much help from Mohammed, is successfully pushing code to to array. But i cannot seem to figure out how to update the picks if an object with a key of that week exists.
My validation is finally working properly. What I mean is we are running a for loop on the picks array, it will console.log true if there is already a matching picks object with for that weeks picks, if the object with a first key value with the current weeks form doesn't exist, it will console.log false and push the new picks to the array.
The only part that isn't working is the if statement nested within my for loop, it is not updating the object if it already exists in the picks.array. But as I said, the validation is working correctly. I suspect the line of code
result.picks[i] = { [`week-${req.params.week}`]:req.body };
is for some reason not updating object with the updated req.body.
Controller
exports.makePicks = async (req, res, next) => {
const picks = req.body;
try {
let result = await User.findById(req.user._id);
if (result.picks.length > 0) {
for (let i = 0; i < result.picks.length; i++) {
if ((Object.keys(result.picks[i])[0] == [`week-${req.params.week}`])) {
console.log(chalk.green("true"));
result.picks[i] = { [`week-${req.params.week}`]:req.body };
break;
} else {
console.log(chalk.red("false"));
result.picks.push({ [`week-${req.params.week}`]: picks });
break;
}
}
} else {
result.picks.push({ [`week-${req.params.week}`]: picks });
console.log(chalk.yellow('results.picks is empty'))
}
await result.save();
res.redirect("/api/dashboard");
} catch (error) {
console.log(error);
}
};
result.picks example structure
[{"week-1":
{"jojo-0":"ARI","jojo-1":"ARI","jojo-2":"ARI"}
},
{"week-2":
{"jojo-0":"ATL","jojo-1":"ATL","jojo-2":"BAL"}
},
{"week-3":
{"jojo-0":"ARI","jojo-1":"ARI","jojo-2":"ARI"}
}]
Router
router.route('/makePicks/:week')
.post(controller.makePicks);
EJS
<% const teamsArr = ['ARI', 'ATL', 'BAL', 'BUF', 'CAR', 'CHI', 'CIN', 'CLE', 'DAL', 'DEN', 'DET', 'GB', 'HOU', 'IND', 'JAX',
'KC', 'LAC', 'LAR', 'LV', 'MIA', 'MIN', 'NE', 'NO', 'NYG','NYJ', 'PHI', 'PIT', 'SEA', 'SF', 'TB', 'TEN', 'WAS' ] %>
<form class="mt-3 mb-3" method="POST" action="/api/users/makePicks/1">
<% for(i=0; i < user.bullets; i++){ %>
<div class="form-group">
<label for="<%= `${user.name}-${i}` %>">Make your pick for bullet <%= `${i}` %></label>
<select class="form-control" name="<%= `${user.name}-${i}` %>" id="<%= `${user.name}-${i}` %>">
<% teamsArr.forEach(team => { %>
<option value="<%= team %>"><%= team %></option>
<% }) %>
</select>
</div>
<% }; %>
<button type="submit" class="btn btn-primary">Save changes</button>
</form>
<form class="mt-3 mb-3" method="POST" action="/api/users/makePicks/2">
<% for(i=0; i < user.bullets; i++){ %>
<div class="form-group">
<label for="<%= `${user.name}-${i}` %>">Make your pick for bullet <%= `${i}` %></label>
<select class="form-control" name="<%= `${user.name}-${i}` %>" id="<%= `${user.name}-${i}` %>">
<% teamsArr.forEach(team => { %>
<option value="<%= team %>"><%= team %></option>
<% }) %>
</select>
</div>
<% }; %>
<button type="submit" class="btn btn-primary">Save changes</button>
</form>
you want to using $push and $set in one findByIdAndUpdate, that's impossible, I prefer use findById() and process and save() so just try
exports.makePicks = async (req, res, next) => {
const picks = req.body;
try {
//implementation business logic
let result = await User.findById(req.user._id)
if(result.picks && result.picks.length > 0){
result.picks.forEach(item =>{
if([`week-${req.params.week}`] in item){
item[`week-${req.params.week}`] = picks
}
else{
result.picks.push({ [`week-${req.params.week}`] : picks })
}
})
}
else{
result.picks.push({ [`week-${req.params.week}`] : picks })
}
await result.save()
res.redirect('/api/dashboard');
} catch (error) {
console.log(error)
}
}
note: don't use callback and async/await together
exports.makePicks = async (req, res, next) => {
const picks = req.body;
const { week } = req.params;
try {
let result = await User.findById(req.user._id);
const data = { [`week-${week}`]: picks };
const allPicks = [...result.picks];
if (allPicks.length > 0) {
// Search index of peek
const pickIndex = _.findIndex(allPicks, (pick) => {
return Object.keys(pick)[0] == `week-${week}`;
});
// If found, update it
if (pickIndex !== -1) {
console.log(chalk.green("true"));
allPicks[pickIndex] = data;
}
// Otherwise, push new pick
else {
console.log(chalk.red("false"));
allPicks.push(data);
}
} else {
allPicks.push(data);
console.log(chalk.yellow('results.picks is empty'))
}
result.picks = allPicks;
console.log('allPicks', allPicks);
await result.save();
res.redirect("/api/dashboard");
} catch (error) {
console.log(error);
}
};

AJAX call is broken, NodeJS, Express, Handlebars

I've been working on this for a couple of days. I'm certain its something really stupid, but I'm at the end of my sanity.
The public files are set up properly.
Error Message:
Uncaught ReferenceError: togDefine is not defined
Front End HTML:
<li class="list-group-item list-group-item-dark">
<div class="row">
<div class="col-md-4"><strong>Heating:</strong> {{#if heating}} {{this.heating}} {{else}} N/A {{/if}}</div>
<div class="col-md-4"><strong>Cooling:</strong> {{#if cooling}} {{this.cooling}} {{else}} N/A {{/if}}</div>
<div class="col-md-4">
<input type="checkbox" id="pvt{{this.id}}" checked="{{this.private}}" onchange="togDefine({{this.id}}, {{this.private}});" data-toggle="toggle" data-on="Private" data-off="Public" data-onstyle="success" data-offstyle="danger" />
</div>
</div>
AJAX Call:
$(function() {
// Private-Public toggle
let togDefine = (id, pvt) => {
$.ajax({
type: "POST",
url: "/api/pvtToggle",
data: {
id: id,
newState: (pvt === 'true') ? false : true
},
success: function(text) {
if (text === 'ok') {
pvtSuccess(id, pvt);
} else {
console.log('updatePvt failed');
}
}
});
};
let pvtSuccess = (id, pvt) => {
$('#pvt' + id).attr('checked', (pvt === 'true') ? 'false' : 'true');
};
});
Back End:
//TOGGLE Private vs Public PROPERTY
app.put('/api/pvtToggle/', isAuthenticated, function(request, response) {
db.Prop.update({
private: request.params.newState
}, {
where: {
id: request.params.id
}
}).then(data => {
response.send('ok');
}).catch(error => {
console.log(error);
});
});
Please help me figure out why the request isn't working properly. :D
Your function togDefine() is defined inside this block:
$(function() { /* in here */ })
Therefore, that function name is only available inside that block and is not available to your HTML. Since just defining a function doesn't actually execute anything, there is really no reason to define a function inside of that kind of block unless you ONLY want the symbol available inside that block.
Since you explicitly don't want that here, just move the definition of togDefine() outside that block.

How do I have a variable available to display on my success page, after adding items to a database via a /POST route?

I would like to display the doc.id variable of a successful /POST of data to a route, on the success page that the user will be redirected to afterward. I'm trying to work out how to carry the variable teamId through to the Handlebar template page success.hbs
I've tried making it a variable, and setting up a Handlebar helper to display it, but nothing is working.
/POST route redirecting to success.hbs:
app.post('/create', (req, res) => {
var players = [];
var playerObj = {};
for (let i = 1; i < 21; i++) {
var playerObj = { playerName: req.body[`player${i}Name`], playerNumber: req.body[`player${i}Number`], playerPosition: req.body[`player${i}Position`] };
if (req.body["player" + i + "Name"] === '') {
console.log("Empty player name detected, disregarding");
} else {
players.push(playerObj);
}
}
var newTeam = new Team({
// WEB SETUP BELOW
"team.teamRoster.teamCoach": req.body.coachName,
"team.shortTeamName": req.body.teamShortName,
"team.teamName": req.body.teamName,
"team.teamRoster.players": players
});
newTeam.save().then((doc) => {
var teamId = doc.id;
console.log(teamId);
res.render('success.hbs');
console.log("Team Added");
}, (e) => {
res.status(400).send(e);
});
});
/views/success.hbs
<div class="container-fluid" id="body">
<div class="container" id="page-header">
<h1><span id="headline">Team Added Succesfully</span></h1>
<hr>
<h3><span id="subheadline">Input the following address as a JSON Data Source within vMix.</span></h3>
<span id="content">
<div class="row">
<div class="container col-md-12">
{{{teamId}}}
</div>
</div>
</span>
</div>
<hr>
</div>
I'd like a Handlebar helper to get the doc.id value of the /POST request, and store it as teamId to display on the success page. It's finding nothing at the moment.
Any help is appreciated.
Node.js can pass variables to the handlebars-view like this:
newTeam.save().then((doc) => {
var teamId = doc.id;
console.log(teamId);
res.render('success.hbs', {
teamId
});
console.log("Team Added");
}, (e) => {
res.status(400).send(e);
});

Grouping my data in the view - nodejs

I grab some data from postgresql DB and want to display it in a view, simple enough as below
routes.js
app.get('/fixtures', async (req, res) => {
const fixtures = await queries.getFixtures();
res.render('fixtures', { fixtures });
});
fixtures returns
[ { id: 27,
home_team: 'Chelsea',
away_team: 'Liverpool',
league_name: 'English Premiership',
},
{ id: 25,
home_team: 'Man Utd',
away_team: 'Everton',
league_name: 'English Premiership',
},
{ id: 30,
home_team: 'Istanbul Basaksehir',
away_team: 'Akhisar Belediye',
league_name: 'Turkish Super Lig',
}
]
getFixtures();
async function getFixtures() {
let response;
try {
response = await pool.query('select * from fixtures ORDER BY league_name ASC');
} catch (e) {
console.error('Error Occurred', e);
}
return response.rows;
}
fixtures.ejs
<% fixtures.forEach((fixture) => { %>
<p><%=fixture.league_name %></p>
<p><%= fixture.home_team %> vs <%= fixture.away_team %> </p>
<% }) %>
So the above will output
English Premiership
Chelsea vs Liverpool
English Premiership
Man Utd v Everton
Turkish Super Lig
Istanbul Basaksehir vs Akhisar Belediye
However I would like to group my fixtures by league and would prefer to have the view output as
English Premiership
Chelsea vs Liverpool
Man Utd v Everton
Turkish Super Lig
Istanbul Basaksehir vs Akhisar Belediye
How do I go about achieving this? Is this something at DB query level or something I do in the view? (though probably not the best place to keep logic I guess)
Thanks
I'm not sure what tools/libs you are using but without depending on any libs/tools you can do it using vanilla JavaScript. For example, check the following code:
app.get('/fixtures', async (req, res) => {
const fixtures = await queries.getFixtures();
const grouped = groupByLeagueName(fixtures);
res.render('fixtures', { fixtures: grouped });
});
Then, implement the groupByLeagueName function like this:
function groupByLeagueName(fixtures) {
return fixtures.reduce((result, item) => {
result[item.league_name] = result[item.league_name] || [];
result[item.league_name].push(item);
return result;
}, {});
}
Then in your view you may loop it something like this:
<% for (let leagueName in fixtures) { %>
<p><%= leagueName %></p>
<% fixtures[leagueName].forEach((match, key) => { %>
<p><%= match.home_team %> vs <%= match.away_team %> </p>
<% }) %>
<% } %>

Nodejs EJS helper functions?

Is there a way to register helper functions to EJS templates, so that they can be called from any EJS template? So, it should work something like this.
app.js
ejs.helpers.sayHi = function(name) {
return 'Hello ' + name;
});
index.ejs
<%= sayHi('Bob') %>
Yes, in Express 3 you can add helpers to app.locals. Ex:
app.locals.somevar = "hello world";
app.locals.someHelper = function(name) {
return ("hello " + name);
}
These would be accessible inside your views like this:
<% somevar %>
<% someHelper('world') %>
Note: Express 2.5 did helpers differently.
I have another solution to this, and I think it has some advantages:
Don't polute your code exporting filters.
Access any method without the need to export them all.
Better ejs usage (no | pipes).
On your controller:
exports.index = function(req, res) {
// send your function to ejs
res.render('index', { sayHi: sayHi });
}
function sayHi(name) {
return 'Hello ' + name;
};
Now you can use sayHi function inside your ejs:
<html>
<h1><%= sayHi('Nice Monkey!') %></h1>
</html>
You can use this method to send modules to ejs, for example, you could send 'moment' module to format or parse dates.
Here's an example filter...I'm not familiar with helpers.
var ejs = require('ejs');
ejs.filters.pluralize = function(num, str){
return num == 1 ? str : str+'s';
};
<%=: items.length | pluralize:'Item' %>
Will produce "Item" if it's 1, or if 0 or > 1, produces "Items"
app.js
ejs.filters.sayHi = function(name) {
return 'Hello ' + name;
});
index.ejs
<%=: 'Bob' | sayHi %>
I am using:
In helpers/helper.js
var func = {
sayhi: function(name) {
return "Hello " + name;
}, 
foo: function(date) {
//do somethings
}    
};
module.exports = func;
In router:
router.get('/', function(req, res, next) {
res.render('home/index', {
helper: require('../helpers/helper'),
title: 'Express'
});
});
In template:
<%= helper.sayhi("Dung Vu") %>
goodluck

Resources