脚本不会从nodejs应用程序中的ejs文件运行 [英] Script won't run from ejs file in nodejs application

查看:78
本文介绍了脚本不会从nodejs应用程序中的ejs文件运行的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用nodejs,express,mysql和ejs使网页显示图表,但我显然不了解ejs / javascript等的工作原理。我需要一个运行的脚本来设置一个图表(来自chart.js模块),但它没有输出任何类型的图表。



我尝试了什么:




  • 在脚本中放置console.log()消息,以查看脚本中的代码是否存在问题。控制台没有输出,所以我确信脚本本身没有运行

  • 将段落标签放在脚本上方的同一个文件中,正常显示
    ,显示该文件仍然被访问,只是脚本不能正常工作



下面的脚本将无法运行:

 < canvas id =myChartwidth =50%height =100px>< / canvas> 

< script scr =map-access-data.jstype =text / javascript>< / script>
< script id =map-populartype =text / javascript>
var Chart = require('chart');
var ctx = document.getElementById(myChart)。getContext(2d);
var myChart = new Chart(ctx,{
type:'bar',
data:{
labels:getMapAccessNames(),
数据集:[{
label:Map Selection Popularity,
data:getMapAccessData(),
backgroundColor:getMapColors(),
borderColor:getMapColors(),
borderWidth:1
}]
},
选项:{
比例:{
yAxes:[{
ticks:{
beginAtZero:true
}
}]
}
}
});
< / script>

在该文件的第一个脚本中引用的map-access-data.js文件:

  var mysql = require('mysql'); 

var connection = mysql.createConnection({
host:'localhost',
user:'admin',
password:'pass',
数据库:'db'
});

connection.connect();

函数getNumMaps(){
connection.query(SELECT NAME,COUNT(*)FROM map_access GROUP BY NAME ORDER BY NAME,function(err,rows){
return rows.length();
});
}

函数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);
}
});
}

函数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);
}
});
返回arr;
}

函数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>了解人们如何使用我们的应用< br />< / p>
<%include map-access-chart%>
< / body>
< / html>


解决方案

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



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




  • 您尝试使客户端执行服务器代码

  • 您运行异步代码并认为它​​将同步返回



那么你的许多小错误:




  • 长度是属性,而不是方法

  • 您必须导出方法以使其在节点外部可用

  • 你使用获取(我在行中)所以我是项目索引(我想你得到了它,因为你将它命名为i),然后你使用<$你放入SQL的c $ c> i.count

  • SELECT COUNT(*)FROM 然后只需使用 .count 属性,我不确定它是否可以在没有 AS计数的情况下工作



此时我只能猜测你的SQL和图表使用情况并不好,抱歉:(我会尽力指出你正确的方向。



识别客户端和服务器



所以,首先,你需要执行这个J来自Node.js服务器的S代码。通常的步骤是:




  • 启动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 ,其余为客户端(如图表

  // app.js 
const express = require('express')
const app = express()

//提供公共静态资产,如样式表和客户端JS
app.use(app.static('public'))

//配置EJS模板引擎
app.set('view engine','ejs')

//您的路线
app.get('/',(req) ,res)=> {
//您的路由处理程序
// req是客户端的请求
// res是服务器的响应
})

//启动Web服务器http:// localhost:8000
app.listen(8000)

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



异步代码



异步是Node的一个非常重要的部分,真的,但我们不能解决这里的一切。您将拥有关键字,我让您进行研究以驯服该部分。我将使用 async / 等待,这样您在阅读代码时就不会感到不安,并且 util.promisify 来转换方法。你必须要理解的事情:




  • connection.query 将查询远程服务器,在Node中它将异步完成,这意味着你不会立即获得任何结果,但你的代码也不会被停止(或者它会阻塞,这很糟糕)

  • 为了表示异步操作,你基本上有两种方法:


    • 将一个回调函数传递给你的异步函数,这个回调函数会在调用结果时立即调用可用的;当使用回调时,你无法返回任何有趣的内容。

    • 返回一个对象(称为 promise ),它只是一个空包装器;然后这个对象会突然包含结果,并且能够调用你已传递给然后方法的函数;当使用promises时,你必须返回那些对象,这些对象代表你未来的数据以及访问它的唯一方法


  • 当你使用promise时,有一个特定的语法叫做 async ,它允许你等待获取承诺的数据,但你的异步函数仍然是异步的,这意味着它会返回一个包装器,而不是你的实际结果,除非你等待也是



这是您的错误:




  • getNumMaps ,你的返回在回调中。函数返回自己的结果之后调用此回调方式,因此它将返回undefined

  • getMapAccessData 中你没有甚至懒得返回任何东西,仍然未定义

  • getMapAccessNames 中,最后你会返回一些东西!但是当connection.query是异步时,你会在函数已经返回 arr 之后将数据推送到你的数组,所以它总是返回 []



我会给你添加三次同样的请求,听起来很浪费;)所以,你知道你我想最终在你的图表实例中包含所有这些,让我们不要将它们分成4个函数,这些函数都执行相同的查询,我们将用适当的格式构建一个结果。



< pre class =lang-js prettyprint-override> // lib / map-access-data.js
const mysql = require('mysql')
const connection = mysql .createConnection(/ *你的配置在这里* /)

//获取promises而不是使用回调
const util = require('util')
const queryCallback = connection.query .bind(连接)//或者我们将遇到this
const queryPromise = util.promisify(queryCallback)//现在这返回一个我们可以等待的承诺

//我们的异步方法d,使用async关键字让节点知道我们可以在那里等待承诺
异步函数getChartData(){
const rows = await queryPromise(SELECT name,COUNT(*)AS count FROM map_access GROUP按名称ORDER BY名称)

//数组#map允许通过转换每个值来创建一个新数组
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 {
labels:names,
data:counts,
colors:colors,
}
}



模块



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



现在你需要能够调用它来自 app.js ,这意味着您需要:




  • 将其导出:



  // lib / map-access-data。 js 
...

//将您的函数导出为默认值
module.exports = getChartData




  • 然后导入并在路线处理程序中使用它:



  // app.js 
const getChartData = require('./ lib / map-access-data)

这叫做 CommonJS模块



现在在您的路线处理程序中,您只需调用异步函数,然后等待获取其结果:

  // app.js 
...

app.get( '/',async(req,res)=> {
//你的路线处理程序
const data = await getChartData()
})



生成HTML



现在您的数据已经可用,您仍然是服务器端,您现在必须为您的客户端生成有效的HTML ,目前看起来像:

 <!DOCTYPE html> 
< html>
...一堆HTML ...

< p>了解人们如何使用我们的应用< br />< / p>
< canvas id =myChartwidth =50%height =100px>< / canvas>

<! - 不!它不是客户端JS,它是服务器JS,客户端无法下载它 - >
< script scr =map-access-data.jstype =text / javascript>< / script>

< script id =map-populartype =text / javascript>
var Chart = require('chart'); //不!你不能*要求来自客户*
var ctx = document.getElementById(myChart)。getContext(2d);
var myChart = new Chart(ctx,{
type:'bar',
data:{
labels:getMapAccessNames(),// NO!你不能调用服务器来自客户的方法
数据集:[{
...

显然我们需要修复一些事情:




  • 删除对的引用map-access-data.js 这没什么意义

  • 添加 chart.js 浏览器的方式,如CDN

  • 在客户端内部注入数据JS



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




  • 服务器端你运行你r SQL,它为您提供一些数据

  • 您将此数据传递给EJS模板,该模板(仍然是服务器端)生成HTML

  • 在此HTML将注入服务器数据的字符串表示形式(使用 JSON.stringify

  • 服务器最终将生成的HTML发送到客户端

  • 客户收到这个格式正确的HTML,其中一些JS在< script> ,运行它,每个人都很高兴



 <! -  views / map-access-chart.ejs  - > 
< canvas id =myChartwidth =50%height =100px>< / canvas>

< script src =https://cdnjs.cloudflare.com/ajax/libs/Chart.js/1.0.2/Chart.min.js>< / script>
< script id =map-populartype =text / javascript>
var ctx = document.getElementById(myChart)。getContext(2d);
var myChart = new Chart(ctx,{
type:'bar',
data:{
labels:<% - JSON.stringify(data.labels)%> ;,
数据集:[{
label:Map Selection Popularity,
data:<% - JSON.stringify(data.data)%>,
backgroundColor: <% - JSON.stringify(data.colors)%>,
borderColor:<% - JSON.stringify(data.colors)%>,
borderWidth:1
...



  // app.js 
...

//您的路由处理程序
const data = await getChartData()
//使用变量'title'和'data'渲染'index.ejs'可用
res.render('index',{
title:'Page title',
data:data,
})

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


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

这篇关于脚本不会从nodejs应用程序中的ejs文件运行的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆