脚本不会从 nodejs 应用程序中的 ejs 文件运行
我正在尝试使用 nodejs、express、mysql 和 ejs 使网页显示图表,但我显然不了解 ejs/javascript 等的工作原理.我需要运行一个脚本来设置图表(来自 chart.js 模块),但它不输出任何类型的图表.
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.
我尝试了什么:
- 在脚本中放置 console.log() 消息以查看脚本中的代码是否存在问题.控制台没有输出,所以我确信脚本本身没有运行
- 将段落标签放在脚本上方的同一文件中,显示正常情况下,显示文件仍在访问中,只是脚本无法正常工作
下面的脚本不会运行:
<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 文件:
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>
推荐答案
这里有一堆误解: HTML 文件将提供给你的客户端,它会看到 <script>
标签,从 Web 服务器请求它们,然后执行代码.您不能期望 Web 浏览器在您的服务器上运行 SQL 查询),因此显然这不是您想要执行此 JS 代码的方式.
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:
- 你试图让客户端执行服务器代码
- 你运行异步代码并认为它会同步返回
然后你的许多小错误:
- 长度是一个属性,而不是一个方法
- 您必须导出一个方法才能在节点外部使用它
- 你使用
for (i in rows)
所以 i 是项目索引(我猜你得到它是因为你将它命名为 i),然后你使用i.count
- 在您的 SQL 中,您输入
SELECT COUNT(*) FROM
然后只需使用.count
属性,我不确定没有AS count代码>
- 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 usei.count
- in your SQL you put
SELECT COUNT(*) FROM
then just use.count
property, I'm not sure it will work withoutAS count
在这一点上,我只能猜测您的 SQL 和 Chart 用法并没有更好,抱歉 :( 不过我会尽力为您指出正确的方向.
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.
因此,首先,您需要从您的 Node.js 服务器执行此 JS 代码.通常的步骤是:
So, first of all, you need to execute this JS code from your Node.js server. The usual steps would be:
- 启动 Express 应用
- 配置 EJS 渲染
- 在您的路线中:
- 运行你的 sql 查询,获取一堆数据(你仍然是服务器端)
- 渲染您的 HTML,传递一些数据
- 现在在您的模板中,只需将您需要的内容从服务器注入到客户端
后续步骤的示例数据结构:
Sample data structure for the next steps:
/ +- app.js +- lib/ +- map-access-data.js +- views/ +- index.ejs +- map-access-chart.ejs +- stylesheets/ +- styles.css
服务器
所需的服务器依赖:
npm install express ejs mysql
,其余用于客户端(如chart
)The server
Required server dependencies:
npm install express ejs mysql
, the rest is for client (likechart
)// 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)
好的,这里你是服务器端的,你可以使用 MySQL 和类似的系统.但首先我们需要解决另一个问题.
OK, here you're server-side, you can use MySQL and similar systems. But first we need to address another issue.
异步是 Node 的一个非常重要的部分,真的,但我们不能在这里解决所有问题.你会有关键词,我让你做你的研究来驯服那部分.我将使用
async
/await
这样您在阅读代码时不会太受干扰,并使用util.promisify
来转换方法.您必须了解的事情: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, andutil.promisify
to transform the methods. The things you have to understand:connection.query
将查询远程服务器,在 Node 中它将异步完成,这意味着您不会立即得到任何结果,但您的代码也不会停止(或者它会被阻塞,这很糟糕)- 要表示异步操作,基本上有两种方式:
- 将回调函数传递给您的异步函数,该回调将在结果可用时立即调用;使用回调时,您无法返回任何有趣的内容
- 返回一个对象(称为 promise),它只是一个空包装器;然后稍后这个对象会突然包含结果,并且能够调用你将传递给它的
then
方法的函数;使用 Promise 时,您必须返回这些对象,这些对象代表您未来的数据,也是访问它的唯一方式
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
这是你的错误:
- 在
getNumMaps
中,您的return
在回调中.这个回调是在函数返回自己的结果后调用的,所以它只会返回 undefined - 在
getMapAccessData
中你甚至都懒得返回任何东西,仍然未定义 - 在
getMapAccessNames
中,你终于返回了一些东西!但是由于 connection.query 是异步的,所以在函数已经返回arr
之后,您将数据推送到您的数组中,所以它总是返回[]
- In
getNumMaps
, yourreturn
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 returnedarr
, so it always returns[]
我会添加你执行相同请求的三倍,听起来很浪费;)所以,你知道你想最终将所有这些都包含在你的 Chart 实例中,我们不要将其拆分为 4 个函数,它们都执行相同的查询,相反,我们将使用经过调整的格式构建单个结果.
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.
现在您需要能够从
app.js
中调用它,这意味着您需要:Now you need to be able to call it from
app.js
, which means you need to:- 导出:
// lib/map-access-data.js … // Export your function as default module.exports = getChartData
- 然后在您的路由处理程序中导入并使用它:
// app.js const getChartData = require('./lib/map-access-data)
这称为 CommonJS 模块
现在在您的路由处理程序中,您可以简单地调用您的 async 函数,然后 await 获取其结果:
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() })
生成 HTML
现在你的数据已经可用,你仍然是服务器端的,你现在必须为你的客户端生成有效的 HTML,目前看起来像:
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:
- 删除对
map-access-data.js
的引用,这是没有意义的 - 添加
chart.js
浏览器的方式,就像来自 CDN 一样 - 在你的客户端 JS 中注入数据
- 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
在这里,我认为您可以使用 Ajax 请求,而不是将真实数据直接注入 HTML,但我不知道 Chart,所以我会让您完成这部分.提供 JSON 数据的 Express 应用程序绝对是微不足道的,只需
res.send(data)
,然后在客户端执行一些 Ajax.让我们看一下将数据直接注入 HTML 以打破所有围墙的版本: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:- 在服务器端运行 SQL,为您提供一些数据
- 您将此数据传递给您的 EJS 模板,该模板(仍然是服务器端)生成 HTML
- 在此 HTML 中,您将注入服务器数据的字符串表示形式(使用
JSON.stringify
) - 服务器最终将生成的 HTML 发送给客户端
- 客户端收到这个格式正确的HTML,
<script>
里面有一些JS,运行一下,大家都很开心
- 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, })
现在,当您从终端运行
node app.js
并转到 http://localhost:8000 你应该看到结果.我想会有很多剩余的错误,但这将是一个更好的开始:)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 :)
相关文章