LoginSignup
4
1

More than 3 years have passed since last update.

ローカルにプレーンなメモアプリを構築

Last updated at Posted at 2020-06-04

プログラミングをしている人には、普段あちこちググったことを
メモっておきたい、という日常があると思います。twitterを
メモ代わりにつぶやいたり、wikiにまとめたり。

しかし、ツイッターにとってつぶやきは過ぎゆく過去であり、
そのためのインタフェースで設計されています。
一方、wikiにまとめたらそれはもう技術資料であり、メモではない、
等々こういったジレンマにかられたりすると思います。

というわけで僕は以下のようなメモアプリをローカルに構築しました。

範囲を選択_047.png

テキストエリアに入力し、「保存」ボタンを押すとDBに保存される
シンプルなメモです。自分はWebアプリエンジニアではないので、
画面例では素人ならではの紆余曲折が残っています。

使っているアーキテクチャ

・HTML
・JavaScript
・mongodb
・nodejs(express)
・WebAPI

なるべくプレーンなHTML、JavaScriptで画面アプリを構築しています。
jqueryとか、vue.jsとか、そんな便利lib/frameworkでガンガン構築
するのは楽しそうですが、プレーンな部分を知らないと
結局よくわかりません。

なんとなく会得したこと

・動的なデータ(今回は追加されていくメモ)を表示するためには、
 テーブル部品自体をJavaScriptで記述する。(当たり前のことかもですが)
・UIとは、データが取得できたら再描画することの繰り返しである。
 そのための非同期処理である。
 非同期処理として、Promise機構を利用している。
 WebAPIを叩くためのfetcherAPIがPromise実装しているので
 then句で非同期処理(データ取れたらUI再描画)を記述している。
・JavaScriptのモジュール化
 もっともモッサイ方法だと思いますが、単に複数ファイルに分けて、
 全部htmlでロードしています。しかし、簡単なんだ、これが・・
 ただ、本当にネットで公開したら一個のJavaScriptにまとめたほうが
 ダウンロード効率的には良いんですかね・・

実装の順番

1.ローカルページの実装
  HTMLとJavaScriptでゴリゴリ書きます。
2.Webサーバ構築
  ローカルとはいえ、webサーバを立てています。nodejsのexpressです。
  このwebサーバには以下の2つの役割を実装しています。
  1.Webページのホスティング
  2.WebAPIのサービサー
3.mongodbセットアップ
  メモのようなゆるいデータを逐次貯めていくにはスキーマレスな
  mongodbが良さげです。mongodbをインストールしてサーバ起動させます。
  2のWebAPIは、nodejsのmongodbドライバを使って、このmongodbに
  アクセスします。

ソースコード

index.html(メモブラウザ)


<html>
<head>
<title>メモ帳</title>
<link rel="stylesheet" type="text/css" href="main.css">
<style type="text/css">
  textarea{width:30%;height:20%;}
</style>
</head>

<body>
  <h1>メモ帳です</h1>
  <div>
    <ul id="todo-container"></ul>
  </div>
  <div>
  <textarea name="memo" id="id_memo"></textarea>
  <p> 
    <input type="button" value="保存" onclick=saveMemo();></input>
    <input type="button" value="消去" onclick=clear2();></input>
    <input type="button" value="reverse" onclick=sort_reverse();></input>
  </p>
  </div>
  <div>
    カテゴリ選択: 
  <select id="category" onchange="category_changed();">
    <option value="none">none</option>
    <option value="html/css">html/css</option>
    <option value="javascript">javascript</option>
    <option value="nodejs">nodejs</option>
    <option value="database">database</option>
    <option value="daily">daily</option>
    <option value="browser">browser</option>
    <option value="stm">stm</option>
  </select>
   リバース
  <input type="button" value="昇順/降順" onclick=sort_reverse();></input>
  <table>
    <tbody id="memo_table">
    </tbody>
  </table>
  </div>

  <script type="text/javascript" src="./tool.js"></script>
  <script type="text/javascript" src="./memo_db.js"></script>
  <script type="text/javascript" src="./main.js"></script>

</body>
</html>

カテゴリ選択もJavaScriptで動的に捌けば、htmlページにあるのはただの
コンテナ宣言(div)だけにスッキリさせられそうです。

main.js(UIのメイン処理)


function category_changed() {
  showMemoList(document.getElementById("category").value);
}

function sort_reverse() {
  sort_order = localStorage.getItem('sort_order');
  if (!sort_order) {
    localStorage.setItem('sort_order', 'asc');
  } else {
    if (sort_order == 'asc') {
      localStorage.setItem('sort_order', 'des');
    } else {
      localStorage.setItem('sort_order', 'asc');
    }   
  }
  showMemoList(document.getElementById("category").value);
}

function saveMemo() {
  // DBへの書き出しが完了したら、UIを再描画する。
  writeMemoDb({date: date_string(),
             memo:document.getElementById("id_memo").value,
             category: document.getElementById("category").value
            })
    .then(showMemoList('none'));
}

function deleteMemo(id) {
  deleteMemoDb(id)
    .then(showMemoList('none'));
}

function clear2() {
  document.getElementById("id_memo").value = ''; 
}

function showMemoList(category) {
  readMemoDbAll(category, localStorage.getItem('sort_order'))
    .then(res_json => {
      console.log('res json = ', res_json);
      let tb = document.getElementById("memo_table");
      while (tb.firstChild) {
        tb.removeChild(tb.firstChild);
      }
      for (let data_key in res_json) {
        console.log(res_json[data_key]['memo']);

        // 純粋なテキスト挿入ならcreateTextNode。
        // テキストにHTML構文を使いたいなら、innerHTMLセット。

        tb.appendChild(document.createElement("tr"));
        let td0 = document.createElement("td");
        let td1 = document.createElement("td");
        let td2 = document.createElement("td");
        let td3 = document.createElement("td");
        let td4 = document.createElement("td");
        td0.style.backgroundColor="green";
        td0.style.color="white";
        td0.appendChild(document.createTextNode(data_key));
        td1.style.backgroundColor="skyblue";
        td1.style.color="white";
        td1.appendChild(document.createTextNode(res_json[data_key]['date']));
        td2.innerHTML = res_json[data_key]['memo'];
        td3.appendChild(document.createTextNode(res_json[data_key]['category']));
        td4.innerHTML = '<input type="button" onclick="deleteMemo(\'' + res_json[data_key]['_id'] + '\')" value="削除">';

        tb.appendChild(td0);
        tb.appendChild(td1);
        tb.appendChild(td2);
        tb.appendChild(td3);
        tb.appendChild(td4);
      }
    })
}

ページロード時、各種ボタン押下時の機能を定義しています。
配列とfor文を使えばもう少しスッキリさせられそう。

memo_db.js(mongodbの操作)


function deleteMemoDb(id) {
  return fetch('./api/memo/' + id, {method: "DELETE"})
    .then((response) => response.json())
}

function readMemoDbAll(category, sort_order) {
  return fetch('./api/memo?category=' + category + '&sort_order=' + sort_order)
    .then((response) => response.json())
}

function writeMemoDb(record) {
  return fetch('./api/memo',
        {method: "POST",
         headers: {
           "Content-TYpe": "application/json; charset=utf-8",
         },
         body: JSON.stringify(record)
        })
    .then(response => response.json())
    .then(data => console.log(JSON.stringify(data)))
    .catch(error => console.error(error));
}

WebAPIを叩いて、DBを操作しています。

memo_app.js(サーバ側メモアプリ)


const express = require('express');
const bodyParser = require('body-parser')
const MongoClient = require('mongodb').MongoClient;
const mongo = require('mongodb');
const assert = require('assert');

const url = 'mongodb://localhost:27017';
const dbName = 'memo';
const option = {
  useNewUrlParser: true,
  useUnifiedTopology: true
}

const app = express();

app.use(express.static('web'));
app.use(bodyParser.json());

app.post('/api/memo/', (req, res) => {
  console.log(req.body);

  MongoClient.connect(url, option, (err, client) => {
    assert.equal(null, err);
    let obj = req.body;
    client.db('memo').collection('memo').insertOne(obj, (err, result) => {
      client.close();
      res.send(result);
    }); 
  }); 
});

app.get('/api/memo/:id', (req, res) => {
  console.log(req.params.id);
});

app.get('/api/memo/', (req, res) => {
  console.log('category = ', req.query.category);
  MongoClient.connect(url, option, (err, client) => {
    assert.equal(null, err);
    condition = {}
    if (req.query.category != 'none') {
      condition = {category: req.query.category}
    }   
    if (req.query.sort_order == 'asc') {
      client.db('memo').collection('memo').find(condition).sort({date:1}).toArray((err, result) => {client.close(); res.send(result);});
    } else {
      client.db('memo').collection('memo').find(condition).sort({date:-1}).toArray((err, result) => {client.close(); res.send(result);});
    }   
  }); 
});

app.delete('/api/memo/:id', (req, res) => {
  console.log(req.params.id);
  MongoClient.connect(url, option, (err, client) => {
    assert.equal(null, err);
    console.log('condition = ', {id:req.params.id});
    client.db('memo').collection('memo').deleteOne({_id: new mongo.ObjectId(req.params.id)}, (err, result) => {
      client.close();
      if (err) {
        res.send(JSON.stringify({result: 'ng'}));
      } else {
        res.send(JSON.stringify({result: 'ok'}));
      }   
    }); 
  }); 
});

app.listen(3009, () => console.log('listening on port 3009'));

つくってみた所感

こんな感じです。別のPCに同じ環境を構築するにはちょっとポータブル性が
足りませんが、しょうがない。nodeパッケージ自体はポータブル性アリアリなので
それで良しとします。

最初はlocalStorageを使ってデータを貯めようかとも
思いましたが、やりたいことに対し機能不足でした。
localStorageにはあくまでページ設定を保存し(ソートオーダーとか)、
データ本体はサーバに置いてWebAPIでアクセスする形になりました。
このあたりがWebアーキテクチャの制約なのかなぁ。
ブラウザのHTMLでローカルに置いたmongodbに直接アクセスできては
いけないんですよね、たぶんセキュリティ的に。

ローカルデバイス内部で発生し、閉じた系でのデータ駆動をモニタしたり
制御するUIアプリでは、サーバサービス不要なんですが、
webアーキテクチャベースで構築するとこうならざるを得ないのかな。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1