อิเลคตรอน: แอพเดสก์ท็อปข้ามแพลตฟอร์มที่ทำได้ง่าย
เผยแพร่แล้ว: 2022-03-11เมื่อต้นปีนี้ Github ได้เปิดตัว Atom-Shell ซึ่งเป็นแกนหลักของโปรแกรมแก้ไขโอเพนซอร์สที่มีชื่อเสียงอย่าง Atom และเปลี่ยนชื่อเป็น Electron สำหรับโอกาสพิเศษ
อิเลคตรอน ซึ่งแตกต่างจากคู่แข่งรายอื่นในหมวดหมู่ของแอปพลิเคชันเดสก์ท็อปที่ใช้ Node.js นำความบิดเบี้ยวมาสู่ตลาดที่มีความมั่นคงอยู่แล้ว โดยผสมผสานพลังของ Node.js (io.js จนถึงรีลีสล่าสุด) เข้ากับ Chromium Engine เพื่อนำมาซึ่ง เราเป็นจาวาสคริปต์ฝั่งเซิร์ฟเวอร์และฝั่งไคลเอ็นต์ที่ดีที่สุด
ลองนึกภาพโลกที่เราสามารถสร้างแอปพลิเคชันเดสก์ท็อปข้ามแพลตฟอร์มที่มีประสิทธิภาพ ขับเคลื่อนด้วยข้อมูล ซึ่งขับเคลื่อนโดยไม่เพียงแต่พื้นที่เก็บข้อมูลของโมดูล NPM ที่เติบโตขึ้นเรื่อยๆ แต่ยังรวมถึงรีจิสทรี Bower ทั้งหมดเพื่อตอบสนองความต้องการฝั่งไคลเอ็นต์ทั้งหมดของเรา
ป้อนอิเล็กตรอน
ในบทช่วยสอนนี้ เราจะสร้างแอปพลิเคชันพวงกุญแจรหัสผ่านอย่างง่ายโดยใช้ Electron, Angular.js และ Loki.js ซึ่งเป็นฐานข้อมูลขนาดเล็กในหน่วยความจำพร้อมไวยากรณ์ที่คุ้นเคยสำหรับนักพัฒนา MongoDB
ซอร์สโค้ดแบบเต็มสำหรับแอปพลิเคชันนี้มีให้ที่นี่
บทช่วยสอนนี้ถือว่า:
- เครื่องอ่านมี Node.js และ Bower ติดตั้งอยู่ในเครื่อง
- พวกเขาคุ้นเคยกับ Node.js, Angular.js และไวยากรณ์การสืบค้นที่เหมือน MongoDB
รับสินค้า
อย่างแรกเลย เราจะต้องรับไบนารีของอิเล็กตรอนเพื่อทดสอบแอปของเราในเครื่อง เราสามารถติดตั้งได้ทั่วโลกและใช้เป็น CLI หรือติดตั้งในเครื่องในเส้นทางของแอปพลิเคชันของเรา ฉันแนะนำให้ติดตั้งทั่วโลก เพื่อที่เราจะได้ไม่ต้องทำซ้ำซ้ำแล้วซ้ำเล่าสำหรับทุกแอปที่เราพัฒนา
เราจะเรียนรู้วิธีบรรจุแอปพลิเคชันเพื่อแจกจ่ายโดยใช้อึกในภายหลัง กระบวนการนี้เกี่ยวข้องกับการคัดลอกไบนารีของอิเล็กตรอน ดังนั้นจึงไม่สมเหตุสมผลเลยที่จะติดตั้งด้วยตนเองในเส้นทางของแอปพลิเคชันของเรา
ในการติดตั้ง Electron CLI เราสามารถพิมพ์คำสั่งต่อไปนี้ในเทอร์มินัลของเรา:
$ npm install -g electron-prebuilt
ในการทดสอบการติดตั้ง ให้พิมพ์ electron -h
และควรแสดงเวอร์ชันของ Electron CLI
ในขณะที่เขียนบทความนี้ เวอร์ชันของอิเล็กตรอนคือ 0.31.2
ตั้งโครงการ
สมมติว่าโครงสร้างโฟลเดอร์พื้นฐานต่อไปนี้:
my-app |- cache/ |- dist/ |- src/ |-- app.js | gulpfile.js
… โดยที่: - แคช/ จะถูกใช้เพื่อดาวน์โหลดไบนารีอิเล็กตรอนเมื่อสร้างแอป - dist/ จะมีไฟล์การแจกจ่ายที่สร้างขึ้น - src/ จะมีซอร์สโค้ดของเรา - src/app.js จะเป็นจุดเริ่มต้นของแอปพลิเคชันของเรา
ต่อไป เราจะไปที่โฟลเดอร์ src/
ในเทอร์มินัลของเรา และสร้างไฟล์ package.json
และ bower.json
สำหรับแอปของเรา:
$ npm init $ bower init
เราจะติดตั้งแพ็คเกจที่จำเป็นในภายหลังในบทช่วยสอนนี้
ทำความเข้าใจกระบวนการอิเล็กตรอน
อิเล็กตรอนแยกความแตกต่างระหว่างกระบวนการสองประเภท:
- กระบวนการหลัก : จุดเริ่มต้นของแอปพลิเคชันของเรา ไฟล์ที่จะดำเนินการทุกครั้งที่เราเรียกใช้แอป โดยทั่วไป ไฟล์นี้จะประกาศหน้าต่างต่างๆ ของแอป และสามารถเลือกใช้เพื่อกำหนดตัวฟังเหตุการณ์ทั่วโลกโดยใช้โมดูล IPC ของ Electron
- กระบวนการ Renderer : ตัวควบคุมสำหรับหน้าต่างที่กำหนดในแอปพลิเคชันของเรา แต่ละหน้าต่างสร้างกระบวนการ Renderer ของตัวเอง
เพื่อความชัดเจนของโค้ด ควรใช้ไฟล์แยกกันสำหรับกระบวนการ Renderer แต่ละขั้นตอน ในการกำหนดกระบวนการหลักสำหรับแอปของเรา เราจะเปิด
src/app.js
และรวมโมดูลapp
เพื่อเริ่มแอป และโมดูลbrowser-window
เพื่อสร้างหน้าต่างต่างๆ ของแอปของเรา (ทั้งส่วนของแกนอิเล็กตรอน) เช่นนี้:
var app = require('app'), BrowserWindow = require('browser-window');
เมื่อแอปเริ่มทำงานจริง แอปจะเริ่มต้นเหตุการณ์ ready
งาน ซึ่งเราสามารถผูกไว้ได้ ณ จุดนี้ เราสามารถยกตัวอย่างหน้าต่างหลักของแอพของเรา:
var mainWindow = null; app.on('ready', function() { mainWindow = new BrowserWindow({ width: 1024, height: 768 }); mainWindow.loadUrl('file://' + __dirname + '/windows/main/main.html'); mainWindow.openDevTools(); });
ประเด็นสำคัญ:
- เราสร้างหน้าต่างใหม่โดยการสร้างอินสแตนซ์ใหม่ของวัตถุ
BrowserWindow
- ใช้วัตถุเป็นอาร์กิวเมนต์เดียว ทำให้เราสามารถกำหนดการตั้งค่าต่างๆ ได้ ซึ่งได้แก่ ความกว้าง และ ความสูง เริ่มต้นของหน้าต่าง
- อินสแตนซ์ของหน้าต่างมี
loadUrl()
ซึ่งช่วยให้เราโหลดเนื้อหาของไฟล์ HTML จริงในหน้าต่างปัจจุบันได้ ไฟล์ HTML สามารถเป็นได้ทั้งแบบ โลคัล หรือ แบบรีโมต - อินสแตนซ์หน้าต่างมี
openDevTools()
ที่ไม่บังคับ ซึ่งช่วยให้เราเปิดอินสแตนซ์ของเครื่องมือ Chrome Dev ในหน้าต่างปัจจุบันเพื่อจุดประสงค์ในการดีบัก
ต่อไป เราควรจัดระเบียบโค้ดของเราเล็กน้อย ฉันแนะนำให้สร้างโฟลเดอร์ windows/
ในโฟลเดอร์ src/
ของเรา และตำแหน่งที่เราสามารถสร้างโฟลเดอร์ย่อยสำหรับแต่ละหน้าต่างได้ดังนี้:
my-app |- src/ |-- windows/ |--- main/ |---- main.controller.js |---- main.html |---- main.view.js
… โดยที่ main.controller.js
จะมีตรรกะ "ฝั่งเซิร์ฟเวอร์" ของแอปพลิเคชันของเรา และ main.view.js
จะมีตรรกะ "ฝั่งไคลเอ็นต์" ของแอปพลิเคชันของเรา
ไฟล์ main.html
เป็นเพียงหน้าเว็บ HTML5 ดังนั้นเราจึงสามารถเริ่มต้นได้ดังนี้:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Password Keychain</title> </head> <body> <h1>Password Keychain</h1> </body> </html>
ณ จุดนี้ แอปของเราควรจะพร้อมทำงาน เพื่อทดสอบ เราสามารถพิมพ์สิ่งต่อไปนี้ในเทอร์มินัลของเราที่รูทของโฟลเดอร์ src
:
$ electron .
เราสามารถทำให้กระบวนการนี้เป็นไปโดยอัตโนมัติโดยกำหนดสคริปต์
start
ของไฟล์ package.son
การสร้างแอปเดสก์ท็อปพวงกุญแจรหัสผ่าน
ในการสร้างแอปพลิเคชันพวงกุญแจรหัสผ่าน เราต้องการ: - วิธีเพิ่ม สร้าง และบันทึกรหัสผ่าน - วิธีที่สะดวกในการคัดลอกและลบรหัสผ่าน
การสร้างและบันทึกรหัสผ่าน
แบบฟอร์มง่าย ๆ ก็เพียงพอที่จะใส่รหัสผ่านใหม่ เพื่อประโยชน์ในการสาธิตการสื่อสารระหว่างหน้าต่างหลายบานในอิเล็กตรอน ให้เริ่มต้นด้วยการเพิ่มหน้าต่างที่สองในแอปพลิเคชันของเรา ซึ่งจะแสดงแบบฟอร์ม "แทรก" เนื่องจากเราจะเปิดและปิดหน้าต่างนี้หลายครั้ง เราควรสรุปตรรกะในเมธอด เพื่อให้เราสามารถเรียกมันได้เมื่อจำเป็น:
function createInsertWindow() { insertWindow = new BrowserWindow({ width: 640, height: 480, show: false }); insertWindow.loadUrl('file://' + __dirname + '/windows/insert/insert.html'); insertWindow.on('closed',function() { insertWindow = null; }); }
ประเด็นสำคัญ:
- เราจะต้องตั้งค่าคุณสมบัติการ แสดง เป็นเท็จ ในอ็อบเจ็กต์ตัวเลือกของตัวสร้างหน้าต่างเบราว์เซอร์ เพื่อป้องกันไม่ให้หน้าต่างเปิดโดยค่าเริ่มต้นเมื่อแอปพลิเคชันเริ่มทำงาน
- เราจะต้องทำลายอินสแตนซ์ของ BrowserWindow ทุกครั้งที่หน้าต่างเริ่มทำงานกับเหตุการณ์ที่ ปิด
การเปิดและปิดหน้าต่าง “แทรก”
แนวคิดคือการสามารถเรียกใช้หน้าต่าง "แทรก" เมื่อผู้ใช้คลิกปุ่มในหน้าต่าง "หลัก" ในการดำเนินการนี้ เราจะต้องส่งข้อความจากหน้าต่างหลักไปยังกระบวนการหลักเพื่อสั่งให้เปิดหน้าต่างแทรก เราสามารถทำได้โดยใช้โมดูล IPC ของอิเล็กตรอน โมดูล IPC มีสองรูปแบบ:
- หนึ่งสำหรับกระบวนการหลัก อนุญาตให้แอปสมัครรับข้อความที่ส่งจาก windows
- หนึ่งสำหรับกระบวนการ Renderer ทำให้แอปสามารถส่งข้อความไปยังกระบวนการหลักได้
แม้ว่าช่องทางการสื่อสารของอิเล็กตรอนส่วนใหญ่จะเป็นแบบทิศทางเดียว แต่ก็สามารถเข้าถึงโมดูล IPC ของกระบวนการหลักในกระบวนการเรนเดอร์ได้โดยใช้โมดูลระยะไกล นอกจากนี้ กระบวนการหลักสามารถส่งข้อความกลับไปยังกระบวนการ Renderer ซึ่งเป็นที่มาของเหตุการณ์โดยใช้เมธอด Event.sender.send()
ในการใช้โมดูล IPC เราแค่ต้องการเช่นเดียวกับโมดูล NPM อื่นๆ ในสคริปต์กระบวนการหลักของเรา:
var ipc = require('ipc');
… แล้วผูกกับเหตุการณ์ด้วย on()
วิธีการ:
ipc.on('toggle-insert-view', function() { if(!insertWindow) { createInsertWindow(); } return (!insertWindow.isClosed() && insertWindow.isVisible()) ? insertWindow.hide() : insertWindow.show(); });
ประเด็นสำคัญ:
- เราสามารถตั้งชื่อเหตุการณ์ได้ตามที่เราต้องการ ตัวอย่างเป็นเพียงการกำหนดโดยพลการ
- อย่าลืมตรวจสอบว่ามีการสร้างอินสแตนซ์ของ BrowserWindow ไว้แล้วหรือไม่ หากไม่ได้สร้างอินสแตนซ์นั้น
- อินสแตนซ์ BrowserWindow มีวิธีการที่เป็นประโยชน์บางประการ:
- isClosed() คืนค่าบูลีน ไม่ว่าหน้าต่างจะอยู่ในสถานะ
closed
หรือไม่ - isVisible() : คืนค่าบูลีน ไม่ว่าหน้าต่างจะมองเห็นได้ในขณะนี้หรือไม่
- show() / hide() : วิธีอำนวยความสะดวกในการแสดงและซ่อนหน้าต่าง
- isClosed() คืนค่าบูลีน ไม่ว่าหน้าต่างจะอยู่ในสถานะ
ตอนนี้ เราต้องเริ่มการทำงานของเหตุการณ์นั้นจากกระบวนการเรนเดอร์ เราจะสร้างไฟล์สคริปต์ใหม่ชื่อ main.view.js
และเพิ่มลงในหน้า HTML ของเราเหมือนกับที่เราทำกับสคริปต์ทั่วไป:
<script src="./main.view.js"></script>
การโหลดไฟล์สคริปต์ผ่านแท็ก
script
HTML จะโหลดไฟล์นี้ในบริบทฝั่งไคลเอ็นต์ ซึ่งหมายความว่า ตัวอย่างเช่น ตัวแปรส่วนกลางพร้อมใช้งานผ่านwindow.<varname>
ในการโหลดสคริปต์ในบริบทฝั่งเซิร์ฟเวอร์ เราสามารถใช้เมธอดrequire()
โดยตรงในหน้า HTML ของเรา:require('./main.controller.js');
.
แม้ว่าสคริปต์จะโหลดในบริบท ฝั่งไคลเอ็นต์ แต่เรายังคงสามารถเข้าถึงโมดูล IPC สำหรับกระบวนการ Renderer ได้ในลักษณะเดียวกับที่เราสามารถทำได้สำหรับกระบวนการหลัก จากนั้นจึงส่งกิจกรรมของเราดังนี้:
var ipc = require('ipc'); angular .module('Utils', []) .directive('toggleInsertView', function() { return function(scope, el) { el.bind('click', function(e) { e.preventDefault(); ipc.send('toggle-insert-view'); }); }; });
นอกจากนี้ยังมีเมธอด sendSync() ในกรณีที่เราจำเป็นต้องส่งกิจกรรมของเราพร้อมกัน
ตอนนี้ สิ่งที่เราต้องทำเพื่อเปิดหน้าต่าง "แทรก" คือการสร้างปุ่ม HTML ที่มีคำสั่ง Angular ที่ตรงกัน:
<div ng-controller="MainCtrl as vm"> <button toggle-insert-view class="mdl-button"> <i class="material-icons">add</i> </button> </div>
และเพิ่มคำสั่งนั้นเป็นการพึ่งพาตัวควบคุม Angular ของหน้าต่างหลัก:
angular .module('MainWindow', ['Utils']) .controller('MainCtrl', function() { var vm = this; });
กำลังสร้างรหัสผ่าน
เพื่อให้ง่ายขึ้น เราสามารถใช้โมดูล NPM uuid
เพื่อสร้าง ID เฉพาะที่จะทำหน้าที่เป็นรหัสผ่านสำหรับวัตถุประสงค์ของบทช่วยสอนนี้ เราสามารถติดตั้งได้เหมือนกับโมดูล NPM อื่นๆ โดยกำหนดให้อยู่ในสคริปต์ 'Utils' ของเรา จากนั้นจึงสร้างโรงงานอย่างง่ายที่จะส่งคืน ID เฉพาะ:
var uuid = require('uuid'); angular .module('Utils', []) ... .factory('Generator', function() { return { create: function() { return uuid.v4(); } }; })
ตอนนี้ สิ่งที่เราต้องทำคือสร้างปุ่มในมุมมองการแทรก และแนบคำสั่งที่จะฟังการคลิกเหตุการณ์บนปุ่มและเรียกเมธอด create()
<!-- in insert.html --> <button generate-password class="mdl-button">generate</button>
// in Utils.js angular .module('Utils', []) ... .directive('generatePassword', ['Generator', function(Generator) { return function(scope, el) { el.bind('click', function(e) { e.preventDefault(); if(!scope.vm.formData) scope.vm.formData = {}; scope.vm.formData.password = Generator.create(); scope.$apply(); }); }; }])
การบันทึกรหัสผ่าน
ณ จุดนี้ เราต้องการเก็บรหัสผ่านของเรา โครงสร้างข้อมูลสำหรับการป้อนรหัสผ่านของเราค่อนข้างง่าย:
{ "id": String "description": String, "username": String, "password": String }
สิ่งที่เราต้องมีก็คือฐานข้อมูลในหน่วยความจำบางประเภทที่สามารถเลือกที่จะซิงค์กับไฟล์เพื่อสำรองข้อมูลได้ เพื่อจุดประสงค์นี้ Loki.js ดูเหมือนจะเป็นตัวเลือกในอุดมคติ มันทำในสิ่งที่เราต้องการสำหรับจุดประสงค์ของแอปพลิเคชั่นนี้อย่างแท้จริง และนำเสนอคุณสมบัติ มุมมองไดนามิก ที่ด้านบน ทำให้เราสามารถทำสิ่งต่าง ๆ ที่คล้ายกับโมดูลการรวมของ MongoDB
มุมมองแบบไดนามิกไม่มีฟังก์ชันทั้งหมดที่โมดูลการรวมของ MongodDB มี โปรดดูเอกสารประกอบสำหรับข้อมูลเพิ่มเติม
เริ่มต้นด้วยการสร้างแบบฟอร์ม HTML อย่างง่าย:
<div class="insert" ng-controller="InsertCtrl as vm"> <form name="insertForm" no-validate> <fieldset ng-disabled="!vm.loaded"> <div class="mdl-textfield"> <input class="mdl-textfield__input" type="text" ng-model="vm.formData.description" required /> <label class="mdl-textfield__label" for="description">Description...</label> </div> <div class="mdl-textfield"> <input class="mdl-textfield__input" type="text" ng-model="vm.formData.username" /> <label class="mdl-textfield__label" for="username">Username...</label> </div> <div class="mdl-textfield"> <input class="mdl-textfield__input" type="password" ng-model="vm.formData.password" required /> <label class="mdl-textfield__label" for="password">Password...</label> </div> <div class=""> <button generate-password class="mdl-button">generate</button> <button toggle-insert-view class="mdl-button">cancel</button> <button save-password class="mdl-button" ng-disabled="insertForm.$invalid">save</button> </div> </fieldset> </form> </div>
และตอนนี้ มาเพิ่มตรรกะของ JavaScript เพื่อจัดการกับการโพสต์และการบันทึกเนื้อหาของแบบฟอร์ม:

var loki = require('lokijs'), path = require('path'); angular .module('Utils', []) ... .service('Storage', ['$q', function($q) { this.db = new loki(path.resolve(__dirname, '../..', 'app.db')); this.collection = null; this.loaded = false; this.init = function() { var d = $q.defer(); this.reload() .then(function() { this.collection = this.db.getCollection('keychain'); d.resolve(this); }.bind(this)) .catch(function(e) { // create collection this.db.addCollection('keychain'); // save and create file this.db.saveDatabase(); this.collection = this.db.getCollection('keychain'); d.resolve(this); }.bind(this)); return d.promise; }; this.addDoc = function(data) { var d = $q.defer(); if(this.isLoaded() && this.getCollection()) { this.getCollection().insert(data); this.db.saveDatabase(); d.resolve(this.getCollection()); } else { d.reject(new Error('DB NOT READY')); } return d.promise; }; }) .directive('savePassword', ['Storage', function(Storage) { return function(scope, el) { el.bind('click', function(e) { e.preventDefault(); if(scope.vm.formData) { Storage .addDoc(scope.vm.formData) .then(function() { // reset form & close insert window scope.vm.formData = {}; ipc.send('toggle-insert-view'); }); } }); }; }])
ประเด็นสำคัญ:
- ก่อนอื่นเราต้องเริ่มต้นฐานข้อมูล กระบวนการนี้เกี่ยวข้องกับการสร้างอินสแตนซ์ใหม่ของ Loki Object โดยให้พาธไปยังไฟล์ฐานข้อมูลเป็นอาร์กิวเมนต์ ค้นหาว่ามีไฟล์สำรองนั้นอยู่หรือไม่ สร้างหากจำเป็น (รวมถึงคอลเล็กชัน 'Keychain') แล้วโหลดเนื้อหาของ ไฟล์นี้ในหน่วยความจำ
- เราสามารถดึงข้อมูลคอลเลกชันเฉพาะในฐานข้อมูลด้วย
getCollection()
- วัตถุคอลเลกชันแสดงวิธีการต่างๆ รวมถึงวิธีการ
insert()
ซึ่งช่วยให้เราสามารถเพิ่มเอกสารใหม่ลงในคอลเลกชันได้ - เพื่อคงเนื้อหาฐานข้อมูลไว้ในไฟล์ ออบเจ็กต์ Loki จะแสดง
saveDatabase()
- เราจะต้องรีเซ็ตข้อมูลของแบบฟอร์มและส่งเหตุการณ์ IPC เพื่อบอกให้กระบวนการหลักปิดหน้าต่างเมื่อบันทึกเอกสารแล้ว
ตอนนี้เรามีแบบฟอร์มง่ายๆ ที่ช่วยให้เราสร้างและบันทึกรหัสผ่านใหม่ได้ กลับไปที่มุมมองหลักเพื่อแสดงรายการเหล่านี้
แสดงรายการรหัสผ่าน
บางสิ่งต้องเกิดขึ้นที่นี่:
- เราจำเป็นต้องได้รับเอกสารทั้งหมดในคอลเลกชันของเรา
- เราจำเป็นต้องแจ้งมุมมองหลักทุกครั้งที่มีการบันทึกรหัสผ่านใหม่ เพื่อให้สามารถรีเฟรชมุมมองได้
เราสามารถเรียกรายการเอกสารโดยเรียก getCollection()
บนอ็อบเจกต์ Loki เมธอดนี้ส่งคืนอ็อบเจ็กต์ที่มีคุณสมบัติที่เรียกว่า data ซึ่งเป็นอาร์เรย์ของเอกสารทั้งหมดในคอลเล็กชันนั้น:
this.getCollection = function() { this.collection = this.db.getCollection('keychain'); return this.collection; }; this.getDocs = function() { return (this.getCollection()) ? this.getCollection().data : null; };
จากนั้นเราสามารถเรียก getDocs() ในตัวควบคุมเชิงมุมและดึงรหัสผ่านทั้งหมดที่จัดเก็บไว้ในฐานข้อมูล หลังจากที่เราเริ่มต้นใช้งาน:
angular .module('MainView', ['Utils']) .controller('MainCtrl', ['Storage', function(Storage) { var vm = this; vm.keychain = null; Storage .init() .then(function(db) { vm.keychain = db.getDocs(); }); });
เทมเพลต Angular เล็กน้อย และเรามีรายการรหัสผ่าน:
<tr ng-repeat="item in vm.keychain track by $index" class="item--{{$index}}"> <td class="mdl-data-table__cell--non-numeric">{{item.description}}</td> <td>{{item.username || 'n/a'}}</td> <td> <span ng-repeat="n in [1,2,3,4,5,6]">•</span> </td> <td> <a href="#" copy-password="{{$index}}">copy</a> <a href="#" remove-password="{{item}}">remove</a> </td> </tr>
คุณลักษณะที่เพิ่มเข้ามาที่ดีคือการรีเฟรชรายการรหัสผ่านหลังจากใส่รหัสผ่านใหม่ สำหรับสิ่งนี้ เราสามารถใช้โมดูล IPC ของอิเล็กตรอนได้ ดังที่ได้กล่าวไว้ก่อนหน้านี้ โมดูล IPC ของกระบวนการหลักสามารถเรียกในกระบวนการเรนเดอร์เพื่อเปลี่ยนให้เป็นกระบวนการฟัง โดยใช้โมดูลระยะไกล นี่คือตัวอย่างวิธีใช้งานใน main.view.js
:
var remote = require('remote'), remoteIpc = remote.require('ipc'); angular .module('MainView', ['Utils']) .controller('MainCtrl', ['Storage', function(Storage) { var vm = this; vm.keychain = null; Storage .init() .then(function(db) { vm.keychain = db.getDocs(); remoteIpc.on('update-main-view', function() { Storage .reload() .then(function() { vm.keychain = db.getDocs(); }); }); }); }]);
ประเด็นสำคัญ:
- เราจะต้องใช้โมดูลระยะไกลโดยใช้วิธี
require()
ของตัวเองเพื่อกำหนดให้ใช้โมดูล IPC ระยะไกลจากกระบวนการหลัก - จากนั้นเราสามารถตั้งค่า Renderer Process ของเราให้เป็นผู้ฟังเหตุการณ์ผ่านเมธอด
on()
และเชื่อมโยงฟังก์ชันการเรียกกลับเข้ากับเหตุการณ์เหล่านี้
มุมมองแทรกจะรับผิดชอบในการดำเนินการกิจกรรมนี้ทุกครั้งที่มีการบันทึกเอกสารใหม่:
Storage .addDoc(scope.vm.formData) .then(function() { // refresh list in main view ipc.send('update-main-view'); // reset form & close insert window scope.vm.formData = {}; ipc.send('toggle-insert-view'); });
กำลังคัดลอกรหัสผ่าน
โดยปกติแล้ว ไม่ควรแสดงรหัสผ่านเป็นข้อความธรรมดา แต่เราจะซ่อนและให้ปุ่มอำนวยความสะดวกที่อนุญาตให้ผู้ใช้คัดลอกรหัสผ่านโดยตรงสำหรับรายการเฉพาะ
ที่นี่อีกครั้ง Electron เข้ามาช่วยเหลือเราโดยมอบโมดูลคลิปบอร์ดที่มีวิธีการง่ายๆ ในการคัดลอกและวาง ไม่เพียงแต่เนื้อหาที่เป็นข้อความเท่านั้น แต่ยังรวมถึงรูปภาพและโค้ด HTML ด้วย:
var clipboard = require('clipboard'); angular .module('Utils', []) ... .directive('copyPassword', [function() { return function(scope, el, attrs) { el.bind('click', function(e) { e.preventDefault(); var text = (scope.vm.keychain[attrs.copyPassword]) ? scope.vm.keychain[attrs.copyPassword].password : ''; // atom's clipboard module clipboard.clear(); clipboard.writeText(text); }); }; }]);
เนื่องจากรหัสผ่านที่สร้างขึ้นจะเป็นสตริงธรรมดา เราจึงสามารถใช้ writeText()
เพื่อคัดลอกรหัสผ่านไปยังคลิปบอร์ดของระบบ จากนั้น เราสามารถอัปเดต HTML มุมมองหลักของเรา และเพิ่มปุ่มคัดลอกด้วยคำสั่ง copy-password
ระบุดัชนีของอาร์เรย์ของรหัสผ่าน:
<a href="#" copy-password="{{$index}}">copy</a>
การลบรหัสผ่าน
ผู้ใช้ปลายทางของเราอาจต้องการสามารถลบรหัสผ่านได้ ในกรณีที่รหัสผ่านล้าสมัย ในการทำเช่นนี้ สิ่งที่เราต้องทำคือเรียกเมธอด remove()
บนคอลเลกชั่นพวงกุญแจ เราจำเป็นต้องจัดเตรียมเอกสารทั้งหมดให้กับเมธอด 'remove()' เช่น:
this.removeDoc = function(doc) { return function() { var d = $q.defer(); if(this.isLoaded() && this.getCollection()) { // remove the doc from the collection & persist changes this.getCollection().remove(doc); this.db.saveDatabase(); // inform the insert view that the db content has changed ipc.send('reload-insert-view'); d.resolve(true); } else { d.reject(new Error('DB NOT READY')); } return d.promise; }.bind(this); };
เอกสารประกอบของ Loki.js ระบุว่าเราสามารถลบเอกสารด้วยรหัสของมันได้ แต่ดูเหมือนว่าจะไม่ทำงานตามที่คาดไว้
การสร้างเมนูเดสก์ท็อป
อิเลคตรอนผสานรวมกับสภาพแวดล้อมเดสก์ท็อป OS ของเราอย่างราบรื่นเพื่อมอบรูปลักษณ์และสัมผัสประสบการณ์ผู้ใช้ "ดั้งเดิม" ให้กับแอปของเรา ดังนั้น Electron จึงมาพร้อมกับโมดูล Menu ซึ่งใช้สำหรับสร้างโครงสร้างเมนูเดสก์ท็อปที่ซับซ้อนสำหรับแอปของเราโดยเฉพาะ
โมดูลเมนูเป็นหัวข้อที่กว้างใหญ่และเกือบจะสมควรได้รับบทช่วยสอนของตัวเอง ฉันขอแนะนำอย่างยิ่งให้คุณอ่านบทแนะนำการบูรณาการสภาพแวดล้อมเดสก์ท็อปของอิเล็กตรอนเพื่อค้นหาคุณสมบัติทั้งหมดของโมดูลนี้
สำหรับขอบเขตของบทช่วยสอนปัจจุบันนี้ เราจะดูวิธีสร้างเมนูแบบกำหนดเอง เพิ่มคำสั่งที่กำหนดเองลงไป และใช้คำสั่ง quit มาตรฐาน
การสร้างและกำหนดเมนูแบบกำหนดเองให้กับแอพของเรา
โดยทั่วไป ตรรกะ JavaScript สำหรับเมนูอิเล็กตรอนจะอยู่ในไฟล์สคริปต์หลักของแอปของเรา ซึ่งกำหนดกระบวนการหลักของเรา อย่างไรก็ตาม เราสามารถแยกเป็นไฟล์แยก และเข้าถึงโมดูลเมนูผ่านโมดูลระยะไกลได้:
var remote = require('remote'), Menu = remote.require('menu');
ในการกำหนดเมนูอย่างง่าย เราจะต้องใช้ buildFromTemplate()
:
var appMenu = Menu.buildFromTemplate([ { label: 'Electron', submenu: [{ label: 'Credits', click: function() { alert('Built with Electron & Loki.js.'); } }] } ]);
รายการแรกในอาร์เรย์จะใช้เป็นรายการเมนู "เริ่มต้น" เสมอ
ค่าของคุณสมบัติ
label
ไม่สำคัญมากนักสำหรับรายการเมนูเริ่มต้น ในโหมด dev จะแสดงElectron
เสมอ เราจะมาดูวิธีการกำหนดชื่อที่กำหนดเองให้กับรายการเมนูเริ่มต้นในขั้นตอนการสร้าง
สุดท้าย เราต้องกำหนดเมนูแบบกำหนดเองนี้เป็นเมนูเริ่มต้นสำหรับแอปของเราด้วย setApplicationMenu()
:
Menu.setApplicationMenu(appMenu);
แป้นพิมพ์ลัดการทำแผนที่
อิเลคตรอนจัดเตรียม "ตัวเร่งความเร็ว" ซึ่งเป็นชุดของสตริงที่กำหนดไว้ล่วงหน้าซึ่งจับคู่กับชุดแป้นพิมพ์จริง เช่น Command+A
หรือ Ctrl+Shift+Z
ตัวเร่ง
Command
ไม่ทำงานบน Windows หรือ Linux สำหรับแอปพลิเคชันพวงกุญแจรหัสผ่าน เราควรเพิ่มรายการเมนูFile
โดยเสนอคำสั่งสองคำสั่ง:
- สร้างรหัสผ่าน : เปิดมุมมองแทรกด้วย Cmd (หรือ Ctrl) + N
- ออก : ออกจากแอปด้วย Cmd (หรือ Ctrl) + Q
... { label: 'File', submenu: [ { label: 'Create Password', accelerator: 'CmdOrCtrl+N', click: function() { ipc.send('toggle-insert-view'); } }, { type: 'separator' // to create a visual separator }, { label: 'Quit', accelerator: 'CmdOrCtrl+Q', selector: 'terminate:' // OS X only!!! } ] } ...
ประเด็นสำคัญ:
- เราสามารถเพิ่มตัวคั่นด้วยภาพโดยการเพิ่มรายการในอาร์เรย์ด้วยคุณสมบัติ
type
ที่ตั้งค่าเป็นseparator
- ตัวเร่ง
CmdOrCtrl
เข้ากันได้กับทั้งแป้นพิมพ์ Mac และ PC - คุณสมบัติ
selector
ใช้ได้กับ OSX เท่านั้น!
ออกแบบแอพของเรา
คุณอาจสังเกตเห็นตัวอย่างโค้ดต่างๆ ที่อ้างอิงถึงชื่อคลาสที่ขึ้นต้นด้วย mdl-
สำหรับจุดประสงค์ของบทช่วยสอนนี้ ฉันเลือกใช้เฟรมเวิร์ก Material Design Lite UI แต่คุณสามารถใช้เฟรมเวิร์ก UI ใดก็ได้ตามต้องการ
อะไรก็ตามที่เราสามารถทำได้ด้วย HTML5 สามารถทำได้ในอิเล็กตรอน เพียงจำไว้ว่าขนาดไบนารีของแอปที่เพิ่มขึ้น และปัญหาด้านประสิทธิภาพที่อาจเกิดขึ้นหากคุณใช้ไลบรารีของบุคคลที่สามมากเกินไป
แอพบรรจุภัณฑ์อิเล็กตรอนเพื่อการจัดจำหน่าย
คุณสร้างแอป Electron ขึ้นมา มันดูดีมาก คุณเขียนการทดสอบ e2e ด้วย Selenium และ WebDriver และคุณพร้อมที่จะเผยแพร่ไปทั่วโลก!
แต่คุณยังต้องการปรับแต่งให้เหมาะกับแต่ละบุคคล ตั้งชื่อที่กำหนดเองให้กับชื่ออื่นที่ไม่ใช่ "อิเลคตรอน" ที่เป็นค่าเริ่มต้น และอาจให้ไอคอนแอปพลิเคชันที่กำหนดเองสำหรับทั้งแพลตฟอร์ม Mac และ PC
สร้างด้วยอึก
ทุกวันนี้ มีปลั๊กอิน Gulp สำหรับทุกสิ่งที่เราคิดออก ทั้งหมดที่ฉันต้องทำคือพิมพ์ gulp electron
ใน Google และแน่นอนว่ามีปลั๊กอิน gulp-electron!
ปลั๊กอินนี้ค่อนข้างใช้งานง่าย ตราบใดที่ยังคงรักษาโครงสร้างโฟลเดอร์ที่มีรายละเอียดไว้ตอนต้นของบทช่วยสอนนี้ ถ้าไม่อย่างนั้น คุณอาจต้องย้ายของไปรอบๆ สักหน่อย
ปลั๊กอินนี้สามารถติดตั้งได้เหมือนกับปลั๊กอิน Gulp อื่นๆ:
$ npm install gulp-electron --save-dev
จากนั้นเราสามารถกำหนดงานอึกของเราได้ดังนี้:
var gulp = require('gulp'), electron = require('gulp-electron'), info = require('./src/package.json'); gulp.task('electron', function() { gulp.src("") .pipe(electron({ src: './src', packageJson: info, release: './dist', cache: './cache', version: 'v0.31.2', packaging: true, platforms: ['win32-ia32', 'darwin-x64'], platformResources: { darwin: { CFBundleDisplayName: info.name, CFBundleIdentifier: info.bundle, CFBundleName: info.name, CFBundleVersion: info.version }, win: { "version-string": info.version, "file-version": info.version, "product-version": info.version } } })) .pipe(gulp.dest("")); });
ประเด็นสำคัญ:
- โฟลเดอร์
src/
ต้องไม่เหมือนกับโฟลเดอร์ที่มี Gulpfile.js หรือโฟลเดอร์เดียวกับโฟลเดอร์การแจกจ่าย - เราสามารถกำหนดแพลตฟอร์มที่เราต้องการส่งออกผ่านอาร์เรย์ของ
platforms
- เราควรกำหนดโฟลเดอร์
cache
ที่จะดาวน์โหลดไบนารีของอิเล็กตรอนเพื่อให้สามารถรวมเข้ากับแอปของเราได้ - ต้องส่งเนื้อหาของไฟล์ package.json ของแอปไปยังงานอึกผ่านคุณสมบัติ
packageJson
- มีคุณสมบัติ
packaging
ที่เป็นตัวเลือก ทำให้เราสามารถสร้างไฟล์ zip ของแอพที่สร้างขึ้นได้ - สำหรับแต่ละแพลตฟอร์มมีชุด "ทรัพยากรแพลตฟอร์ม" ที่แตกต่างกันซึ่งสามารถกำหนดได้
การเพิ่มไอคอนแอพ
หนึ่งในคุณสมบัติ platformResources
คือคุณสมบัติ icon
ซึ่งช่วยให้เราสามารถกำหนดไอคอนที่กำหนดเองสำหรับแอปของเรา:
"icon": "keychain.ico"
OS X ต้องใช้ไอคอนที่มีนามสกุลไฟล์ .
.icns
มีเครื่องมือออนไลน์มากมายที่ช่วยให้เราสามารถแปลงไฟล์ ..png
เป็น.ico
และ ..icns
ได้ฟรี
บทสรุป
ในบทความนี้ เราได้เพียงแต่ขีดข่วนพื้นผิวของสิ่งที่อิเล็กตรอนสามารถทำได้จริง ลองนึกถึงแอปดีๆ อย่าง Atom หรือ Slack ว่าเป็นแรงบันดาลใจที่คุณสามารถใช้เครื่องมือนี้ได้
ฉันหวังว่าคุณจะพบว่าบทช่วยสอนนี้มีประโยชน์ โปรดแสดงความคิดเห็นและแบ่งปันประสบการณ์ของคุณกับ Electron!