Desktop app với Electron phần 2 : tạo menu
Ở phần 1 mình đã giới thiệu về Electron và những bước cơ bản để tạo một app desktop chạy được trên môi trường Window. Để nhắc lại một chút thì Electron (trước kia gọi là Atom-shell ) và một bộ khung để xây dựng desktop app dựa trên công nghệ NodeJS , được phát triển bởi Github và đã được ...
Ở phần 1 mình đã giới thiệu về Electron và những bước cơ bản để tạo một app desktop chạy được trên môi trường Window. Để nhắc lại một chút thì Electron (trước kia gọi là Atom-shell) và một bộ khung để xây dựng desktop app dựa trên công nghệ NodeJS, được phát triển bởi Github và đã được ứng dụng cho rất nhiều app nổi tiếng mà trong đó phải kể đến trình soạn thảo Atom hay Slack app trên desktop.
Phần này mình sẽ hướng dẫn cách tạo menu cho app từ lần trước, cùng với những chú ý về Browser Process và Render Process. Phần 1 các bạn có thể xem lại ở đây.
Trong một app xây dựng bằng Electron có 2 loại menu: Application Menu và Context Menu. Application Menu*là menu ở phía trên xổ xuống, bạn tưởng tượng giống như *File , View, Edit của chương trình word,excel.... Context menu là menu hiện ra khi ấn chuột phải vào cửa sổ app.
Để tạo context menu thì cần hiểu về Browser Process và Render Process nên mình để cuối bài, trước hãy chúng ta sẽ tạo một Application Menu trước.
Đầu tiên chúng ta sẽ dòng gói Menu. Trong app.js mình sẽ thêm
var Menu = require('menu');
Kế đó, bên trong hàm app.on('ready',function( .... ){}) mình sẽ khởi tạo Menu theo cấu trúc template.
var menu = Menu.buildFromTemplate(menuTemplate); Menu.setApplicationMenu(menu);
Ở đây menuTemplate sẽ được định nghĩa gồm các menu con và các đầu mục bên trong đó:
var menuTemplate = [ { label: 'Mdpreview', submenu: [ { label: 'Quit', accelerator: 'Ctrl+Q', click: function () {app.quit();} } ] }, { label: 'View', submenu: [ { label: 'Reload', accelerator: 'Ctrl+R', click: function() { BrowserWindow.getFocusedWindow().reloadIgnoringCache(); } }, { label: 'Toggle DevTools', accelerator: 'Alt+Ctrl+I', click: function() { BrowserWindow.getFocusedWindow().toggleDevTools(); } } ] } ];
Ở đây bạn có thể thấy cả các phím tắt cho đầu mục như Ctrl+Q hay Ctrl+R. Menu như trên đây chỉ có 3 đề mục đơn giản nhất là Quit, Reload, và Toggle DevTools .
Electron là một dạng app multiprocess. Có 2 process chính là Browser Process và Renderer Process. Browser Process chuyên làm các xử lý tính toán nặng, còn Renderer Process chuyên làm nhiệm vụ hiển thị. App của chúng ta có địnhg nghĩa "main": "app.js", ở trong package.json, vì vậy file app.js là dùng cho Browser Process.
Khi muốn tạo context menu, mình sẽ tạo sẵn một menu ẩn và hiển thị nó chỉ khi user click chuột phải. Context menu này sẽ được tạo ở Renderer Process bằng cách include một file js vào index.html
<script src="context.js"></script>
Bên trong file context.js sẽ là định nghĩa cho context menu.
'use strict'; var remote = require('remote'); var Menu = remote.require('menu'); var MenuItem = remote.require('menu-item'); var menu = new Menu(); menu.append(new MenuItem({ label: 'ping', click: function() { console.log('ping'); } })); menu.append(new MenuItem({ type: 'separator' })); menu.append(new MenuItem({ label: 'pong', type: 'checkbox', checked: true, click: function() { console.log('pong'); } }));
Cuối cùng là bind vào thao tác chuột của user
window.addEventListener('contextmenu', function (e) { e.preventDefault(); menu.popup(remote.getCurrentWindow()); }, false);
Nếu bạn để ý thì ở file context.jsnói trên đã dùng một package tên là remote. Bạn sẽ tự hỏi tại sao lại cần remote ở đây.
Như đã nói ở trên thì context.js là thuộc Renderer Process, tuy nhiên khi dùng remote để gọi các package menu và menu-item thì 2 package này sẽ là của Browser Process gọi sang. Như vậy phẩn xử lý của menu và menu-item được giữ ở ngoài Renderer Process giúp phần xử lý render chỉ gồm những logic liên quan đến view, trở nên nhẹ nhàng và mượt hơn.
Ở phần tiếp mình sẽ nói tiếp về remote cụ thể hơn, cùng với cách đóng gói app và release.
Source code cho phần này bạn có thể tìm thấy ở link dưới đây :
////////////////////////////// context.js ////////////////////////////// | |
'use strict'; | |
var remote = require('remote'); | |
var Menu = remote.require('menu'); | |
var MenuItem = remote.require('menu-item'); | |
var menu = new Menu(); | |
menu.append(new MenuItem({ | |
label: 'ping', | |
click: function() { | |
console.log('ping'); | |
} | |
})); | |
menu.append(new MenuItem({ | |
type: 'separator' | |
})); | |
menu.append(new MenuItem({ | |
label: 'pong', | |
type: 'checkbox', checked: true, | |
click: function() { | |
console.log('pong'); | |
} | |
})); | |
window.addEventListener('contextmenu', function (e) { | |
e.preventDefault(); | |
menu.popup(remote.getCurrentWindow()); | |
}, false); | |
////////////////////////////// app.js ////////////////////////////// | |
'use strict'; | |
var app = require('app'); | |
var BrowserWindow = require('browser-window'); | |
var Menu = require('menu'); | |
var mainWindow = null; | |
app.on('window-all-closed', function() { | |
if (process.platform != 'darwin') | |
app.quit(); | |
}); | |
app.on('ready', function() { | |
createApplicationMenu(); | |
openWindow(); | |
}); | |
var openWindow = function(){ | |
mainWindow = new BrowserWindow({awidth: 800, height: 600}); | |
mainWindow.loadUrl('file://' + __dirname + '/index.html'); | |
mainWindow.on('closed', function() { | |
mainWindow = null; | |
}); | |
} | |
var createApplicationMenu = function() { | |
var menuTemplate = [ | |
{ | |
label: 'Mdpreview', | |
submenu: [ | |
{ | |
label: 'Quit', | |
accelerator: 'Ctrl+Q', | |
click: function () {app.quit();} | |
} | |
] | |
}, { | |
label: 'View', | |
submenu: [ | |
{ | |
label: 'Reload', | |
accelerator: 'Ctrl+R', | |
click: function() { | |
BrowserWindow.getFocusedWindow().reloadIgnoringCache(); | |
} | |
}, | |
{ | |
label: 'Toggle DevTools', | |
accelerator: 'Alt+Ctrl+I', | |
click: function() { | |
BrowserWindow.getFocusedWindow().toggleDevTools(); | |
} | |
} | |
] | |
} | |
]; | |
var menu = Menu.buildFromTemplate(menuTemplate); | |
Menu.setApplicationMenu(menu) | |
} |