我试图使用nodejs、express、mysql和ejs使网页显示图表,但是我显然不了解ejs/javascript等的工作原理。我需要一个脚本来设置一个图表(来自chart.js模块),但它没有输出任何类型的图表。
我试过的:
在脚本中放置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文件:
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;
此代码呈现的实际文件:
<!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代码的方式。
这么多是错误的,这将是一个长期的答案。以下是你的两个主要误解:
您尝试使客户端执行服务器代码
运行异步代码并认为它将同步返回
那么你的许多小错误:
长度是一个属性,而不是一个方法
必须导出一个方法,才能使它在节点中的外部可用
你使用for (i in rows)
所以我是项目索引(我猜你是因为把它命名为i而得到的),然后你使用i.count
在您的SQL中输入SELECT COUNT(*) FROM
然后使用.count
属性,如果没有AS count
属性,我不确定它是否能工作
在这一点上,我只能猜测您的SQL和图表使用情况并没有更好,对不起:(不过,我会尽量给您指出正确的方向)。
标识客户端和服务器
因此,首先,需要从Node.JS服务器执行此JS代码。通常的步骤是:
启动Express应用程序
配置EJS呈现
在您的路线上:
运行sql查询,获取一堆数据(您仍然是服务器端的)
呈现HTML,传递一些数据
现在在模板中,只需将所需的内容从服务器注入到客户端
利润
下一步的示例数据结构:
/
+- app.js
+- lib/
+- map-access-data.js
+- views/
+- index.ejs
+- map-access-chart.ejs
+- stylesheets/
+- styles.css
服务器
所需的服务器依赖项:
npm install express ejs mysql
,其余为客户端(如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)
好的,这里是服务器端,您可以使用MySQL和类似的系统。但首先我们需要解决另一个问题。
异步代码
异步是节点的一个非常重要的部分,真的,但是我们不能在这里解决所有问题。你会有关键词,我让你做你的研究驯服那部分。我将使用
async
/await
以便您在阅读代码时不会受到太多干扰,并使用util.promisify
来转换方法。你必须明白的是:connection.query
将查询远程服务器,在节点中它将异步完成,这意味着您不会立即得到任何结果,但您的代码也不会停止(或者它将被阻塞,这很糟糕)要表示异步操作,基本上有两种方法:
将回调函数传递给异步函数,此回调将在结果可用时立即调用;使用回调时,不能返回任何感兴趣的内容
返回一个对象(称为promise),它只是一个空包装器;然后,这个对象将突然包含结果,并能够调用传递给它的
then
方法的函数;当使用promises时,必须返回那些对象,它们是未来数据的表示,是访问它的唯一方法当您使用promise时,有一个称为
async
的特定语法允许您为promised数据wait
,但是您的异步函数仍然是异步的,这意味着它返回一个包装器,而不是实际的结果,除非您也为它wait
以下是您的错误:
在
getNumMaps
中,您的return
位于回调中。这个回调是在函数返回自己的结果后调用的,因此它只返回未定义的在
getMapAccessData
中,您甚至没有返回任何内容,仍然没有定义在
getMapAccessNames
中,您终于返回了一些内容!但是由于connection.query是异步的,所以在funtion已经返回arr
之后,您将数据推送到数组中,因此它总是返回[]
我将添加您执行三次相同的请求,听起来很浪费;)因此,您知道您希望最终将所有这些都包含在图表实例中,让我们不要将其拆分为4个执行相同查询的函数,而是使用适当的格式生成单个结果。
// 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,
}
}
模块
好的,现在你有了一个功能,只提供服务器端,这给了你所需要的。
现在您需要能够从
app.js
调用它,这意味着您需要:导出它:
// lib/map-access-data.js
…
// Export your function as default
module.exports = getChartData
然后导入并在路由处理程序中使用它:
// app.js
const getChartData = require('./lib/map-access-data)
这叫做CommonJS模块
现在,在路由处理程序中,您可以简单地调用异步函数,并等待其结果:
// app.js
…
app.get('/', async (req, res) => {
// Your route handler
const data = await getChartData()
})
生成HTML
现在您的数据可用了,您仍然是服务器端的,您现在必须为您的客户端生成有效的HTML,它当前看起来像:
<!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:[{
…
显然我们需要解决一些问题:
删除对
map-access-data.js
的引用,这毫无意义添加浏览器的方式,就像从CDN
在客户端JS中注入数据
在这里,我认为您可以使用Ajax请求,而不是直接将真实数据注入HTML,但是我不知道Chart,所以我将让您完成这一部分。一个提供JSON数据的Express应用程序非常简单,只需
chart.js
,然后在客户端执行一些Ajax。让我们看看您将数据直接注入HTML以打破所有障碍的版本:服务器端你运行你的SQL,它给你一些数据
将这些数据传递到EJS模板,该模板(仍然是服务器端)生成HTML
在这个HTML中,您将注入服务器数据的字符串表示(使用
res.send(data)
)服务器最终将生成的HTML发送到客户端
客户端接收到这个格式良好的HTML,并在
JSON.stringify
中运行一些JS,每个人都很高兴<!-- 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,
})
现在,当您从终端运行
<script>
并转到http://localhost:8000时,您应该会看到结果。我想还有很多错误,但那将是一个更好的开始:)
关于javascript - 脚本无法从Node.js应用程序中的EJS文件运行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49343185/