初めてのElectron vol.03 [簡易ToDoリストを作成(データ保存)]
記事はbookへ統合されます
vol.03 今回の内容
第三回です。前回の
これのデータを保存できるようにしていきます。
どうやって実現するか
ぶっちゃけWindow.localStorage使うのが定石な気がするのはおいておいて、
electronの機能を学ぶためにelectron-storeを使います。
electron-store
electronでデータを保存しておくためのモジュールです。
簡単にデータの読み書きができるようになります。
const Store = require('electron-store');
const store = new Store();
store.set('key', 'data'); // 保存
const data = store.get('key'); // 取り出し
store.delete('key'); // 削除
store.clear(); // データ全部削除
if( store.has('key') ){// 存在確認
console.log('keyあった');
}
簡単でしょ
入れるのはnpmで一発
npm install electron-store
実際にコードを書く前にelectronの基本の話
electronは大きく分けるとメインプロセスとレンダラープロセスで構成されています
メインプロセス
これはelectronを起動すると呼び出されるやつです。今作成しているモノだと./main.js
が該当します。このメインプロセスがレンダラープロセスを呼んでああだこうだする感じです
Electronの設定やOS由来の機能はメインプロセスで制御されてます
レンダラープロセス
こっちが実際に表示される部分を制御している部分です
今作っているモノだと./index.html(から呼び出されている[./todolist.js])
とか表示しているウインドウが該当します、多分
通常のブラウザでできること以外のNode.jsやElectronの機能を直接呼び出すことはできません
正確には昔は使えました
この仕様変更のせいで、electronのチュートリアルにつまずく人が増える気がする
<body>
<!--色々-->
<script>
const {ipcRenderer} = require('electron');//<!--ここがエラーになる-->
//何らかの処理
</script>
</body>
上記書き方をしている記事が多くそのままだとUncaught ReferenceError: require is not defined
エラーが発生しちゃうんですよね
今も非推奨ですがちょっといじれば使えますがセキュリティ的によくないそうです。なので使いません
余談ですが
const mainWindow = new BrowserWindow({
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
},
});
と定義すれば上記エラーとかでなくなりまっせ
レンダラーとメインの橋渡し
preload.jsを使う
小見出しの通り橋渡しをしてくれるやつ
レンダラーがNode Electron APIに自由にアクセスできないように、
あらかじめpreload.jsを作成し必要な機能のみを記載することで、安全に橋渡ししてくれる便利もの
[メインプロセス] < == > [preload.js] < == > [レンダラープロセス]
直接じゃない&定義されていないものを呼び出せなくすることで安全性向上してるっぽいですね
これを踏まえていくとちょっとわかりやすくなるかもしれません
実際にコード書いてく
では実際にコードを書いていきます
main.js書き換え
レンダラーからの通信に応答するためのやつを読み込むようにする
const electron = require("electron"); //electron読み込んで
-const { app, BrowserWindow } = electron;//electronからapp,BrowserWindowを取り出す
+const { app, BrowserWindow, ipcMain } = electron;
+const Store = require('electron-store');//storeも読み込んでおく
通信に応答するところは後で書きます
preload.js
を使うよってBrowserWindowに教えてやる
//省略
//electron が準備終わったとき
app.on("ready", function () {
//新しいウインドウを開く
mainWindow = new BrowserWindow({
width: 320,
height: 500,
+ webPreferences: {
+ preload: path.join(__dirname, 'preload.js'),
+ }
});
//省略
});
このように教えてあげましょう
webpreferencesって書き忘れてはまったのはないしょ
preload.js作成
今日の本題preload.js
を書いていきます
const { contextBridge, ipcRenderer } = require("electron");
contextBridge.exposeInMainWorld(
'dataapi', {
getlist: () => ipcRenderer.invoke("getlist"),
setlist: (data) => ipcRenderer.invoke("setlist", data),
});
レンダラー側からwindow.dataapi.getlist()を呼ぶとメイン側に"getlist"中継する
レンダラー側からwindow.dataapi.setlist()を呼ぶとメイン側に"setlist"とdataを中継する
みたいなことを書いてます
main.jsに処理を書いていく
下記のように追記します
//省略
+const Store_Data = new Store({ name: "data" });//Dataを格納しておくStore
//electron が準備終わったとき
app.on("ready", function () {
//省略
}
+// IPC通信(DataAPI関係)
+
+//getlist(data取得処理)
+ipcMain.handle('getlist', async (event, data) => {
+ return Store_Data.get('ToDoList', []);//ToDoListがあれば取り出し、なければからのリストを返す
+});
+
+//getlist(data保存処理)
+ipcMain.handle("setlist", async (event, data) => {
+ Store_Data.set('ToDoList', data); // 保存
+});
todolist.jsを書いていく
//アイテム追加ボタンを押したときの処理
function addToDo() {
const item = document.querySelector("#ToDoItem").value;//formの文字列取得
if (item === "") {//itemがからだったら何もしない
return
}
+ //preloadを介してmainjsでStoreのデータを取得
+ const ToDoList = await window.dataapi.getlist();
+ ToDoList.push(item);//今回追加されたものをデータに追加
+ await window.dataapi.setlist(ToDoList);//preloadを介してmainjsでStoreのデータを保存
+ listview();//表示を更新
- const ul = document.querySelector("#ToDolist");//id=ToDolistを取得する
- const li = document.createElement("li");//li elementを作る
- const todotext = document.createTextNode(item);//itemをテキストノードにする
- li.appendChild(todotext);//liにtodotextを入れる
- ul.appendChild(li);//ulに作ったliを入れる
}
+//Storeデータをmain.jsからとってきて表示する処理
+async function listview() {
+ const ul = document.querySelector("#ToDolist");//id=ToDolistを取得する
+ const clone = ul.cloneNode(false); //ul要素の中身以外を拝借
+ const ToDoList = await window.dataapi.getlist();
+ for (const item of ToDoList) {
+ const li = document.createElement("li");//li elementを作る
+ const todotext = document.createTextNode(item);//itemをテキストノードにする
+ li.appendChild(todotext);//liにtodotextを入れる
+ clone.appendChild(li);//ulに作ったliを入れる
+ }
+ ul.parentNode.replaceChild(clone, ul); //ulをcloneに入れ替える
+}
+//起動時初期化用
+listview();
準備はできた
ここまでいろいろファイルをいじったので、
現在のファイル内容を確認しよう
コメントの位置とかちょっとづつ違うのはご容赦
./main.js
const electron = require("electron"); //electron読み込んで
const { app, BrowserWindow, ipcMain } = electron;//electronからapp,BrowserWindowを取り出す
const path = require("path"); //pathも使う
const Store = require('electron-store');//storeも読み込んでおく
let mainWindow;
const Store_Data = new Store({ name: "data" });//Dataを格納しておくStore
//electron が準備終わったとき
app.on("ready", function () {
//新しいウインドウを開く
mainWindow = new BrowserWindow({
width: 320,
height: 500,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
}
});
//mainWindowでhtmlファイルを開く
//"file://" + path.join(__dirname, 'index.html');>> file://作業ディレクトリ/index.html
mainWindow.loadURL("file://" + path.join(__dirname, 'index.html'));
//メインウインドウが閉じたらアプリが終了する
mainWindow.on("closed", function () {
app.quit();
});
});
// IPC通信(DataAPI関係)
//getlist(data取得処理)
ipcMain.handle('getlist', async (event, data) => {
return Store_Data.get('ToDoList', []);//ToDoListがあれば取り出し、なければからのリストを返す
});
//getlist(data保存処理)
ipcMain.handle("setlist", async (event, data) => {
Store_Data.set('ToDoList', data); // 保存
});
./todolist.js
//アイテム追加ボタンを押したときの処理
async function addToDo() {
const item = document.querySelector("#ToDoItem").value;//formの文字列取得
if (item === "") {//itemがからだったら何もしない
return
}
const ToDoList = await window.dataapi.getlist();//preloadを介してmainjsでStoreのデータを取得
ToDoList.push(item);//今回追加されたものを追加
await window.dataapi.setlist(ToDoList);//preloadを介してmainjsでStoreのデータを保存
listview();
}
//Storeデータをmain.jsからとってきて表示する処理
async function listview() {
const ul = document.querySelector("#ToDolist");//id=ToDolistを取得する
const clone = ul.cloneNode(false); //ul要素の中身以外を拝借
const ToDoList = await window.dataapi.getlist();
for (const item of ToDoList) {
console.log(item);
const li = document.createElement("li");//li elementを作る
const todotext = document.createTextNode(item);//itemをテキストノードにする
li.appendChild(todotext);//liにtodotextを入れる
clone.appendChild(li);//ulに作ったliを入れる
}
ul.parentNode.replaceChild(clone, ul); //入れ替える
}
//起動時初期化用
listview();
./preload.js
const { contextBridge, ipcRenderer } = require("electron");
contextBridge.exposeInMainWorld(
'dataapi', {
getlist: () => ipcRenderer.invoke("getlist"),
setlist: (data) => ipcRenderer.invoke("setlist", data),
});
./index.html
<html>
<head>
<title>ToDoリスト</title>
</head>
<body>
<h1>ToDoリスト</h1>
<div class="form">
<input type="text" id="ToDoItem">
<button onclick="addToDo()">追加</button>
<!-- ボタンが押されたらjsファイルの function addToDo()を呼ぶ-->
</div>
<ul id="ToDolist">
</ul>
<script src="./todolist.js"></script>
<!-- jsファイルを読み込む-->
</body>
</html>
動作テスト
npm start
この状態でウィンドウを閉じて開きなおしても
この通り保存できた。
次回予告
- 完了したものを消す
- 全削除ボタンを付ける
非アクティブでもキーボードショートカットで呼び出せるようにする
の全部かどれかです。
あとがき
book形式で記事書けばよかったとちょっと後悔中
Discussion