【问题标题】:Script won't run from ejs file in nodejs application脚本不会从 nodejs 应用程序中的 ejs 文件运行
【发布时间】:2018-08-26 20:17:31
【问题描述】:

我正在尝试使用 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>

【问题讨论】:

    标签: javascript html node.js chart.js ejs


    【解决方案1】:

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

    错的太多了,这将是一个很长的答案。这是您的两个主要误解:

    • 你试图让客户端执行服务器代码
    • 您运行异步代码并认为它​​会同步返回

    然后你的许多小错误:

    • 长度是属性,不是方法
    • 您必须导出一个方法才能使其在节点外部可用
    • 你使用for (i in rows),所以i是项目索引(我猜你得到它是因为你将它命名为i),然后你使用i.count
    • 在你的 SQL 中你放了SELECT COUNT(*) FROM 然后只使用.count 属性,我不确定没有AS count 它是否能工作

    在这一点上,我只能猜测您的 SQL 和 Chart 用法并没有更好,抱歉 :( 不过我会尽力为您指出正确的方向。

    识别客户端和服务器

    因此,首先,您需要从您的 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 和类似的系统。但首先我们需要解决另一个问题。

    异步代码

    异步是 Node 的一个非常重要的部分,真的,但我们不能在这里解决所有问题。你会有关键词,我让你做你的研究来驯服那部分。我将使用async/await 这样您在阅读代码时不会太受干扰,并使用util.promisify 来转换方法。您必须了解的事情:

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

    这是你的错误:

    • getNumMaps 中,您的return 在回调中。这个回调是在函数返回自己的结果后调用的,所以它只会返回 undefined
    • getMapAccessData 中你甚至都懒得返回任何东西,仍然未定义
    • getMapAccessNames,你终于回来了!但是由于 connection.query 是异步的,所以在函数已经返回 arr 后,您将把数据推送到您的数组中,所以它总是返回 []

    我会添加你执行相同请求的三倍,听起来很浪费;)所以,你知道你想最终将所有这些都包含在你的 Chart 实例中,我们不要将其拆分为 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 模块

    现在在您的路由处理程序中,您可以简单地调用您的 async 函数,然后 await 获取其结果:

    // 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 的引用,这是没有意义的
    • 以浏览器的方式添加chart.js,例如来自CDN
    • 在您的客户端 JS 中注入数据

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

    • 您在服务器端运行 SQL,这会为您提供一些数据
    • 您将此数据传递给您的 EJS 模板,该模板(仍然是服务器端)生成 HTML
    • 在此 HTML 中,您将注入服务器数据的字符串表示形式(使用 JSON.stringify
    • 服务器最终将生成的 HTML 发送给客户端
    • 客户端收到这个格式正确的HTML,在&lt;script&gt;中有一些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,
    })
    

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

    【讨论】:

    • PHP 的“阻塞”特性与运行 Flask 或 Django 的 Python 有何不同?
    • 不,完全一样,但询问者对 PHP 的了解比对 python 的了解要多;)
    • 好的,在这种情况下,对 PHP 的引用是无关紧要的。我的编辑是建议性的,但如果您想坚持单独挑选 PHP,我认为值得一票。对语言的批评很好,但(IMO)帖子不应该用来锻炼自己的爱好,尤其是当它与手头的主题无关时。
    • 当你在app.get()中写下一行:const data = await getChartData(),它不起作用,我在网上看到是因为'await'只能在异步函数中调用.解决此问题的最佳方法是什么?我考虑将 app.get() 中的所有内容放在一个大的异步方法中,但这看起来真的很难看
    • 我已经在hander之前添加了异步:app.get(..., async (req, res) => ...)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-20
    • 1970-01-01
    • 2014-07-24
    • 1970-01-01
    • 2015-05-10
    相关资源
    最近更新 更多