setInterval() in EJS template does not work - javascript

I'm currently stuck with a problem in a homework project. I'm trying to make a project where the price of bitcoin will update every second. Now the API request is working fine and I can see the data render from an EJS template but I can't make the price update every second. Can anyone check my code and see if anything is wrong in my code? For reference please check www.preev.com. It shows how I want the price to be updated. And also check below my code.
I have tried to call the API request in app.js file and rendered it in an EJS template called results.ejs.
app.js
var express = require("express");
var app = express();
var request = require("request");
app.set("view engine", "ejs");
app.get("/", function(req, res) {
request("https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd&include_market_cap=true&include_24hr_vol=true&include_24hr_change=true&include_last_updated_at=true", function(error, response, body) {
if(!error && response.statusCode == 200) {
var data = JSON.parse(body);
res.render("result", {data: data});
}
});
});
app.listen(3000, function(){
console.log("server has started");
});
results.ejs
<h1>
Bitcoin Latest
</h1>
Price: $ <span id="showPrice"></span>
<br>
MarketCap: $<%= data["bitcoin"]["usd_market_cap"] %>
<br>
24h Volume: $<%= data["bitcoin"]["usd_24h_vol"] %>
<br>
24h Change: <%= data["bitcoin"]["usd_24h_change"] %>%
<script>
function updatePrice(){
document.getElementById("showPrice").innerHTML= <%= data["bitcoin"]["usd"] %>;
};
setInterval(updatePrice, 500);
</script>

Initial answer
Your setInterval works fine, it's just that inside your function the data never changes.
To fix it you have to reference a variable (of which the content changes), rather than hardcoding the value in your function.
Extra explanation
For example you are using EJS, which is a templating language. A templating language parses output based on your variables (once per page load).
Your template line
document.getElementById("showPrice").innerHTML= <%= data["bitcoin"]["usd"] %>;
parses into
document.getElementById("showPrice").innerHTML= 9624.46;
And your interval then updates the innerHTML of #showPrice with that value, every 500 ms.
What you probably mean to do is make the request from the client (the browser), then store its response into a variable, say latestResult, and then code your function to reference that variable, like so:
document.getElementById("showPrice").innerHTML= latestResult;
Example implementation
This means that your express application (app.js) will render result without data:
app.get('/', function(req, res) {
res.render('result');
});
And the request part will be in your template:
function updateLatestPrice() {
fetch('https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd&include_market_cap=true&include_24hr_vol=true&include_24hr_change=true&include_last_updated_at=true').then((result) => {
const latestResult = result.bitcoin.usd
document.getElementById("showPrice").innerHTML = latestResult || 'failed'
})
}
setInterval(updateLatestPrice, 3000)
Note that I changed request into fetch here because I couldn't be sure whether your client code has babel, so I went with the browser's native Fetch API.

Related

Get variable from client-side JavaScript file in Express

I am trying to send a variable from my client-side JavaScript file to my server-side app.js file. I know that you can get a value from something like a form input field using methods such as the one below (using Express):
var express = require('express');
var app = express();
var path = require('path');
let cityResponse;
app.post('/city', function(req,res) {
cityResponse = {
user_name: req.body.userName
}
res.sendFile(path.join(__dirname, '/client/city/index.html'));
});
But I would like to get a value not directly from the HTML, but from the JavaScript file that the HTML is attached to.
As well as this, I am currently using Socket.io to send the data from the server to the client, and vice versa, using a window.onload to let the server know when the page is ready:
index.js
window.onload = function() {
socket.emit('cityPageReady');
console.log("city page ready");
};
socket.on('cityPageInfo', function(cityResponse) {
console.log('city page info received');
console.log(cityResponse);
console.log(cityResponse.user_name);
userName = cityResponse.user_name;
document.getElementById("name").innerHTML = userName;
});
app.js
var city = io.of('/city').on('connection', (socket) => {
console.log('A user has connected!');
socket.on('cityPageReady', function() {
socket.emit('cityPageInfo', cityResponse);
console.log('city page ready recieved');
});
});
This works, but many people have said that this is overkill, or as one person put it, "using a hammer to kill a bee". Ideally, I'd like to use the optimal method. I do know that template engines can achieve this, but I do not want to have to rewrite all my HTML just to be able to send a single variable to the server.
To reiterate, my questions are:
How would I get a variable from the client-side JavaScript file (not the HTML)?
What is the best way to send these variables back over to client-side?
Any help would be greatly appreciated.

Rendering something on server side by taking input from frontend

I have been asked to perform following task
Take a code input from frontend, i.e user would give his code on frontend (design for a web/landing page)
On backend we have many fields inside an api route
route.get("/", (req, res) => {
const fullName: "Varun Bindal"
const contactNo = 9293939933
const message = "Message I want to display"
//Many more
}
Tell user a way where when we serve his code such that, he could dynamically access/assign the fields we have in the backend into his code
I did some googling and found the express officially recommends ejs for server side compilation of webpage
Can someone please help me figure out how we can achieve this?
Yes you can!
Firstly you must include ejs in your project, configure it in your server.js file for example, then you can call res.render() in your callback parameter on route.get().
In your html or javascript you can create a placeholder which gets populated.
Example (server):
route.engine('html', ejs.renderFile);
route.engine('js', ejs.renderFile);
route.get('/', (req, res) => res.render(path.resolve(__dirname, '
../ui/index.html'), {
'myVal': 42,
}));
Example (client html, js, etc...):
<%= myVal %>

Script won't run from ejs file in nodejs application

I'm trying to make a webpage display a chart using nodejs, express, mysql, and ejs, but I clearly don't understand something about how ejs / javascript etc. works. I need a script to run that sets up a chart (from chart.js module) yet it is not outputting any sort of chart whatsoever.
What I tried:
placing console.log() messages in the script to see if my code within the script was the problem. There was no output to the console so I'm convinced the script itself is not running
placing paragraph tags in the same file above the scripts, which displayed
normally, showing that the file is still being accessed and that just the scripts aren't working
script below will not run:
<canvas id="myChart" width="50%" height="100px"></canvas>
<script scr="map-access-data.js" type="text/javascript"></script>
<script id ="map-popularity" type="text/javascript">
var Chart = require('chart');
var ctx = document.getElementById("myChart").getContext("2d");
var myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: getMapAccessNames(),
datasets:[{
label: "Map Selection Popularity",
data: getMapAccessData(),
backgroundColor: getMapColors(),
borderColor: getMapColors(),
borderWidth: 1
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero:true
}
}]
}
}
});
</script>
map-access-data.js file referenced in first script of that file:
var mysql = require('mysql');
var connection = mysql.createConnection({
host : 'localhost',
user : 'admin',
password : 'pass',
database : 'db'
});
connection.connect();
function getNumMaps(){
connection.query("SELECT NAME, COUNT(*) FROM map_access GROUP BY NAME ORDER BY NAME", function(err, rows){
return rows.length();
});
}
function getMapAccessData(){
var arr = [];
connection.query("SELECT NAME, COUNT(*) FROM map_access GROUP BY NAME ORDER BY NAME", function(err, rows){
for(i in rows){
arr.push(i.count);
}
});
}
function getMapAccessNames(){
var arr = [];
connection.query("SELECT NAME, COUNT(*) FROM map_access GROUP BY NAME ORDER BY NAME", function(err, rows){
for(i in rows){
arr.push(i.name);
}
});
return arr;
}
function getMapColors(){
var arr = [];
for(var i = 0; i < getNumMaps(); i++){
arr.push('rgba(255,99,132,1)');
}
return arr;
actual file that this code is rendered in:
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<% include header.ejs %>
<h1><%= title %></h1>
<p>See how people are using our app <br/></p>
<% include map-access-chart %>
</body>
</html>
There are a bunch of misconceptions here: The HTML file will be served to your client, which will see <script> tags, request them from web server, and execute the code. You cannot expect a web browser to run a SQL query on your server), so obviously this is not the way you want to execute this JS code.
So much is wrong, it will be a long answer. Here is a your two main misconceptions:
You try to make client execute server code
You run asynchronous code and think it will return synchronously
Then your many smaller errors:
length is a property, not a method
you have to export a method to make it usable from outside in node
you use for (i in rows) so i is the item index (and I guess you got that because you named it i), then you use i.count
in your SQL you put SELECT COUNT(*) FROM then just use .count property, I'm not sure it will work without AS count
At this point I can only guess your SQL and Chart usage are no better, sorry :( I will try to point you in the right direction though.
Identify client and server
So, first of all, you need to execute this JS code from your Node.js server. The usual steps would be:
initiate Express app
configure EJS rendering
in your route:
run your sql queries, get a bunch of data (you're still server side)
render your HTML, passing some data
now in your template, just inject what you need from server, to client
profit
Sample data structure for the next steps:
/
+- app.js
+- lib/
+- map-access-data.js
+- views/
+- index.ejs
+- map-access-chart.ejs
+- stylesheets/
+- styles.css
The server
Required server dependencies: npm install express ejs mysql, the rest is for client (like chart)
// app.js
const express = require('express')
const app = express()
// Serve public static assets, like stylesheets and client-side JS
app.use(app.static('public'))
// Configure EJS template engine
app.set('view engine', 'ejs')
// Your route
app.get('/', (req, res) => {
// Your route handler
// req is client's request
// res is server's response
})
// Start web server on http://localhost:8000
app.listen(8000)
OK, here you're server-side, you can use MySQL and similar systems. But first we need to address another issue.
Asynchronous code
Asynchronous is a very important part of Node, really, but we can't address everything here. You will have the keywords, I let you do your research to tame that part. I'll use async/await so you're not too disturbed when reading the code, and util.promisify to transform the methods. The things you have to understand:
connection.query will query a remote server, in Node it will be done asynchronously, which means you won't get any result immediately, but your code won't be stopped either (or it would be blocking, which sucks)
to represent asynchronous operations, you have basically two ways:
pass a callback function to your async function, this callback will be called with the result as soon as it's available; when using a callback you cannot return anything interesting
return an object (called a promise) which is just an empty wrapper; then later this object will suddenly contain the result, and be able to call functions you will have passed to its then method; when using promises you must return those objects, which are a representation of your future data and the only way to access it
when you work with promise there is a specific syntax called async which allows you to wait for promised data, but your async function will still be async which means it returns a wrapper, not your actual result, unless you wait for it too
Here is your errors:
In getNumMaps, your return is in the callback. This callback is called way after the function has returned its own result, so it will just return undefined
In getMapAccessData you didn't even bother to return anything, still undefined
In getMapAccessNames, finally you return something! but as connection.query is async, you will push data to your array way after funtion has already returned arr, so it always returns []
And I'll add you execute three times the same request, sounds wasteful ;) So, you know you want to finally include all this in your Chart instance, let's not split that in 4 functions which all execute the same query, we'll instead build a single result with adapted format.
// lib/map-access-data.js
const mysql = require('mysql')
const connection = mysql.createConnection(/* your config here */)
// get promises instead of using callbacks
const util = require('util')
const queryCallback = connection.query.bind(connection) // or we'll have issues with "this"
const queryPromise = util.promisify(queryCallback) // now this returns a promise we can "await"
// our asynchronous method, use "async" keyword so Node knows we can await for promises in there
async function getChartData () {
const rows = await queryPromise("SELECT name, COUNT(*) AS count FROM map_access GROUP BY name ORDER BY name")
// Array#map allows to create a new array by transforming each value
const counts = rows.map(row => row.count)
const names = rows.map(row => row.name)
const colors = rows.map(row => 'rgba(255,99,132,1)')
// Return an object with chart's useful data
return {
labels: names,
data: counts,
colors: colors,
}
}
Modules
OK, now you have a function, available server side only, that gives you what you need.
Now you need to be able to call it from app.js, which means you need to:
export it:
// lib/map-access-data.js
…
// Export your function as default
module.exports = getChartData
then import and use it in your route handler:
// app.js
const getChartData = require('./lib/map-access-data)
This is called CommonJS modules
Now in your route handler you can simply call your async function, and await for its result:
// app.js
…
app.get('/', async (req, res) => {
// Your route handler
const data = await getChartData()
})
Generating the HTML
Now you have your data made available, you're still server-side, you now have to generate valid HTML for your client, which currently looks like:
<!DOCTYPE html>
<html>
… a bunch of HTML …
<p>See how people are using our app <br/></p>
<canvas id="myChart" width="50%" height="100px"></canvas>
<!-- NO! it's not a client JS, it's server JS, client CANNOT download it -->
<script scr="map-access-data.js" type="text/javascript"></script>
<script id ="map-popularity" type="text/javascript">
var Chart = require('chart'); // NO! you can't *require* from client
var ctx = document.getElementById("myChart").getContext("2d");
var myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: getMapAccessNames(), // NO! You can't call server methods from client
datasets:[{
…
Obviously we need to fix a few things:
Remove the reference to map-access-data.js which makes no sense
Add chart.js the browser's way, like from a CDN
Inject data inside your client JS
Here I think instead of injecting the real data directly into HTML you could use an Ajax request, but I don't know Chart so I will let you do this part. An Express app serving JSON data is absolutely trivial, just res.send(data), then do some Ajax on client side. Let's see the version where you inject data directly into HTML to break all the walls:
Server-side you run your SQL, that gives you some data
You pass this data to your EJS template, which (still server-side) generates HTML
In this HTML you will inject String representation of your server data (using JSON.stringify)
Server finally sends generated HTML to client
Client receives this wellformed HTML, with some JS in <script>, runs it, everyone is happy
<!-- views/map-access-chart.ejs -->
<canvas id="myChart" width="50%" height="100px"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/1.0.2/Chart.min.js"></script>
<script id ="map-popularity" type="text/javascript">
var ctx = document.getElementById("myChart").getContext("2d");
var myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: <%- JSON.stringify(data.labels) %>,
datasets:[{
label: "Map Selection Popularity",
data: <%- JSON.stringify(data.data) %>,
backgroundColor: <%- JSON.stringify(data.colors) %>,
borderColor: <%- JSON.stringify(data.colors) %>,
borderWidth: 1
…
// app.js
…
// Your route handler
const data = await getChartData()
// Render 'index.ejs' with variables 'title' and 'data' available
res.render('index', {
title: 'Page title',
data: data,
})
Now when you run node app.js from your terminal, and go to http://localhost:8000 you should see the result. I guess there will be many remaining errors, but that will be a better start :)

How to access and display database (MongoDB) information in the view (HTML)?

I have a database on mlab.com that I can successfully post to using this code in my controller:
app.post('/dashboard', function(req, res) {
var newEvent = req.body;
db.collection('events').insertOne(newEvent, function(err, doc) {
if (err) {
error(res, "Failed to create new event.");
} else {
res.sendFile('views/dashboard.html', { root: '.' });
}
});
});
At the localhost:8080/dashboard URL, I have a form where a user can enter in event data.
Now my question is, how do I get that data to be able to display it in the HTML? For example, displaying a list of all the events entered at that same /dashboard URL but in a different location on the page. I believe I need to do something along these lines:
app.get('/dashboard', function (req, res) {
db.collection('events').findOne( {
eventName : req.body.eventName
}, function(err, doc) {
// ????
});
});
But then how do I display the data I got back in the HTML view?
Any help would be greatly appreciated because I've been stuck on this for quite some time now.
You have two options
1) Use a templating system like ejs or jade and render the page. For example if you are passing variables to a dashboard.ejs file located in views/pages folder, you call it like
res.render('views/pages/dashboard', {
feild1: value1,
feild2: value2
});
Then print it in dashboard.ejs using a code like
<div><%= feild1 %></div>
<div><%= feild2 %></div>
Lots of documentation is available in the net for both the options
2) Send data as json and use some javascript code or javascript based front end framework to render them. If you are good in front end javascript coding, you can go for this approach
var data = { "feild1" : doc.xxx, "feild2" : doc.yyyy };
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify(data));
The json can be accessed using an ajax call from client side (you can use jquery for that)
$.getJSON('/dashboard', function(obj) {
alert(obj.feild1);
alert(obj.feild2);
});

Updating data realtime in Node.js express website

I'm trying to achieve something what I think should be very simple to do, but all the tutorials and examples I find seem to be an overkill.
What I am doing:
I'm fetching weather info periodically, and I want to update the text on the website everytime its fetched without user having to refresh the browser.
Almost every tutorial on realtime data updating recommends using socket.io and I have given it a try, but I can't get it to do what I want and I'm starting to think that there should be an easier way and that socket.io might not be the best way to go about this. Any suggestions? How do I get simple line of text update on website without user having to refresh the page?
My weather script:
var express = require('express');
var app = express();
var server = require('http').Server(app);
var io = require('socket.io')(server);
function refreshWeather() {
var temperature = getTemperature();
io.sockets.emit('broadcast', {
temperature : temperature
});
}
My jade script:
doctype html
html
head
link(rel='stylesheet', href='/css/index.css')
title Dashboard
body
script(src='/socket.io/socket.io.js')
script.
var socket = io();
socket.on('broadcast', function (data) {
// UPDATE WEATHER HERE
});
.main-content
h1 Weather is: // I WANT THIS TO BE UPDATED
You could:
Get rid of socket.io.
Make an end point for retrieving the temperature.
Do some polling on the frontend.
Backend would look something like this:
var express = require('express');
var app = express();
var server = require('http').Server(app);
app.get('/weather', function (req, res) {
res.send(getTemperature())
});
Frontend would look something like this:
doctype html
html
head
link(rel='stylesheet', href='/css/index.css')
title Dashboard
body
.main-content
h1(id='weather') Weather is: // I WANT THIS TO BE UPDATED
script.
setInterval(function () {
fetch('/some/url', {
method: 'get'
}).then(function(response) {
// UPDATE WEATHER HERE
document.getElementById('weather').innerHTML = response
}).catch(function(err) {
// Error :(
});
}, 1000) // milliseconds
Code is totally untested, so please try not to copy and paste — adapt it instead. Also, if you're going to use the fetch API, you might want to use a polyfill, otherwise, just use plain ajax.

Categories