Re:SALOON & VBA

Node.js版 読書履歴管理システム 再構築

10連休が到来(僕にとっては襲来)しました。
現在、就業中(ありがたいことに失業中ではない)ので、お休みはソコソコ嬉しいですが、
永過ぎる休みは、反って苦痛になったりもします。
何故なら、閑。テレビでさえも観たい番組がない。
歳をとるということはそういうことです。

で、老齢フリーランスSEとしては、
去年の黄金連休は、失業開始にあたり、腰を据えて
今まで出来なかった、Node.js の学習をしていました。

で、今回、PCを変えてしまったので、Node.js環境が失われたこともあり
復習がてら、再構築を試みました。
凄いもので、1年もすると、まったく初心者に戻っています。
javascript なので読めはしますが、特有の仕組みは全く覚えていない。

また、一から学習です。
今年、参考にさせていたたいたサイト↓
思考の葉 Node.js – ExpressでHello world

で復習で作成するのは、MySQL のデータがあるので
またまた、去年と同じ読書履歴管理システムです。
但し、できるだけ簡単にしたかったので、一から再度(過去のソースの引用はせず)、作り直しました。
なので、低機能版にはなってます。

さて手順ですが、
①Node.jsのインストール
https://nodejs.org/ja/download/
node-v10.15.3-x64.msiを使用
手順は割愛(画面の指示に従うのみ)



②Expressのインストール(コマンドプロンプト)
npm が入っているので、以下の打ち込みのみ
(daresoreは、仮のユーザーID,node_booklogは、任意のプロジェクト名)

C:\Users\daresore>npm install express-generator -g

C:\Users\daresore>express -e node_booklog

C:\Users\daresore>cd node_booklog

C:\Users\daresore\node_booklog>npm init

C:\Users\daresore\node_booklog>npm install --save express ejs

C:\Users\daresore\node_booklog>npm install --save mysql

C:\Users\daresore\node_booklog>npm install cookie-parser

C:\Users\daresore\node_booklog>npm install morgan

順番は、違っているかも知れません。実は、結構試行錯誤したので・・・(汗)

③起動確認
C:\Users\daresore\node_booklog>node app.js

http://localhost:3000/

④停止
ctlr + C

⑤修正・追加
自動で作成されたソースを修正(app.js,index.js,index.ejs)
そして、追加(add.ejs、edit.ejs)





潜在(?)バグはかなりあると思います(テスト不十分)。取りあえず動いたのレベル。
でも、これだけで、サーバーサイドJSのシステムができるのですから・・・大したものです。
MySQLの設定は割愛(HTA版で作成したものをそのまま使用)

■./app.js

const createError = require('http-errors');
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
const indexRouter = require('./routes/index');
const app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
global.page = 1;
// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};
  // render the error page
  res.status(err.status || 500);
  res.render('error');
});
app.listen(3000, () => {
    console.log('start')
})



■./routes/index.js

const express = require('express');
const mysql = require('mysql');
const { check, validationResult } = require('express-validator/check');
const router = express.Router();
const mysql_setting = {
  host: '127.0.0.1',
  user: 'root',
  password: 'password',
  database: 'test'
}
//localhost:3000/
router.get('/', function (req, res, next) {
  const connection = mysql.createConnection(mysql_setting);
  connection.connect();
  const page = req.query.page;
  connection.query('SELECT isbn13,bookname,coverimg FROM booklog ORDER BY getdate DESC', function (error, results, fields) {
    if (error) throw error;
    res.render('index', { content: results, page: page });
  });
  connection.end();
});
//localhost:3000/add
router.get('/add', function (req, res, next) {
  res.render('add');
});
//localhost:3000/addへのPOST
router.post('/add', (req, res, next) => {
  const errors = validationResult(req);
  const isbn13      = req.body.isbn13;
  const isbn10      = req.body.isbn10;
  const bookname    = req.body.bookname;
  const author      = req.body.author;
  const publisher   = req.body.publisher;
  const genre       = req.body.genre;
  const ownership   = req.body.ownership;
  var   purchase    = null;
  if (req.body.purchase !== "") {
    purchase = req.body.purchase;
  }
  const library     = req.body.library;
  const overview    = req.body.overview;
  const impressions = req.body.impressions;
  const state       = req.body.state;
  const coverimg    = req.body.coverimg;
  var getdate       = null;
  if (req.body.getdate !== "") {
    getdate   = req.body.getdate;
  }
  var issuedate     = null;
  if (req.body.issuedate !== "") {
    issuedate = req.body.issuedate;
  }
  var readdate      = null;
  if (req.body.readdate !== "") {
    readdate  = req.body.readdate;
  }
  const post = { 'isbn13'     : isbn13,
                 'isbn10'     : isbn10,
                 'bookname'   : bookname,
                 'author'     : author,
                 'publisher'  : publisher,
                 'genre'      : genre,
                 'ownership'  : ownership,
                 'purchase'   : purchase,
                 'library'    : library,
                 'overview'   : overview,
                 'impressions': impressions,
                 'state'      : state,
                 'coverimg'   : coverimg,
                 'getdate'    : getdate,
                 'issuedate'  : issuedate,
                 'readdate'   : readdate };
  const connection = mysql.createConnection(mysql_setting);
  connection.connect();
  connection.query('INSERT INTO booklog SET ?', post, function (error, results, fields) {
    if (error) throw error;
    res.redirect('./');
    console.log('ISBN13:', results.insertIsbn13);
  });
  connection.end();
})
//localhost:3000/delete
router.post('/delete', (req, res, next) => {
  const isbn13 = req.body.isbn13;
  const connection = mysql.createConnection(mysql_setting);
  connection.connect();
  connection.query('DELETE FROM booklog WHERE isbn13=?', isbn13, function (error, results, fields) {
    if (error) throw error;
    res.redirect('./');
  });
  connection.end();
})
//localhost:3000/edit
router.get('/edit', function (req, res, next) {
  const isbn13 = req.query.isbn13;
  const strSql = "SELECT DATE_FORMAT(getdate,'%Y/%m/%d') getdate,"
                 + "DATE_FORMAT(issuedate,'%Y/%m/%d') issuedate,"
                 + "DATE_FORMAT(readdate,'%Y/%m/%d') readdate,"
                 + "isbn10,bookname,author,publisher,genre,ownership,"
                 + "purchase,library,overview,impressions,state,coverimg"
                 + " FROM booklog WHERE isbn13=?"
  const connection = mysql.createConnection(mysql_setting);
  connection.connect();
  connection.query(strSql, isbn13, function (error, results, fields) {
    if (error) throw error;
    if (!results.length) {
      res.redirect('../');
    } else {
      const data = {
        isbn13:      isbn13,
        isbn10:      results[0].isbn10,
        bookname:    results[0].bookname,
        author:      results[0].author,
        publisher:   results[0].publisher,
        genre:       results[0].genre,
        ownership:   results[0].ownership,
        purchase:    results[0].purchase,
        library:     results[0].library,
        overview:    results[0].overview,
        impressions: results[0].impressions,
        state:       results[0].state,
        coverimg:    results[0].coverimg,
        getdate:     results[0].getdate,
        issuedate:   results[0].issuedate,
        readdate:    results[0].readdate
      };
      res.render('edit', data);
    }
  });
  connection.end();
});
//localhost:3000/editへPOST
router.post('/edit', (req, res, next) => {
  const errors = validationResult(req);
  const isbn13      = req.body.isbn13;
  const isbn10      = req.body.isbn10;
  const bookname    = req.body.bookname;
  const author      = req.body.author;
  const publisher   = req.body.publisher;
  const genre       = req.body.genre;
  const ownership   = req.body.ownership;
  var   purchase    = null;
  if (req.body.purchase !== "") {
    purchase    = req.body.purchase;
  }
  const library     = req.body.library;
  const overview    = req.body.overview;
  const impressions = req.body.impressions;
  const state       = req.body.state;
  const coverimg    = req.body.coverimg;
  var   getdate     = null;
  if (req.body.getdate !== "") {
    getdate   = req.body.getdate;
  }
  var   issuedate   = null;
  if (req.body.issuedate !== "") {
    issuedate = req.body.issuedate;
  }
  var   readdate    = null;
  if (req.body.readdate !== "") {
    readdate  = req.body.readdate;
  }
  const post = { 'isbn10'     : isbn10,
                 'bookname'   : bookname,
                 'author'     : author,
                 'publisher'  : publisher,
                 'genre'      : genre,
                 'ownership'  : ownership,
                 'purchase'   : purchase,
                 'library'    : library,
                 'overview'   : overview,
                 'impressions': impressions,
                 'state'      : state,
                 'coverimg'   : coverimg,
                 'getdate'    : getdate,
                 'issuedate'  : issuedate,
                 'readdate'   : readdate };
  const connection = mysql.createConnection(mysql_setting);
  connection.connect();
  connection.query('UPDATE booklog SET ? WHERE isbn13 = ?', [post, isbn13], function (error, results, fields) {
    if (error) throw error;
    res.redirect('../')
  });
  connection.end();
});
module.exports = router;



■./views/index.ejs




  
  


  追加
  <% if (!page) { page = 0; } %>
  <% var itemFrom = page * 12; %>
  <% var itemTo = itemFrom + 12; %>
    <% for(let i in content) { %>
      <% if ((i >= itemFrom) && (i < itemTo)){ %>
        <% let obj = content[i]; %>
        <% if(i % 6 == 5 ){ %><% } %>
      <% } %>
    <% } %>
<% let rsrt = obj.coverimg.indexOf('._'); %> <% let srcImg = 'https://images-na.ssl-images-amazon.com/images/I/' + obj.coverimg.substr(0,rsrt) + '._AC_UL320_SR256,320_.jpg'; %> ">" width="170" />

  <% if (page > 0){ %>
    ">≪前の12件へ 
  <% } %>
  <% if ((page + 1) * 12 < content.length){ %>
    ">後の12件へ≫
  <% } %>



■./views/add.ejs








<form action="./add" method="post">
ISBN13:<input maxlength="13" name="isbn13" required="" size="15" type="number" value="9784000000000" /> ISBN10:<input maxlength="10" name="isbn10" size="12" type="number" value="4000000000" /> 画像:<input maxlength="41" name="coverimg" size="49" type="text" />
書名:<input maxlength="50" name="bookname" required="" size="64" type="text" /> 状況:<select name="state"> <option selected="selected" value="0">未読</option> <option value="1">読了</option> <option value="2">読書中</option> </select>
 
著者:<input maxlength="25" name="author" size="64" type="text" /> 分類:<input maxlength="25" name="genre" size="14" type="text" />
出版:<input maxlength="25" name="publisher" size="64" type="text" /> 所有:<input checked="checked" name="ownership" type="radio" value="1" /> Yes   <input name="ownership" type="radio" value="0" /> No
発行:<input id="issuedate" title="YYYY/MM/DD" maxlength="10" name="issuedate" pattern="\d{4}/\d{2}/\d{2}" size="11" type="text" /> 入手:<input id="getdate" title="YYYY/MM/DD" maxlength="10" name="getdate" pattern="\d{4}/\d{2}/\d{2}" required="" size="11" type="text" /> 読了:<input id="readdate" title="YYYY/MM/DD" maxlength="10" name="readdate" pattern="\d{4}/\d{2}/\d{2}" size="11" type="text" />
書店:<input maxlength="25" name="library" size="64" type="text" /> 価格:<input title="数字" maxlength="7" name="purchase" pattern="\d" size="5" type="number" />
概要:<input maxlength="255" name="overview" size="107" type="text" />
感想:<textarea cols="109" name="impressions" rows="4"></textarea>
<button class="btn btn-success" name="button" type="submit">登録</button> <button class="btn btn-warning" type="button">戻る</button></form>



■./views/edit.ejs











<form style="display: inline;" action="./edit" method="post">
ISBN13:<input name="isbn13" type="hidden" value="<%= isbn13 %">" /><%= isbn13 %> " target="_blank" rel="noopener">ISBN10:<input id="isbn10" maxlength="10" name="isbn10" pattern="^[0-9]*$" size="12" type="number" value="<%= isbn10 %">" /> 画像:<input id="coverimg" maxlength="41" name="coverimg" size="49" type="text" value="<%= coverimg %">" />
書名:<input id="bookname" maxlength="50" name="bookname" required="" size="64" type="text" value="<%= bookname %">" /> 状況:<select name="state"> <% if (state == "0") { %> <option selected="selected" value="0">未読</option> <option value="1">読了</option> <option value="2">読書中</option> <% } else if (state == "1") { %> <option value="0">未読</option> <option selected="selected" value="1">読了</option> <option value="2">読書中</option> <% } else { %> <option value="0">未読</option> <option value="1">読了</option> <option selected="selected" value="2">読書中</option> <% } %></select> <% if (coverimg !== "") { %> " height="360" /> <% } %>
著者:<input maxlength="25" name="author" size="64" type="text" value="<%= author %">" /> 分類:<input maxlength="25" name="genre" size="14" type="text" value="<%= genre %">" />
出版:<input id="publisher" maxlength="25" name="publisher" size="64" type="text" value="<%= publisher %">" /> 所有: <% if (ownership == 1) { %> <input checked="checked" name="ownership" type="radio" value="1" /> Yes   <input name="ownership" type="radio" value="0" /> No <% } else { %> <input name="ownership" type="radio" value="1" /> Yes   <input checked="checked" name="ownership" type="radio" value="0" /> No <% } %>
発行:<input id="issuedate" title="YYYY/MM/DD" maxlength="10" name="issuedate" pattern="\d{4}/\d{2}/\d{2}" size="11" type="text" value="<%= issuedate %">" /> 入手:<input id="getdate" title="YYYY/MM/DD" maxlength="10" name="getdate" pattern="\d{4}/\d{2}/\d{2}" required="" size="11" type="text" value="<%= getdate %">" /> 読了:<input id="readdate" title="YYYY/MM/DD" maxlength="10" name="readdate" pattern="\d{4}/\d{2}/\d{2}" size="11" type="text" value="<%= readdate %">" />
書店:<input maxlength="25" name="library" size="64" type="text" value="<%= library %">" /> 価格:<input id="purchase" title="数字" maxlength="7" name="purchase" pattern="\d" size="5" type="number" value="<%= purchase %">" />
概要:<input maxlength="255" name="overview" size="107" type="text" value="<%= overview %">" />
感想:<textarea cols="109" name="impressions" rows="4"><%= impressions %></textarea>
<button class="btn btn-success" type="submit">更新</button> <button class="btn btn-warning" type="button">戻る</button></form>
<form style="display: inline;" action="./delete" method="post"><input name="isbn13" type="hidden" value="<%= isbn13 %">" /> <button class="btn btn-danger" type="submit">削除</button></form>

コメント一覧

frontflug
過日、コメントに
「HTA版では、画像ファイル名を含む書籍情報をISBN10さえ分かればAjaxで読み込んでセットするという関数を作成していました。ところが、Node.jsにも組み込んでみましたがクロスドメイン制約があり、ERROR になります。」ということで断念していたのですが、Nodo.js には、Node.js の仕組みがありましたね。cheerio-httpcli というモジュールです。
更新版をhttps://github.com/frontflg/BOOKLOG_NODE
にソース公開していまので、どうぞ参考に。
ブログ作成者
GitHub登録
https://github.com/frontflg/BOOKLOG_NODE
https://github.com/frontflg/BOOKLOG_NODE
ソース公開しています。
ブログ作成者
バグ多数在
バグがありますね。
(page + 1) * 12 < content.length
は、ダメですね。

自分では直しましたが・・・
記事は修正しませんので、考えてみてください。
(って、投げっぱなしかよ、なんて奴だ!)
他も、こまごま直してます。
ブログ作成者
誤字は数多あり
④停止
ctlr + C
は、
Ctrl + C
のミスですね。
察してくだされぃ

そういうの上げたらキリがないブログです。
実は、キーボードが視づらく(老眼)なって来てます。末期?
ブログ作成者
Yahoo!ボックスで、ソース公開しています。
https://yahoo.jp/box/Dqgq0Q
↓Yahoo!ボックスで、ソース公開しています。

https://yahoo.jp/box/Dqgq0Q

↑こちらは、今後の改良も(都度)反映させるつもりです。

HTAより優れているのは、ソース量(ステップ数)が
圧倒的に少なくて済むところ
曲がりなりにも、WEBアプリで
3画面(一覧、新規登録、詳細)あって
項目も必須項目、数字、日付、画像と一通りあり、
3機能(登録・変更・削除)が、(単一テーブル、低機能とは言え)
400行程で出来るなんて
ブログ作成者
Ajax関数
失敗するAjax のFUNCTION を乗っけておきます。
TRYする方、改良される方が現れるのを期待して
※著者名はうまく取れない(形式が不定)

呼び出し
<td colspan="2"><a href="#" onclick="getImg ();">画像</a>:<input type="text" name="coverimg" id="coverimg" value="<%= coverimg %>" maxlength="41" size="46"></td></tr>


関数(FUNCTION)
<script>
function getImg () {
isbn10 = $('#isbn10').val();
if (isbn10 == '') { return; }
if (isbn10 == '4000000000') { return; }
$.ajax({
url: 'https://www.amazon.co.jp/dp/' + isbn10,
type: 'get',
}).done(function (data, textStatus, jqXHR) {
var str = data;
var content = str.substr((str.indexOf('data-a-dynamic-image=')+78),39);
$('#coverimg').val(content);
if ($('#bookname').val() == '') {
content = str.substr((str.indexOf('id="productTitle" class="a-size-large">')+39),100);
content = content.substr(0,content.indexOf('</span>'));
if (content.length > 50) { content = content.substr(0,50).trim(); }
$('#bookname').val(content);
}
if ($('#publisher').val() == '') {
content = str.substr((str.indexOf('<li><b>出版社:</b> ')+16),50);
content = content.substr(0,content.indexOf('('));
if (content.length > 25) { content = content.substr(0,25).trim(); }
$('#publisher').val(content);
}
if ($('#issuedate').val() == '') {
content = str.substr((str.indexOf('<li><b> 発売日:</b> ')+17),10);
content = content.replace( '</', '' );
content = content.replace( '<', '' );
$('#issuedate').val(content);
}
// 検索失敗時には、その旨をダイアログ表示
}).fail(function (jqXHR, textStatus, errorThrown) {
alert("Ajaxで失敗しました: " + textStatus + " " + errorThrown);
})
};
</script>
ブログ作成者
画像ファイル
本気に使いこもうとしないと分からないと思いますが
この「読書履歴管理システム」は、歴代、そして最近は特に
表紙画像がキモ(要)になってます。
昔は、タウンロードして落とし込んでましたが
現在は、Amazonの図書サイトの画像ファイル名を登録し
Amazonの画像を表示させてます。借景です。

HTA版では、画像ファイル名を含む書籍情報を
ISBN10さえ分かれば
Ajaxで読み込んでセットするという関数を作成していました。
超便利です。

ところが、Node.jsにも組み込んでみましたが
クロスドメイン制約があり、ERROR になります。
これ当たり前(HTAができるのがおかしい)
回避する仕組みは、あるらしい(Yahoo経由)のですが、
簡単にしたいという当初の目標があり
そこまで、する?
とも思いますしたので、Node.js版は、手作業になります。
まあ、個人的には、HTA版が簡単で使い勝手が格段にいいので
Node.js はあくまで勉強です(の扱いです)。

で、手設定ですが、
新規登録の画面で、ISBN10 の見出しのリンクをクリックして
Amazonの検索画面から
登録したい図書のページを開いて
書籍内容をコピペするという・・・手作業です。
画像は、表紙のイメージを右クリックで
プロパティ(R)から、アドレス(URL):のファイル名(jpg)部分を
コピペして、画像に張り付けてください。
登録がうまく行ったら、一覧画面に画像イメージが表示されてます(筈です)。
名前:
コメント:

※文字化け等の原因になりますので顔文字の投稿はお控えください。

コメント利用規約に同意の上コメント投稿を行ってください。

 

※ブログ作成者から承認されるまでコメントは反映されません。

  • Xでシェアする
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

最新の画像もっと見る

最近の「Node.js他(Python)」カテゴリーもっと見る

最近の記事
バックナンバー
人気記事