這是 Electron in Action chap2 的 github sample bookmarker github,該章節是第一個 Electron applicaiton sample,裡面使用了 localStorage 及 shell,因為測試的關係,修改使用 IPC,將 shell 呼叫移到 main process。
Electron API 有列出 API 項目,以及該 API 可以使用在 Main/Renderer process 的資訊
package.json
- 將 electron 版本改為目前的 stable 版
"electron": "^13.1.9"
{
"name": "bookmarker",
"version": "1.0.0",
"description": "An example Electron application",
"main": "app/main.js",
"scripts": {
"start": "electron .",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/electron-in-action/bookmarker.git"
},
"author": "Steve Kinney <hello@stevekinney.net> (http://stevekinney.net)",
"license": "MIT",
"private": true,
"dependencies": {},
"bugs": {
"url": "https://github.com/electron-in-action/bookmarker/issues"
},
"homepage": "https://github.com/electron-in-action/bookmarker#readme",
"devDependencies": {
"electron": "^13.1.9"
}
}
main.js
把原本放在 renderer process 的 shell 呼叫,移到 main process
增加 IPC main 的部分,處理 renderer process 傳送的訊息及 href 參數,然後呼叫
shell.openExternal(href);
因為新版的 electron 的限制,renderer process 使用了 localStorage,故 BrowserWindow 必須增加兩個 webPreferences 設定
mainWindow = new BrowserWindow({ webPreferences: { nodeIntegration: true, contextIsolation: false } });
用以下 code 查詢 localStorage 儲存的硬碟路徑
// 查詢 application 將 localStorage 儲存在哪一個 folder let userdata = app.getPath('userData'); console.log('userdata='+userdata);
const {
app,
shell,
BrowserWindow,
ipcMain
} = require('electron');
let mainWindow = null; // #A
app.on('ready', () => {
console.log('Hello from Electron.');
// 查詢 application 將 localStorage 儲存在哪一個 folder
let userdata = app.getPath('userData');
console.log('userdata='+userdata);
mainWindow = new BrowserWindow({
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
});
mainWindow.webContents.loadURL(`file://${__dirname}/index.html`); // #A
});
ipcMain.on('shell-openExternal', (event, data) => {
var href = data.href;
console.log("shell-openExternal href="+href);
shell.openExternal(href);
});
index.html
修改 include js 的部分
<!-- <script> require('./renderer'); </script> --> <script src="renderer.js"></script>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="style.css" type="text/css">
<title>Bookmarker</title>
</head>
<body>
<h1>Bookmarker</h1>
<div class="error-message"></div>
<section class="add-new-link">
<form class="new-link-form">
<input type="url" class="new-link-url" placeholder="URL" required>
<input type="submit" class="new-link-submit" value="Submit" disabled>
</form>
</section>
<section class="links"></section>
<section class="controls">
<button class="clear-storage">Clear Storage</button>
</section>
<!-- <script>
require('./renderer');
</script> -->
<script src="renderer.js"></script>
</body>
</html>
style.css
html {
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}
body, input {
font: menu; // #A
}
renderer.js
刪除 shell 呼叫的部分,改用 IPC 傳送到 main process 處理
注意處理 Submit 的寫法
在填寫 newLinkUrl 時,用 validity 檢查是否為正確的 URL 字串,然後將結果,轉為指定 newLinkSubmit enable/disable
submit form 的時候,
event.preventDefault();
可避免 Chromium 呼叫原本 form submit 的動作,不會 trigger HTTP requestfetch 可取得 URL 的 response,在這邊只需要 title,用來作為 bookmark 的 title。實際上是用了 DOMParser parsing HTTP response data
newLinkUrl.addEventListener('keyup', () => { newLinkSubmit.disabled = !newLinkUrl.validity.valid; }); newLinkForm.addEventListener('submit', (event) => { event.preventDefault(); const url = newLinkUrl.value; fetch(url) .then(response => response.text()) .then(parseResponse) .then(findTitle) .then(title => storeLink(title, url)) .then(clearForm) .then(renderLinks) .catch(error => handleError(error, url)); });
localStorage 是 key-value 的形式
localStorage.setItem(url, JSON.stringify({ title: title, url: url }));
let ipcRenderer = require('electron').ipcRenderer;
// const {shell} = require('electron');
const parser = new DOMParser();
const linksSection = document.querySelector('.links');
const errorMessage = document.querySelector('.error-message');
const newLinkForm = document.querySelector('.new-link-form');
const newLinkUrl = document.querySelector('.new-link-url');
const newLinkSubmit = document.querySelector('.new-link-submit');
const clearStorageButton = document.querySelector('.clear-storage');
newLinkUrl.addEventListener('keyup', () => {
newLinkSubmit.disabled = !newLinkUrl.validity.valid;
});
newLinkForm.addEventListener('submit', (event) => {
event.preventDefault();
const url = newLinkUrl.value;
fetch(url)
.then(response => response.text())
.then(parseResponse)
.then(findTitle)
.then(title => storeLink(title, url))
.then(clearForm)
.then(renderLinks)
.catch(error => handleError(error, url));
});
clearStorageButton.addEventListener('click', () => {
localStorage.clear();
linksSection.innerHTML = '';
});
linksSection.addEventListener('click', (event) => {
if (event.target.href) {
event.preventDefault();
var data = {
href: event.target.href
};
// shell.openExternal(event.target.href);
ipcRenderer.send('shell-openExternal', data);
}
});
const clearForm = () => {
newLinkUrl.value = null;
}
const parseResponse = (text) => {
return parser.parseFromString(text, 'text/html');
}
const findTitle = (nodes) => {
return nodes.querySelector('title').innerText;
}
const storeLink = (title, url) => {
localStorage.setItem(url, JSON.stringify({ title: title, url: url }));
}
const getLinks = () => {
return Object.keys(localStorage)
.map(key => JSON.parse(localStorage.getItem(key)));
}
const convertToElement = (link) => {
return `<div class="link"><h3>${link.title}</h3>
<p><a href="${link.url}">${link.url}</a></p></div>`;
}
const renderLinks = () => {
const linkElements = getLinks().map(convertToElement).join('');
linksSection.innerHTML = linkElements;
}
const handleError = (error, url) => {
errorMessage.innerHTML = `
There was an issue adding "${url}": ${error.message}
`.trim();
setTimeout(() => errorMessage.innerText = null, 5000);
}
const validateResponse = (response) => {
if (response.ok) { return response; }
throw new Error(`Status code of ${response.status} ${response.statusText}`);
}
renderLinks();
執行畫面
References
Electron in Action repositories
Electron in Action source code
Where an electron application's sessionStorage and localStorage stored?