Script won't run from ejs file in nodejs application - javascript

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 :)

Related

How to get data from back end side, to use it in the browser side?

I am new to programming, and I heard that some guys on this website are quite angry, but please don't be. I am creating one web app, that has a web page and also makes som ecalculations and works with database (NeDB). I have an index.js
const selects = document.getElementsByClassName("sel");
const arr = ["Yura", "Nairi", "Mher", "Hayko"];
for (let el in selects) {
for (let key in arr) {
selects[el].innerHTML += `<option>${arr[key]}</option>`;
}
}
I have a function which fills the select elements with data from an array.
In other file named: getData.js:
var Datastore = require("nedb");
var users = new Datastore({ filename: "players" });
users.loadDatabase();
const names = [];
users.find({}, function (err, doc) {
for (let key in doc) {
names.push(doc[key].name);
}
});
I have some code that gets data from db and puts it in array. And I need that data to use in the index.js mentioned above, but the problem is that I don't know how to tranfer the data from getData.js to index.js. I have tried module.exports but it is not working, the browser console says that it can't recognize require keyword, I also can't get data directly in index.js because the browse can't recognize the code related to database.
You need to provide a server, which is connected to the Database.
Browser -> Server -> DB
Browser -> Server: Server provides endpoints where the Browser(Client) can fetch data from. https://expressjs.com/en/starter/hello-world.html
Server -> DB: gets the Data out of the Database and can do whatever it want with it. In your case the Data should get provided to the Client.
TODOs
Step 1: set up a server. For example with express.js (google it)
Step 2: learn how to fetch Data from the Browser(Client) AJAX GET are the keywords to google.
Step 3: setup a Database connection from you Server and get your data
Step 4: Do whatever you want with your data.
At first I thought it is a simple method, but them I researched a little bit and realized that I didn't have enough information about how it really works. Now I solved the problem, using promises and templete engine ejs. Thank you all for your time. I appreciate your help)

Unknown value in req.param in node.js

I'm am learning node.js and therefore try to build a simple web app that shows the current news. The API that I am using offers several categories for the news.
So I create a route that takes the category as a param. My routes/index.js:
const router = require('express').Router();
const renderHome = require('../controllers/newsController');
const quotesCookie = require('./../middleware/quotesCookie');
router.get('/', quotesCookie, renderHome);
router.get('/:category', quotesCookie, renderHome);
module.exports = router;
My controllers/newsController.js looks like this:
const newsService = require('./../services/newsService');
const renderHome = async ( req, res ) => {
const category = req.params.category;
console.log(req.params);
const quote = res.quoteOfTheDay;
const { status, msg } = await newsService.topHeadlines(category);
res.render('home', {
title: 'News2Go',
author: quote.author,
quote: quote.quote,
articles: msg.articles
});
};
module.exports = renderHome;
When I for instance call http://localhost:3000/entertainment the console.log in the controller prints this to the console:
{ category: 'entertainment' }
{ category: 'sw.js' }
I have absolute no clue where the sw.js comes from... It appears a few milliseconds after the real category and ensures that topHeadlines is called twice.
Did someone know what this is? Did I miss something?
Apparently your web page has a script in it named sw.js. Because of that, the browser will request that with the URL http://localhost:3000/sw.js and your :category route will handle that request and log a category of sw.js.
Remember, ALL resources used on your site will be requested by the browser and will be seen by your Express server as incoming requests. Not just the top level page, but all scripts, images, fonts, CSS files, etc... used by your pages.
It's generally not a good idea to define a wide-open top level route handler like this:
router.get('/:category', ...)
Because that will grab ALL top level URLs and leave none for the rest of your site to use. It would probably make more sense to use a structure like this:
router.get('/category/:category', ...)
With a URL of http://localhost:3000/category/entertainment. Then, you can more clearly separate out the actual category requests from all the other requests in your site. Either that or you will have to move ALL other URLs used on your site to routes that come before this and/or use sub-directories in their page such as:
http://localhost:3000/scripts/sw.js
http://localhost:3000/styles/main.css

setInterval() in EJS template does not work

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.

Express with JSON Data Control

I use lowDB dependency to control the JSON Data with Express and actually it works. But there is a bug and I cannot find how to solve it.
I create /create page to add information in JSON file and it contains 4 form and submit button.
And In express I code like this. each forms data will save it in variable and push with lowdb module.
router.post('/post', function (req, res) {
let pjName = req.body.projectName;
let pjURL = req.body.projectURL;
let pjtExplanation = req.body.projectExplanation;
let pjImgURL = req.body.projectImgURL;
console.log(pjName);
db.get('project').push({
name: pjName,
url: pjURL,
explanation: pjtExplanation,
imgurl: pjImgURL
}).write();
console.log(db.get('project'));
console.log(db.get('project').value());
res.redirect('/');
})
And it works well. But when I modify the JSON file myself (ex. reset the JSON file) and execute again. It shows the data that I reset before. I think in this app somewhere saves the all data and show save it in array again.
And When I shutdown the app in CMD and execute again, the Array is initialized.
As you may already know the lowdb persist the data into your secondary memory (hdd), and may return a promise depending on your environment when you call write method.As mentioned in the doc
Persists database using adapter.write (depending on the adapter, may return a promise).
So the data may be still getting write when you read them, so the old data is queried. Try this,
db.get('project').push({
name: pjName,
url: pjURL,
explanation: pjtExplanation,
imgurl: pjImgURL
}).write().then(() => {
console.log(db.get('project'));
console.log(db.get('project').value());
});

Meteor.js Collection not being created in mongo

Server Side Code:
if (Meteor.isClient) {
Meteor.subscribe("messages");
Template.hello.greeting = function () {
Messages = new Meteor.Collection("messages");
Stuff = new Meteor.Collection("stuff");
return "Welcome to feelings.";
};
Template.hello.events({
'click input' : function () {
// template data, if any, is available in 'this'
if (typeof console !== 'undefined')
var response = Messages.insert({text: "Hello, world!"});
var messages = Messages.find
console.log("You pressed the button", response, Messages, Stuff);
}
});
}
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
Messages = new Meteor.Collection("messages");
Messages.insert({'text' : 'bla bla bla'});
});
}
Client Side Code
<head>
<title>Test</title>
</head>
<body>
{{> hello}}
</body>
<template name="hello">
<h1>Hello World!</h1>
{{greeting}}
<input type="button" value="Click"/>
</template>
The problem:
When in javascript console I type Messages.insert({'text' : 'test test test'});
or click the button, underneath which a database insertion call is written
I don't see a document inserted in mongo. Going to mongo console and doing show dbs shows
messages (empty)
I have a few other questions, I have read through the meteor docs and also googled but I can't seem to find a clear answer to this:
Why do I need to declare a collection in client as well as server code?
I'm declaring collections inside Template.hello.greeting, what's the difference if I put it in if(Meteor.isClient) block directly.
Is any plans of adding some app directory structure in meteor like rails? where models and templates are separate? I'm not talking about express.js
Thanks.
You need to create the MongoDB collection in a global scope like outside of both the isClient and isServer scopes. So remove Messages = new Meteor.Collection("Messages") from that helper function and place it in global scope.
You cannot perform insertion directly through client, as meteor doesn't allows database insertion from client code. If you still want to insert/update from client, you must define database rules for client, see docs.
Or the preferred way is to create a server method to insert document, and call it from client using Meteor.call().
Creating collections inside Template.hello.greeting doesn't makes any sense, since the collections are used to store data on server that is accessible from client.
Update: Meteor > 0.9.1
Creating collections in Meteor is now:
Messages = new Mongo.Collection("Messages")
instead of:
Messages = new Meteor.Collection("Messages")

Categories