var request = require('request');
var cheerio = require('cheerio');
app.get('/getMenu', function (req, res) {
request("http://www.kopo.ac.kr/kangseo/content.do?menu=262", function(err, res2, body) {
const $ = cheerio.load(body);
var menus = [];
for(var i = 0; i < 5; i++) {
menus.push($('td')[i*4+2].children[1].children[0].data.replace(/\n/g, "").split(","));
}
// console.log(menus);
res.send(menus);
})
})
// const를 사용해 URL 요청을 option으로 따로 분리할 수 있다.
app.get('/getMenu', function (req, res) {
const options = {
url:'http://www.kopo.ac.kr/kangseo/content.do?menu=262',
method: 'GET'
}
request.get(options, function(err,httpResponse,body){
const $ = cheerio.load(body);
var menus = [];
for(var i = 0; i < 5; i++) {
menus.push($('td')[i*4+2].children[1].children[0].data.replace(/\n/g, "").split(","));
}
res.send(menus);
});
})
따라서
app.js
var express = require('express');
var app = express();
var http = require('http');
var server = http.createServer(app).listen(80);
console.log("Server is running...")
var mysql = require('mysql');
var request = require('request');
var cheerio = require('cheerio');
const ExcelJS = require('exceljs');
var bodyParser = require("body-parser");
// extended : 객체 안의 객체를 파싱 가능하게 하려면 true로 해야한다.
app.use(bodyParser.urlencoded({limit: '50mb', extended: true}));
app.use(bodyParser.json({limit: '50mb'}));
app.get('/', function (req, res) {
res.sendfile("main.html");
});
app.get('/main', function (req, res) {
res.sendfile("main.html");
});
app.get('/getMenu', function (req, res) {
const options = {
url:'http://www.kopo.ac.kr/kangseo/content.do?menu=262',
method: 'GET'
}
request.get(options, function(err,httpResponse,body){
const $ = cheerio.load(body);
var menus = [];
for(var i = 0; i < 5; i++) {
menus.push($('td')[i*4+2].children[1].children[0].data.replace(/\n/g, "").split(","));
}
res.send(menus);
});
})
main.html
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<span id="menuArea"></span>
<br />
<input type="button" id="drawGraph" value="그래프그리기">
<input type="button" id="downloadExcel" value="엑셀다운로드">
<div id="graph" style="width: 800px; height: 500px;"></div>
</body>
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
var week = ['월','화','수','목','금'];
var menus;
$.ajax({
url : '/getMenu',
type : 'GET',
data : {
},
success : function (data) {
menus = data; // 바깥 배열이 요일별로 분리, 안쪽 배열이 메뉴별로 분리된 매트릭스
for(var i = 0; i < menus.length; i++) {
// 바깥 배열 길이만큼 돌며 요일을 출력
$("#menuArea").append(week[i] + "요일<br>");
// console.log(week[i] + "요일<br>");
// 셀렉트 박스를 만들기 위해 appendingText에 한 번에 담아서 출력하기
for(var j = 0; j < menus[i].length; j++) {
var appendingText = "";
// i 요일의 j 번째 메뉴를 출력
// appendingText += menus[i][j] + `<select id = "s${i}_${j}"`;
appendingText += menus[i][j] + `<select name="s${i}">`;
// select 의 수량을 10개 만든다.
for (var k = 0; k < 11; k++) {
appendingText += `<option value = ${k}>${k}</option>`;
}
appendingText += `<select><br>`;
$("#menuArea").append(appendingText);
// console.log(appendingText);
}
$("#menuArea").append("<br>");
}
}
});
// 그래프 그리기
$("#drawGraph").click(function() {
});
// 엑셀 다운로드
$("#downloadExcel").click(function() {
});
</script>
</html>
그래프
이미 html에 가져온 데이터만을 이용해 자바스크립트로 그려낼 것이기 때문에 main.html 안에서 해결한다.
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="graph" style="width: 800px; height: 500px;"></div>
</body>
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
var week = ['월','화','수','목','금'];
var menus;
// 그래프 그리기
$("#drawGraph").click(function() {
var averageArr = [];
console.log($("#count").val());
// 월 ~ 금 반복문
for (var i = 0; i < menus.length; i++) {
var dailyAverage = 0;
// 각 요일별 메뉴의 반복문
var dailyScore = $(`select[name=s${i}]`);
for (var j = 0; j < dailyScore.length; j++) {
// 각 메뉴별 1 ~ 10 점 중 어떤 것이 선택되었는지를 확인
for (var k = 0; k < 11; k++) {
if (dailyScore[j][k].selected) {
dailyAverage +=k; // 각 요일별 메뉴들의 점수의 총 합
}
}
}
dailyAverage = dailyAverage / dailyScore.length // 각 요일별 메뉴의 점수들의 총 합을 메뉴의 개수로 나눠 평균을 계산
averageArr.push([week[i], dailyAverage]);
console.log(averageArr);
}
google.charts.load('current', {'packages':['corechart', 'line']});
google.charts.setOnLoadCallback(drawCurveTypes);
function drawCurveTypes() {
var data = new google.visualization.DataTable();
data.addColumn('string', '요일');
data.addColumn('number', '점수');
data.addRows(averageArr);
var options = {
hAxis: {
title: '요일'
},
vAxis: {
title: '만족도'
},
series: {
1: {curveType: 'function'}
}
};
var chart = new google.visualization.LineChart(document.getElementById('graph'));
chart.draw(data, options);
}
});
// 엑셀 다운로드
$("#downloadExcel").click(function() {
});
</script>
</html>
엑셀
엑셀로 만들어주기 위해 exceljs를 사용한다.
app.js (엑셀 파일을 만드는 라우터)
const ExcelJS = require('exceljs');
// ajax로 데이터 넘겨받아 엑셀형식으로 만들기
app.post('/getExcel', async function (req, res) {
var menus = req.body.menus;
var scores = req.body.scores;
const workbook = new ExcelJS.Workbook();
const sheet = workbook.addWorksheet('menu');
const alpahbet = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'];
const dayOfTheWeek = ['월','화','수','목','금','토','일'];
var menusMaxLength = 0;
var roundedAverage = [];
// 월, 화, 수, 목, 금을 도는 for문
for (var i = 0; i < menus.length; i++) {
sheet.getCell(`${alpahbet[i*2]}1`).value = dayOfTheWeek[i]; // 1행에 월, 화, 수, 목, 금
scores[i] = scores[i].map( str => parseInt(str) ); // str to integer (점수 array 형변환)
// 각 요일별 메뉴의 개수만큼 도는 for문 (각 요일의 메뉴와 메뉴별 점수를 엑셀에 삽입한다.)
for (var j = 0; j < menus[i].length; j++) {
sheet.getCell(`${alpahbet[i*2]}${j+2}`).value = menus[i][j].trim();
sheet.getCell(`${alpahbet[i*2+1]}${j+2}`).value = scores[i][j];
}
// 각 요일별 메뉴의 평균 점수를 계산하고, 이를 배열에 담아둔다.
// reduce 참고 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
// acc : 누적값, (,0 : 초기값), curr : 현재값... 그러니까 파이썬 식으로 표현하면 sum = sum + curr 이런거다.
const sum = scores[i].reduce( ( accumulator, currentValue ) => accumulator + currentValue, 0);
const average = sum / menus[i].length;
roundedAverage[i] = ( Math.round(average*10) ) / 10; // 소수점 첫째 자리까지 표현
// 메뉴의 길이 중 최대로 긴 값의 크기를 구해서 저장한다. (요일마다 메뉴의 길이가 다를 수 있으니까.)
if (menus[i].length > menusMaxLength) menusMaxLength = menus[i].length;
}
// 요일마다 메뉴의 길이가 다른 경우가 있기 때문에 위에서 메뉴를 모두 채우는 동안 배열에 저장해둔 다음 한 번에 출력한다.
for (var i = 0; i < menus.length; i++) {
sheet.getCell(`${alpahbet[i*2]}${menusMaxLength+3}`).value = '평균';
sheet.getCell(`${alpahbet[i*2+1]}${menusMaxLength+3}`).value = roundedAverage[i]; // 계산한 평균을 입력
}
// await : 파일이 다 만들어 질 때까지 기다려준다. (callback 함수와 같은 기능. 최근에 생겨난 함수다.)
await workbook.xlsx.writeFile('menu.xlsx');
// 엑셀 다 만들었다고 클라이언트에 응답을 보냄.
res.send("ok");
});
/getExcel 라우터가 main.html에서 보낸 ajax request를 받아서 body에 실어 보낸 데이터를 분해, 엑셀 파일로 만든다.
exceljs를 사용하며, sheet.getCell();을 통해 각 셀에 데이터를 삽입한다.
주의 : 이 때 각 요일별 메뉴의 개수가 달라지는 주가 존재한다. 따라서 최대값을 구한 다음 마지막 줄 최종 평균은 위 for문이 끝난 다음 다른 for문을 통해 구현한다.
// 월, 화, 수, 목, 금을 도는 for문
for (var i = 0; i < menus.length; i++) {
sheet.getCell(`${alpahbet[i*2]}1`).value = dayOfTheWeek[i]; // 1행에 월, 화, 수, 목, 금
scores[i] = scores[i].map( str => parseInt(str) ); // str to integer (점수 array 형변환)
// 각 요일별 메뉴의 개수만큼 도는 for문 (각 요일의 메뉴와 메뉴별 점수를 엑셀에 삽입한다.)
for (var j = 0; j < menus[i].length; j++) {
sheet.getCell(`${alpahbet[i*2]}${j+2}`).value = menus[i][j].trim();
sheet.getCell(`${alpahbet[i*2+1]}${j+2}`).value = scores[i][j];
}
// 각 요일별 메뉴의 평균 점수를 계산하고, 이를 배열에 담아둔다.
// reduce 참고 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
// acc : 누적값, (,0 : 초기값), curr : 현재값... 그러니까 파이썬 식으로 표현하면 sum = sum + curr 이런거다.
const sum = scores[i].reduce( ( accumulator, currentValue ) => accumulator + currentValue, 0);
const average = sum / menus[i].length;
roundedAverage[i] = ( Math.round(average*10) ) / 10; // 소수점 첫째 자리까지 표현
// 메뉴의 길이 중 최대로 긴 값의 크기를 구해서 저장한다. (요일마다 메뉴의 길이가 다를 수 있으니까.)
if (menus[i].length > menusMaxLength) menusMaxLength = menus[i].length;
}
// 요일마다 메뉴의 길이가 다른 경우가 있기 때문에 위에서 메뉴를 모두 채우는 동안 배열에 저장해둔 다음 한 번에 출력한다.
for (var i = 0; i < menus.length; i++) {
sheet.getCell(`${alpahbet[i*2]}${menusMaxLength+3}`).value = '평균';
sheet.getCell(`${alpahbet[i*2+1]}${menusMaxLength+3}`).value = roundedAverage[i]; // 계산한 평균을 입력
}
마찬가지로 반올림의 자리수를 따로 지정할 수 없어 round함수를 사용하기 전, 10을 곱하고 다시 10으로 나누는 것에 주목한다. 만약 소수점 둘째 자리까지 남기고 싶다면 100을 곱한 다음 100으로 나누면 된다.
roundedAverage[i] = ( Math.round(average*10) ) / 10; // 소수점 첫째 자리까지 표현
await
마지막으로 콜백함수와 동일한 기능을 하지만, 기존 콜백함수 구현보다 훨씬 짧고 간결한 await가 있다. 단, 나온지 얼마 안 되어서 지원되지 않는 라이브러리들이 있을 수 있다.
// await : 파일이 다 만들어 질 때까지 기다려준다. (callback 함수와 같은 기능. 최근에 생겨난 함수다.)
await workbook.xlsx.writeFile('menu.xlsx');
기존의 콜백함수 형식
// 콜백함수
res.sendFile(__dirname + '/menu.xlsx', function(err) { // 파일 보내기가 완료 되면
fs.unlinkSync(__dirname + '/menu.xlsx'); // 파일을 삭제해라.
});
main.html
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<span id="menuArea"></span>
<br />
<input type="button" id="drawGraph" value="그래프그리기">
<input type="button" id="downloadExcel" value="엑셀다운로드">
<div id="graph" style="width: 800px; height: 500px;"></div>
</body>
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
var week = ['월','화','수','목','금'];
var menus;
$.ajax({
url : '/getMenu',
type : 'GET',
data : {
},
success : function (data) {
... 생략
}
});
// 그래프 그리기
$("#drawGraph").click(function() {
});
// 엑셀 다운로드
$("#downloadExcel").click(function() {
var scores = [];
for (var i = 0; i < menus.length; i++) {
scores[i] = [];
var dailyScore = $(`select[name=s${i}]`);
for (var j = 0; j < dailyScore.length; j++) {
for (var k = 0; k < 11; k++) {
if (dailyScore[j][k].selected) {
scores[i].push(k);
}
}
}
}
console.log(scores);
console.log(menus);
$.ajax({
url : '/getExcel',
type : 'POST',
data : {
scores : scores,
menus : menus
},
success : function (res) {
console.log(res);
location.href = '/getExcelFile';
}
});
});
</script>
</html>
ajax로 /getExcel 라우터에 요청을 보낸 다음, 라우터가 파일을 다 만들었다고 응답을 보내면, success에서 다시 /getExcelFile 라우터에 요청을 보내 엑셀을 다운로드 하게 된다. 즉, 엑셀을 만드는 라우터(/getExcel)와 엑셀 파일을 전송하는 라우터(/getExcelFile)를 클라이언트의 html 파일 내의 자바스크립트 로직이 연결을 하게 된다는 것을 알 수 있다.
app.js (엑셀 파일을 전송하는 라우터)
// 위에서 파일 만들기가 완료되었다는 응답이 가면 ajax가 이쪽 라우터로 이동시켜준다.
app.get('/getExcelFile', function (req, res) {
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
res.setHeader("Content-Disposition", "attachment; filename=menu.xlsx");
var fs = require('fs');
// 콜백함수
res.sendFile(__dirname + '/menu.xlsx', function(err) { // 파일 보내기가 완료 되면
fs.unlinkSync(__dirname + '/menu.xlsx'); // 파일을 삭제해라.
});
});
전체 코드
app.js
var express = require('express');
var app = express();
var http = require('http');
var server = http.createServer(app).listen(80);
console.log("Server is running...")
var mysql = require('mysql');
var request = require('request');
var cheerio = require('cheerio');
const ExcelJS = require('exceljs');
// var connection = mysql.createConnection({
// host: 'localhost',
// port: 3308,
// user:'root',
// password: '1234',
// database: 'javascript'
// });
var bodyParser = require("body-parser");
// extended : 객체 안의 객체를 파싱 가능하게 하려면 true로 해야한다.
app.use(bodyParser.urlencoded({limit: '50mb', extended: true}));
app.use(bodyParser.json({limit: '50mb'}));
app.get('/', function (req, res) {
res.sendfile("main.html");
});
app.get('/main', function (req, res) {
res.sendfile("main.html");
});
// app.get('/getMenu', function (req, res) {
// request("http://www.kopo.ac.kr/kangseo/content.do?menu=262", function(err, res2, body) {
// const $ = cheerio.load(body);
// var menus = [];
//
// for(var i = 0; i < 5; i++) {
// menus.push($('td')[i*4+2].children[1].children[0].data.replace(/\n/g, "").split(","));
// }
// // console.log(menus);
//
// res.send(menus);
// })
// })
app.get('/getMenu', function (req, res) {
const options = {
url:'http://www.kopo.ac.kr/kangseo/content.do?menu=262',
method: 'GET'
}
request.get(options, function(err,httpResponse,body){
const $ = cheerio.load(body);
var menus = [];
for(var i = 0; i < 5; i++) {
menus.push($('td')[i*4+2].children[1].children[0].data.replace(/\n/g, "").split(","));
}
res.send(menus);
});
})
// ajax로 데이터 넘겨받아 엑셀형식으로 만들기
app.post('/getExcel', async function (req, res) {
var menus = req.body.menus;
var scores = req.body.scores;
const workbook = new ExcelJS.Workbook();
const sheet = workbook.addWorksheet('menu');
const alpahbet = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'];
const dayOfTheWeek = ['월','화','수','목','금','토','일'];
var menusMaxLength = 0;
var roundedAverage = [];
// 월, 화, 수, 목, 금을 도는 for문
for (var i = 0; i < menus.length; i++) {
// console.log(`${alpahbet[i*2]}1 = ${dayOfTheWeek[i]}`);
sheet.getCell(`${alpahbet[i*2]}1`).value = dayOfTheWeek[i]; // 1행에 월, 화, 수, 목, 금
scores[i] = scores[i].map( str => parseInt(str) ); // str to integer (점수 array 형변환)
// 각 요일별 메뉴의 개수만큼 도는 for문 (각 요일의 메뉴와 메뉴별 점수를 엑셀에 삽입한다.)
for (var j = 0; j < menus[i].length; j++) {
// console.log(`${alpahbet[i*2]}${j+2} = ${menus[i][j].trim()}`);
// console.log(`${alpahbet[i*2+1]}${j+2} = ${scores[i][j]}`);
sheet.getCell(`${alpahbet[i*2]}${j+2}`).value = menus[i][j].trim();
sheet.getCell(`${alpahbet[i*2+1]}${j+2}`).value = scores[i][j];
}
// 각 요일별 메뉴의 평균 점수를 계산하고, 이를 배열에 담아둔다.
// reduce 참고 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
// acc : 누적값, (,0 : 초기값), curr : 현재값... 그러니까 파이썬 식으로 표현하면 sum = sum + curr 이런거다.
const sum = scores[i].reduce( ( accumulator, currentValue ) => accumulator + currentValue, 0);
const average = sum / menus[i].length;
roundedAverage[i] = ( Math.round(average*10) ) / 10; // 소수점 첫째 자리까지 표현
// 메뉴의 길이 중 최대로 긴 값의 크기를 구해서 저장한다. (요일마다 메뉴의 길이가 다를 수 있으니까.)
// console.log(`${alpahbet[i*2]}${menus[i].length+3} = 평균`);
if (menus[i].length > menusMaxLength) menusMaxLength = menus[i].length;
}
// 요일마다 메뉴의 길이가 다른 경우가 있기 때문에 위에서 메뉴를 모두 채우는 동안 배열에 저장해둔 다음 한 번에 출력한다.
for (var i = 0; i < menus.length; i++) {
sheet.getCell(`${alpahbet[i*2]}${menusMaxLength+3}`).value = '평균';
sheet.getCell(`${alpahbet[i*2+1]}${menusMaxLength+3}`).value = roundedAverage[i]; // 계산한 평균을 입력
}
// await : 파일이 다 만들어 질 때까지 기다려준다. (callback 함수와 같은 기능. 최근에 생겨난 함수다.)
await workbook.xlsx.writeFile('menu.xlsx');
// 엑셀 다 만들었다고 클라이언트에 응답을 보냄.
res.send("ok");
});
// 위에서 파일 만들기가 완료되었다는 응답이 가면 ajax가 이쪽 라우터로 이동시켜준다.
app.get('/getExcelFile', function (req, res) {
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
res.setHeader("Content-Disposition", "attachment; filename=menu.xlsx");
var fs = require('fs');
// 콜백함수
res.sendFile(__dirname + '/menu.xlsx', function(err) { // 파일 보내기가 완료 되면
fs.unlinkSync(__dirname + '/menu.xlsx'); // 파일을 삭제해라.
});
});
main.html
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<span id="menuArea"></span>
<br />
<input type="button" id="drawGraph" value="그래프그리기">
<input type="button" id="downloadExcel" value="엑셀다운로드">
<div id="graph" style="width: 800px; height: 500px;"></div>
</body>
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
var week = ['월','화','수','목','금'];
var menus;
$.ajax({
url : '/getMenu',
type : 'GET',
data : {
},
success : function (data) {
menus = data; // 바깥 배열이 요일별로 분리, 안쪽 배열이 메뉴별로 분리된 매트릭스
for(var i = 0; i < menus.length; i++) {
// 바깥 배열 길이만큼 돌며 요일을 출력
$("#menuArea").append(week[i] + "요일<br>");
// console.log(week[i] + "요일<br>");
// 셀렉트 박스를 만들기 위해 appendingText에 한 번에 담아서 출력하기
for(var j = 0; j < menus[i].length; j++) {
var appendingText = "";
// i 요일의 j 번째 메뉴를 출력
// appendingText += menus[i][j] + `<select id = "s${i}_${j}"`;
appendingText += menus[i][j] + `<select name="s${i}">`;
// select 의 수량을 10개 만든다.
for (var k = 0; k < 11; k++) {
appendingText += `<option value = ${k}>${k}</option>`;
}
appendingText += `<select><br>`;
$("#menuArea").append(appendingText);
// console.log(appendingText);
}
$("#menuArea").append("<br>");
}
}
});
// 그래프 그리기
$("#drawGraph").click(function() {
var averageArr = [];
console.log($("#count").val());
// 월 ~ 금 반복문
for (var i = 0; i < menus.length; i++) {
var dailyAverage = 0;
// 각 요일별 메뉴의 반복문
var dailyScore = $(`select[name=s${i}]`);
for (var j = 0; j < dailyScore.length; j++) {
// 각 메뉴별 1 ~ 10 점 중 어떤 것이 선택되었는지를 확인
for (var k = 0; k < 11; k++) {
if (dailyScore[j][k].selected) {
dailyAverage +=k; // 각 요일별 메뉴들의 점수의 총 합
}
}
}
dailyAverage = dailyAverage / dailyScore.length // 각 요일별 메뉴의 점수들의 총 합을 메뉴의 개수로 나눠 평균을 계산
averageArr.push([week[i], dailyAverage]);
console.log(averageArr);
}
google.charts.load('current', {'packages':['corechart', 'line']});
google.charts.setOnLoadCallback(drawCurveTypes);
function drawCurveTypes() {
var data = new google.visualization.DataTable();
data.addColumn('string', '요일');
data.addColumn('number', '점수');
data.addRows(averageArr);
var options = {
hAxis: {
title: '요일'
},
vAxis: {
title: '만족도'
},
series: {
1: {curveType: 'function'}
}
};
var chart = new google.visualization.LineChart(document.getElementById('graph'));
chart.draw(data, options);
}
});
// 엑셀 다운로드
$("#downloadExcel").click(function() {
var scores = [];
for (var i = 0; i < menus.length; i++) {
scores[i] = [];
var dailyScore = $(`select[name=s${i}]`);
for (var j = 0; j < dailyScore.length; j++) {
for (var k = 0; k < 11; k++) {
if (dailyScore[j][k].selected) {
scores[i].push(k);
}
}
}
}
console.log(scores);
console.log(menus);
$.ajax({
url : '/getExcel',
type : 'POST',
data : {
scores : scores,
menus : menus
},
success : function (res) {
console.log(res);
location.href = '/getExcelFile';
}
});
});
</script>
</html>