脚本不会从nodejs应用程序中的ejs文件运行

nic*_*111 1 html javascript ejs node.js chart.js

我正在尝试使用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>
Run Code Online (Sandbox Code Playgroud)

在该文件的第一个脚本中引用的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;
Run Code Online (Sandbox Code Playgroud)

此代码呈现的实际文件:

<!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>
Run Code Online (Sandbox Code Playgroud)

小智 8

这里有一堆误解:HTML文件将提供给您的客户端,它将看到<script>标签,从Web服务器请求它们,并执行代码.您不能指望Web浏览器在您的服务器上运行SQL查询),因此显然这不是您想要执行此JS代码的方式.

这么多都是错的,这将是一个很长的答案.以下是您的两个主要误解:

  • 您尝试使客户端执行服务器代码
  • 您运行异步代码并认为它​​将同步返回

然后你的许多小错误:

  • 长度是属性,而不是方法
  • 您必须导出一个方法,使其可以从节点外部使用
  • 你使用for (i in rows)所以我是项目索引(我想你得到了,因为你命名为我),然后你使用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
Run Code Online (Sandbox Code Playgroud)

服务器

必需的服务器依赖项: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)
Run Code Online (Sandbox Code Playgroud)

好的,这里你是服务器端,你可以使用MySQL和类似的系统.但首先我们需要解决另一个问题.

异步代码

异步是Node的一个非常重要的部分,实际上,我们无法在这里解决所有问题.您将拥有关键字,我让您进行研究以驯服该部分.我会使用async/ await所以你在阅读代码时不会太烦恼,并且util.promisify转换方法.你必须要了解的事情:

  • connection.query 将查询远程服务器,在Node中它将异步完成,这意味着你不会立即获得任何结果,但你的代码也不会被停止(或者它会阻塞,这很糟糕)
  • 要表示异步操作,基本上有两种方法:
    • 将回调函数传递给异步函数,一旦可用,就会调用该回调函数; 使用回调时,您无法返回任何有趣的内容
    • 返回一个对象(称为promise),它只是一个空包装器; 然后这个对象会突然包含结果,并且能够调用你传递给它的then方法的函数; 使用promises时,必须返回那些对象,这些对象是未来数据的表示,也是访问它的唯一方法
  • 当你使用promise时会有一个特定的语法async,它允许你wait使用承诺的数据,但你的异步函数仍然是异步的,这意味着它返回一个包装器,而不是你的实际结果,除非你wait也是

这是你的错误:

  • getNumMaps,你return的回调.在函数返回自己的结果后调用此回调方式,因此它将返回undefined
  • getMapAccessData你甚至懒得返回任何东西,仍未定义
  • getMapAccessNames,最后你回来了!但是当connection.query是异步时,你会在函数已经返回后将数据推送到数组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,
  }
}
Run Code Online (Sandbox Code Playgroud)

模块

好的,现在你有一个功能,只有服务器端,它可以满足你的需要.

现在你需要能够从中调用app.js,这意味着你需要:

  • 出口它:
// lib/map-access-data.js
…

// Export your function as default
module.exports = getChartData
Run Code Online (Sandbox Code Playgroud)
  • 然后导入并在路由处理程序中使用它:
// app.js
const getChartData = require('./lib/map-access-data)
Run Code Online (Sandbox Code Playgroud)

这称为CommonJS模块

现在在您的路由处理程序中,您只需调用异步函数,并等待其结果:

// app.js
…

app.get('/', async (req, res) => {
  // Your route handler
  const data = await getChartData()
})
Run Code Online (Sandbox Code Playgroud)

生成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:[{
…
Run Code Online (Sandbox Code Playgroud)

显然我们需要解决一些问题:

  • 删除map-access-data.js没有意义的引用
  • 添加chart.js浏览器的方式,就像从CDN那样
  • 在客户端JS中注入数据

在这里,我认为不是直接将真实数据注入HTML,而是可以使用Ajax请求,但我不知道Chart,所以我会让你做这部分.提供JSON数据的Express应用程序绝对是微不足道的res.send(data),然后在客户端执行一些Ajax.让我们看一下将数据直接注入HTML以打破所有问题的版本:

  • 服务器端运行SQL,为您提供一些数据
  • 您将此数据传递给EJS模板,该模板(仍然是服务器端)生成HTML
  • 在此HTML中,您将注入服务器数据的String表示形式(使用JSON.stringify)
  • Server最终将生成的HTML发送到客户端
  • 客户端收到这个格式良好的HTML,有一些JS <script>,运行它,每个人都很高兴
<!-- 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
…
Run Code Online (Sandbox Code Playgroud)
// 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,
})
Run Code Online (Sandbox Code Playgroud)

现在,当您node app.js从终端运行并转到http:// localhost:8000时,您应该看到结果.我想会有很多剩余的错误,但这将是一个更好的开始:)