Время на прочтение6 мин
Количество просмотров161K
Ни для кого не секрет, что в наше время JavaScript стал одним из самых популярных языков программирования. В далекие 90е годы, в момент зарождения языка, когда он был создан с единственной целью добавить интерактивность веб страницам и улучшить процесс взаимодействия с пользователем, кто бы мог подумать, что он достигнет столь небывалых высот. Ведь сейчас на нем можно делать практически все что угодно. Хотите написать сайт: и бэкэнд и фронтэнд на JavaScript? пожалуйста! Хотите написать мобильное приложение на JavaScript? нет проблем. Программируете микроконтроллер – и тут вам на помощь придет JavaScript.
Есть конечно небольшие минусы в подходе использования JavaScript везде, но если поразмыслить, то сколько времени и сил можно сэкономить, изучив всего лишь одни язык, особенно, если то же самое приложение должно работать на разных платформах. Разных платформах говорите? Хм… Точно – разных платформах – теперь JS может позволить себе десктопные приложения для Windows, Linux, Mac, как спросите вы? Ответ прост: встречайте – NW.js.
По первым буквам можно прочитать – Node.js + Webkit, если данные понятия вам пока не знакомы, то скоро вы поймете о чем идет речь.
Node.js – программная платформа, основанная на движке V8, который транслирует наш скрипт в машинный код. Данная платформа была создана в 2009 году преимущественно для работы с бэкэндом сайтов.
WebKit — свободный движок, разработанный компанией Apple. Впервые был анонсирован в составе Safari в 2003 году
Итак, коду, написанному на JS для данной технологии, будут доступны как Node.js модули, так и стандартный браузерный API (соответственно WebKit)
Быстрый старт
Все это конечно хорошо, но с чего же начать? На github можно найти и скачать репозиторий с исходным кодом. Так же здесь можно найти прямые ссылки для скачивания под ту платформу, на которой будет вестись разработка. Помимо прочего нам понадобится установленная node.js.
После того, как необходимое ПО скачано и установлено, вы написали свое приложение на любимом JS (как это сделать читайте далее) и локализовали все в одну папку. Полдела сделано, теперь остается самое сложное и долгое – упаковать все в один файл и подготовить для распространения. Для упрощения вы можете воспользоваться готовыми библиотеками, например nw-builder. Установка библиотеки не составит труда, если вы уже работали с node.js. Как известно, в состав node.js входит менеджер пакетов npm, с которым нужно работать из командной строки. Для того, чтобы поставить какую-либо библиотеку, необходимо выполнить команду:
> npm install [имя_библиотеки] [опции]
Обратите внимание, что библиотеку можно ставить, как локально, так и глобально, для локальной установки используйте опцию —save-dev, для глобальной -g. Таким образом поставим наш сборщик для NW.js глобально, выполнив команду:
> npm install nw-builder -g
Для того, чтобы собрать наше приложение, необходимо выполнить команду (с большим количеством опций можно ознакомиться в документации):
> nwbuild -p [имя_платформы] -o [путь_к_папке_для_собранной_версии] [путь_до_приложения]
В качестве имени платформы могут быть следующие значения: win32, win64, osx32, osx64, linux32, linux64.
Во время разработки нет нужды каждый раз собирать приложение, можно просто запустить его как есть и оно откроется в отдельном окне. Для этого нужно запустить приложение nw.exe из командной строки и передать в качестве параметров путь к папке с вашим приложением. Кроме того, если вы работаете под Windows, можно просто методом drag-n-drop перетащить папку с исходным кодом приложения на JS (обратите внимание, что именно папку целиком) в nw.exe.
Hello, world!
Теперь, когда вы знаете, как запустить приложение, как собрать его в один файл, давайте напишем что-нибудь. По традиции знакомство с новой платформой начинается с написания приложения Hello, world.
Для данного приложения, нам даже не понадобится JavaScript, только HTML. Создадим папку с названием HelloWorld. Поместим внутрь файл index.html со следующей разметкой:
<html>
<head>
<title> Hello, world </title>
</head>
<body>
<div>
Hello, world, from NW.js
</div>
</body>
</html>
Кроме того для каждого приложения под NW.js необходим файл, который обязательно должен называться package.json. Из него будет браться информация для построения приложения. Создадим простейший вариант файла и поместим в папку HelloWorld. Итак:
{
"name": "hello-world",
"version": "1.0.0",
"description": "First application",
"main": "index.html",
"author": "Developer",
"window": {
"toolbar": false,
"width": 500,
"height": 200
}
}
Содержимое файла понятно без пояснений (обратите внимание, что
обязательные поля только main и name
). В main необходимо записать файл с разметкой, который будет являться точкой входа в приложение. Секция window настраивает параметры окна (в данном случае мы отключаем панель инструментов и задаем размеры окна 500×200).
Кроме того, можно настроить такие поля как (за полным списком опций обращайтесь в документацию):
- icon – указываем путь до иконки (переопределить стандартную)
- position – можно указать позицию окна при загрузке (null, center или mouse)
- min_width, min_height, max_width, max_height – ограничение размеров окна
- resizable – логическое значение, которое показывает можно ли пользователю изменять размеры окна
- fullscreen – включить полноэкранный режим
- kiosk – включить режим киоска
- transparent – сделать окно прозрачным
Приложение создано и можно его запустить. После запуска (о том как это сделать, смотри раздел выше) вы должны получить следующее окно:
Приложение написано, но в нем всего один div элемент и совсем нет логики, а что делать, если у нас богатая на элементы разметка и сложная логика? На помощь к нам приходит элемент конфигурационного файла toolbar, который мы установили в false. Для того, чтобы сделать доступными средства отладки, необходимо
установить toolbar в true
. Проделав это при запуске приложения мы получим следующее окно:
После нажатия на кнопку в верхнем правом углу откроется еще одно окно, в котором будут отображены знакомые инструменты разработчика:
Работа с нативными контролами
NW.js позволяет работать с нативными контролами. Рассмотрим работу на примере меню. Для работы с нативным UI контролами в nw.js необходимо использовать модуль nw.gui, который можно подключить следующим образом:
var gui = require('nw.gui');
Общий шаблон для использования контролов:
var element = new gui.ElementName(option);
Таким образом для создания элементов меню можно воспользоваться следующей конструкцией:
var menu = new gui.Menu();
Кроме того любые свойства созданного нами объекта можно легко изменить стандартными конструкциями JS, например так:
menu.title = 'New Title';
Меню создано, теперь нужно его заполнить, для манипуляции дочерними элементами существуют методы:
menu.append(new gui.MenuItem({label: 'Label of menu item'}));
menu.removeAt(0);
Кроме того для более гибкого добавления элементов в menu можно воспользоваться методом insert, в параметрах которого необходимо передать MenuItem и номер позиции, куда его вставить (позиция перед первым элементом соответствует 0).
Для доступа к созданным элементам можно использовать свойство items:
menu.items[0].title = "New title"
Обратите внимание, что нельзя напрямую создавать элементы:
menu.items[2] = new gui.MenuItem(); // НЕПРАВИЛЬНО
Самое главное при работе с нативными контролами, это помнить, что
любая ошибка при работе с ними может привести к краху всего приложения
, поэтому необходимо быть крайне внимательными и по возможности при удалении элементов, также присваивать переменной значение null. Таким образом для удаления контрола, можно выполнить следующее:
control.remove();
control = null;
Для более удобной работы с контролами, они унаследованы от EventEmitter, поэтому хорошая новость в том, что мы можем легко работать с событиями, например так:
menuitem.on('click', function() {
// сделать что-нибудь полезное
});
Меню было создано, но если запустить приложение, то никакого меню вы не увидите. Для отображения меню существует метод popup, в параметрах которого необходимо передать координаты для отображения меню.
Для демонстрации основных возможностей меню добавьте следующий скрипт к созданному ранее проекту Hello, world:
var gui = require('nw.gui');
var menu1 = new gui.Menu();
menu1.append(new gui.MenuItem({label: 'Item 1'}));
var subMenu1 = new gui.Menu();
subMenu1.append(new gui.MenuItem({label: 'Item 2'}));
menu1.append(new gui.MenuItem({
label: "Submenu",
submenu: subMenu1
}));
document.body.addEventListener('contextmenu', function(ev) {
ev.preventDefault();
menu1.popup(ev.x, ev.y);
return false;
});
После запуска приложения, мы можем увидеть созданное контекстное меню для body. Таким образом, мы можем определить контекстное меню для любого элемента.
Итак, теперь кроссплатформенные приложения может создавать каждый, но за все нужно платить. В данном случае мы жертвуем как скоростью, так и занимаемым объемом памяти (собранное приложение получается достаточно большим, более 50 Мб). Список приложений, созданных, используя данную технологию можно найти на github.
Во второй части статьи мы рассмотрим технологию более подробно.
Last Updated :
08 Oct, 2024
Building a JavaScript desktop application is possible using various frameworks and technologies. One popular approach is to use Electron, which is an open-source framework developed by GitHub.
Electron allows you to build cross-platform desktop applications using web technologies such as HTML, CSS, and JavaScript. In this article, we are going to use JavaScript for a desktop app.
Prerequisites:
- Node.js and npm
Steps to build a desktop app using JavaScript and Electron:
Step 1: Install Node.js and npm
Make sure you have Node.js and npm (Node Package Manager) installed on your machine. You can download them from the official website: Node.js
Step 2: Create a new project:
Create a new directory for your project and navigate into it in the terminal.
mkdir my-electron-app
cd my-electron-app
Step 3: Initialize a new Node.js project
Run the following command to initialize a new package.json
file.
npm init -y
Step 4: Install Electron
Install Electron as a development dependency using npm.
npm install electron --save-dev
Step 5: Create main and renderer processes:
In your project directory, create two files, main.js
and index.html
. main.js
will be the main process, and index.html
will be the renderer process.
Example: This example shows the creation of a desktop app.
HTML
<!-- index.html --> <!DOCTYPE html> <html> <head> <title>Desktop App</title> </head> <body> <h1>Hello, Geeks!</h1> </body> </html>
JavaScript
//main.js const { app, BrowserWindow } = require('electron'); let mainWindow; function createWindow() { mainWindow = new BrowserWindow({ width: 400, height: 300, webPreferences: { nodeIntegration: true, }, }); mainWindow.loadFile('index.html'); mainWindow.on('closed', function () { mainWindow = null; }); } app.whenReady().then(createWindow); app.on('window-all-closed', function () { if (process.platform !== 'darwin') app.quit(); }); app.on('activate', function () { if (mainWindow === null) createWindow(); });
Step 6: Update package.json
:
Modify the scripts
section in your package.json
file to include a start script.
"scripts": {
"start": "electron main.js"
},
Step 7: Run your Electron app:
Execute the following command to run your Electron app.
npm start
This will launch your desktop application with Electron. You can further customize and enhance your app by exploring the Electron API and integrating additional packages as needed.
Output:
Задавались ли вы когда-нибудь вопросом, возможно ли создавать кроссплатформенные настольные приложения на HTML, CSS и JavaScript? С Electron это становится возможным. В этой статье мы рассмотрим основы Electron и напишем простое приложение.
Функциональность нашего приложения будет заключаться в том, что при нажатии определённой клавиши на клавиатуре будет воспроизводиться соответствующий звук.
Прим. перев. Для создания приложений с использованием Electron не требуется знание таких языков программирования, как C++ и Python, — знания веб-технологий будет достаточно. Если вы не ограничены веб-технологиями, хорошей альтернативой использованию Electron будут GTK+ и Qt: в отличие от «родных» приложений, приложения, написанные с использованием Electron, обладают множеством недостатков, в том числе крайне неэффективно используют свободную оперативную память компьютера. Статью по QT можно посмотреть у нас на сайте.
Electron — фреймворк для кроссплатформенной разработки настольных приложений с использованием Chromium и Node.js.
С его помощью можно легко написать приложение с использованием HTML, CSS и JavaScript, которое «из коробки» будет совместимо с Mac, Windows и Linux.
Другие встроенные особенности включают:
- Автоматические обновления приложений;
- Нативные меню и уведомления;
- Сообщения об ошибках, которые можно отправлять на удалённый сервер;
- Отладка и профилирование — модуль content Chromium ищет места, где проседает производительность. Вы также можете использовать инструменты разработчика в своём приложении;
- Быстрое и простое создание установочных пакетов для Windows.
Если вы довольны тем, что предлагает Electron, то давайте займёмся нашим приложением. Однако прежде чем мы начнём, необходимо установить Node.js. Также вам пригодится аккаунт на GitHub, чтобы хранить и обновлять своё приложение. Это делать необязательно, но желательно, так как в наше время важно знать, как работает GitHub.
Принимаемся за работу
Когда вы закончите с подготовкой, откройте терминал и следуйте дальнейшим инструкциям, чтобы клонировать репозиторий Electron Quick Start на ваш компьютер. Именно на основе Electron Quick Start мы и будем писать наше приложение.
# Клонируем репозиторий
git clone https://github.com/electron/electron-quick-start
# Переходим к нему
cd electron-quick-start
# Устанавливаем зависимости и запускаем
npm install && npm start
После выполнения этих шагов приложение должно запуститься в чём-то, похожем на окно браузера. Хотя, это и есть окно браузера!
Как было сказано ранее, в своём приложении вы можете использовать инструменты разработчика. Всё, что вы можете делать с инструментами вашего браузера, вы также можете делать и в приложении. Потрясающе!
Архитектура приложения
Теперь давайте взглянем на код и структуру приложения.
index.html
— HTML-страница, отвечает за внешний вид;main.js
— создаёт окна и управляет системными событиями;package.json
— описание пакета (имя, зависимости и т.д.) для npm;renderer.js
— управляет процессами рендеринга.
Возможно, вы задаётесь вопросом: «Что за звери эти процессы рендеринга и основной процесс?» Давайте разбираться.
Что есть процесс?
Когда вы видите слово «процесс», думайте о процессе в операционной системе. По сути, это экземпляр программы, работающей в системе.
Если запустить наше Electron-приложение и заглянуть в Диспетчер Задач в Windows, Мониторинг Активности в macOS или Системный Монитор в Linux, то можно увидеть процессы, связанные с приложением.
Все они работают параллельно, однако память и ресурсы, выделенные под каждый из них, изолированы от других процессов.
Допустим, мы хотим написать цикл в процессе рендеринга:
var a = 1;
for ( a = 1; a < 10; a ++) {
console.log('Это цикл for');
}
Этот код никак не повлияет на основной процесс.
Основной процесс
Этот процесс контролирует происходящее в приложении. В него встроен полноценный Node.js API. Из него создаются процессы рендеринга и открываются диалоговые окна. Также он отвечает за разное взаимодействие с операционной системой, запускает и закрывает приложение.
Файл с этим процессом принято называть main.js
, но вы можете дать ему любое имя. Также вы можете менять файл основного процесса через package.json
. Чтобы проверить, как это работает, откройте файл package.json
, замените строку "main": "main.js"
на "main": "mainTest.js"
и попробуйте запустить приложение.
Имейте в виду, что основной процесс может быть только один.
Процесс рендеринга
Этот процесс представляет собой окно браузера в вашем приложении. В отличие от основного процесса, процессов рендеринга может быть несколько и каждый из них будет независим от остальных. За счёт этого ошибка в одном из них никак не повлияет на другие. Скажем за это спасибо многопроцессорной архитектуре Chromium. Также эти окна можно спрятать или изменить, так как они работают как HTML-файлы.
Но в Electron у нас также есть доступ к Node.js API. Это значит, что мы можем открывать диалоговые окна и взаимодействовать с операционной системой прочими способами.
Представить происходящее можно следующим образом:
Остаётся один вопрос. Можно ли как-нибудь связать эти процессы?
Эти процессы выполняются одновременно и независимо. Однако им всё равно нужно как-то взаимодействовать. Особенно учитывая то, что они отвечают за разные задачи.
Специально для таких целей существует межпроцессное взаимодействие (IPC). Его можно использовать для передачи сообщений между основным процессом и процессами рендеринга.
Вот мы и разобрались с основами процессов для создания Electron-приложения. Возвращаемся к коду!
Добавим индивидуальности
Поменяем название папки с нашим приложением на более подходящее. Измените название папки с electron-quick-start
на hear-me-type-tutorial
. Откройте папку заново в текстовом редакторе или IDE. Теперь похимичим с файлом package.json
. Он содержит важную информацию о нашем приложении: имя, версию, автора, лицензию и многое другое.
Давайте укажем, кто является автором приложения. Для этого найдите параметр author
и замените его значение на своё имя. Должно получиться что-то такое: "author": "Carol Pelu"
. Также вы можете изменить и другие параметры вроде name
и description
, которые отвечают за название приложения и его описание соответственно. В итоге должно получиться примерно так:
Помните, что вы всегда можете ввести npm start
в терминале, чтобы запустить приложение и посмотреть на внесённые изменения.
Пора идти дальше и добавить в наше приложение функциональность.
Добавляем функциональность
Мы хотим, чтобы при нажатии клавиши на клавиатуре проигрывался соответствующий звук. Чтобы реагировать на пользовательский ввод, мы должны определить элемент, который будет перехватывать нажатия клавиш и затем активировать нужное действие.
Для этого мы создадим элементы audio
со своим id
для каждой клавиши. Затем напишем switch-конструкцию, чтобы понять, какая клавиша была нажата. После этого воспроизведём звук, привязанный к этой клавише. Если это звучит сложно — не беспокойтесь, мы разберёмся со всем пошагово.
Скачайте этот архив с нужными нам аудиофайлами. Пора встраивать аудио в наше приложение.
Откройте index.html
и внутри <body>
создайте новый элемент <div>
с классом audio
.
Затем внутри этого <div>
создайте элемент <audio>
с id
равным "A"
, src
равным "sounds/A.mp3"
и атрибутом preload
равным "auto"
.
Мы используем preload="auto"
, чтобы сказать приложению, что оно должно загрузить весь аудиофайл после загрузки страницы. Главным файлом нашего приложения является index.html
, и все аудио загрузятся после запуска приложения.
В итоге код должен выглядеть так:
<div class="audio">
<audio id="A" src="sounds/A.mp3" preload="auto"></audio>
</div>
Теперь index.html
имеет примерно такой вид:
Отлично! Теперь нам не хватает только JavaScript-кода.
Создадим новый файл functions.js
. Давайте запросим его в файле index.html
, чтобы JS-код был готов к использованию, когда приложение будет запущено.
Следуя примеру require(./renderer.js')
, добавим строку require('./functions.js')
прямо под ней.
Проект теперь должен иметь такой вид:
Отлично! Теперь, когда уже почти всё готово, наступает момент истины.
Откроем functions.js
и добавим туда следующий код:
document.onkeydown = function(e) {
switch (e.keyCode) {
case 65:
document.getElementById('A').play();
break;
default:
console.log("Клавиша на обнаружена!");
}
};
Откройте консоль, убедитесь, что вы находитесь в директории проекта и введите npm start
для запуска приложения.
Сделайте звук погромче и нажмите клавишу «А» на клавиатуре.
#крышесносно
JS-код довольно простой. Мы используем событие onkeydown
для объекта document
, чтобы выяснить, к какому HTML-элементу мы обращаемся. Имейте в виду, что объектом document
является главное окно нашего приложения.
В анонимной функции мы используем switch-выражение, которое выясняет Unicode-значение нажатой клавиши. Если это значение правильное, то воспроизводится звук. В противном случае в консоль выводится сообщение: «Клавиша не обнаружена!».
Как вы могли заметить, у нас есть файлы для клавиш от A до Z и от 0 до 9. Поэтому давайте используем и их, чтобы «А» было не так одиноко.
Вернёмся к index.html
и создадим элемент <audio>
для всех клавиш, к которым у нас есть аудио. Да, можете просто скопипастить:
<audio id="B" src="sounds/B.mp3" preload="auto"></audio>
<audio id="C" src="sounds/C.mp3" preload="auto"></audio>
<audio id="D" src="sounds/D.mp3" preload="auto"></audio>
<audio id="E" src="sounds/E.mp3" preload="auto"></audio>
<audio id="F" src="sounds/F.mp3" preload="auto"></audio>
<audio id="G" src="sounds/G.mp3" preload="auto"></audio>
<audio id="H" src="sounds/H.mp3" preload="auto"></audio>
<audio id="I" src="sounds/I.mp3" preload="auto"></audio>
<audio id="J" src="sounds/J.mp3" preload="auto"></audio>
<audio id="K" src="sounds/K.mp3" preload="auto"></audio>
<audio id="L" src="sounds/L.mp3" preload="auto"></audio>
<audio id="M" src="sounds/M.mp3" preload="auto"></audio>
<audio id="N" src="sounds/N.mp3" preload="auto"></audio>
<audio id="O" src="sounds/O.mp3" preload="auto"></audio>
<audio id="P" src="sounds/P.mp3" preload="auto"></audio>
<audio id="Q" src="sounds/Q.mp3" preload="auto"></audio>
<audio id="R" src="sounds/R.mp3" preload="auto"></audio>
<audio id="S" src="sounds/S.mp3" preload="auto"></audio>
<audio id="T" src="sounds/T.mp3" preload="auto"></audio>
<audio id="U" src="sounds/U.mp3" preload="auto"></audio>
<audio id="V" src="sounds/V.mp3" preload="auto"></audio>
<audio id="W" src="sounds/W.mp3" preload="auto"></audio>
<audio id="X" src="sounds/X.mp3" preload="auto"></audio>
<audio id="Y" src="sounds/Y.mp3" preload="auto"></audio>
<audio id="Z" src="sounds/Z.mp3" preload="auto"></audio>
<audio id="0" src="sounds/0.mp3" preload="auto"></audio>
<audio id="1" src="sounds/1.mp3" preload="auto"></audio>
<audio id="2" src="sounds/2.mp3" preload="auto"></audio>
<audio id="3" src="sounds/3.mp3" preload="auto"></audio>
<audio id="4" src="sounds/4.mp3" preload="auto"></audio>
<audio id="5" src="sounds/5.mp3" preload="auto"></audio>
<audio id="6" src="sounds/6.mp3" preload="auto"></audio>
<audio id="7" src="sounds/7.mp3" preload="auto"></audio>
<audio id="8" src="sounds/8.mp3" preload="auto"></audio>
<audio id="9" src="sounds/9.mp3" preload="auto"></audio>
Потрясающе! Теперь давайте провернём то же самое в functions.js
.
Код для каждой клавиши можно найти здесь. Но вы по-прежнему можете просто скопировать:
document.onkeydown = function(e) {
switch (e.keyCode) {
case 48:
document.getElementById('0').play();
break;
case 49:
document.getElementById('1').play();
break;
case 50:
document.getElementById('2').play();
break;
case 51:
document.getElementById('3').play();
break;
case 52:
document.getElementById('4').play();
break;
case 53:
document.getElementById('5').play();
break;
case 54:
document.getElementById('6').play();
break;
case 55:
document.getElementById('7').play();
break;
case 56:
document.getElementById('8').play();
break;
case 57:
document.getElementById('9').play();
break;
case 65:
document.getElementById('A').play();
break;
case 66:
document.getElementById('B').play();
break;
case 67:
document.getElementById('C').play();
break;
case 68:
document.getElementById('D').play();
break;
case 69:
document.getElementById('E').play();
break;
case 70:
document.getElementById('F').play();
break;
case 71:
document.getElementById('G').play();
break;
case 72:
document.getElementById('H').play();
break;
case 73:
document.getElementById('I').play();
break;
case 74:
document.getElementById('J').play();
break;
case 75:
document.getElementById('K').play();
break;
case 76:
document.getElementById('L').play();
break;
case 77:
document.getElementById('M').play();
break;
case 78:
document.getElementById('N').play();
break;
case 79:
document.getElementById('O').play();
break;
case 80:
document.getElementById('P').play();
break;
case 81:
document.getElementById('Q').play();
break;
case 82:
document.getElementById('R').play();
break;
case 83:
document.getElementById('S').play();
break;
case 84:
document.getElementById('T').play();
break;
case 85:
document.getElementById('U').play();
break;
case 86:
document.getElementById('V').play();
break;
case 87:
document.getElementById('W').play();
break;
case 88:
document.getElementById('X').play();
break;
case 89:
document.getElementById('Y').play();
break;
case 90:
document.getElementById('Z').play();
break;
default:
console.log("Key is not found!");
}
};
Прим. перев. Как вы, вероятно, заметили, такая switch-case конструкция выглядит довольно громоздко. А как вы бы оптимизировали этот участок кода? Делитесь своими вариантами в комментариях.
Вот мы и закончили наше приложение! Поздравляем!
Основная функциональность в приложении присутствует, но его ещё можно доработать.
Дополняем приложение
Да, у нас всё работает, но всё равно то тут, то там чего-то не хватает. Например, в index.html
вы можете изменить заголовок приложения и содержимое основного окна. Кроме того, у нас нет никакого дизайна, нет красивых цветов и нет картинок с котиками. Включите воображение и попробуйте улучшить внешний вид приложения.
Код тоже не верх совершенства. У нас куча одинакового кода, который можно оптимизировать и улучшить. В итоге код будет занимать меньше места, и глазам будет не так больно. Помните: повторяющийся код — это плохо.
Тестируем, тестируем и ещё раз тестируем
Хорошее ПО должно быть тщательно протестировано. Попробуйте нажать каждую клавишу, чтобы увидеть, что произойдёт. В лучшем случае вы услышите звук для каждой клавиши, указанной в коде. Но что если вы нажмёте много клавиш подряд так быстро, как только можете? А что насчёт клавиш вроде Home и NumLock, для которых у нас нет звука?
Если вы свернёте приложение и нажмёте клавишу, вы услышите звук? А если окно приложения неактивно, и вы нажмёте клавишу, то что-нибудь произойдёт?
К сожалению, ответ — нет.
Так происходит из-за архитектуры, на которой построен Electron. Вы можете регистрировать нажатия клавиш внутри приложения как в C#, но не можете этого делать за его пределами. Это выходит за рамки привычных Electron-приложений.
Пройдитесь по коду строка за строкой и попробуйте сделать его нерабочим. Посмотрите, что произойдёт и какие ошибки выбросит Electron. Это упражнение поможет вам разобраться в отладке. Если вы знаете слабые места своего приложения, то вы знаете, как их исправить и сделать приложение лучше.
В файле functions.js
было использовано устаревшее событие. Сможете его найти? Когда найдёте, подумайте, как его заменить без изменения функциональности приложения.
Использование устаревшего кода — плохая практика. Это может привести к серьёзным багам, о существовании которых вы могли даже не подозревать. Следите за документацией языка, чтобы знать, какие произошли изменения. Всегда будьте в курсе последних событий.
Table of Contents
- Introduction
- Benefits
- We can start by using a template
- Node.js modules required
- Folder structure
- Build out the routes, don’t worry there is only one
- Build out the view folder
- Create the Express App.js
- End-to-end solution
- Conclusion
Introduction
Looking to create a Node.js desktop application? You have come to the right place. In this tutorial, we will discover developing Node.js desktop applications with Electron, Bootstrap, and Express.js. This will allow us to create modern, intuitive user-interfaces backed up by powerful backend data systems.
Once you have everything successfully configured and deployed, you may want to look into options for monitoring your application — which is an essential part of the application lifecycle.
Benefits
Create a desktop application that enables easy integration with third-party software from the web.
It uses node-package-manager — which is the largest software registry in the world.
You can search available integrations on their website here: Node Registry
We can start by using a template
Perform the git clone command to pull down the template to get started:
git clone https://github.com/zero-equals-false/node-desktop-app-template
Install the dependencies:
npm install
Test run the server:
npm start
Your blank canvas:
In this tutorial, we will use this template to create a modern full-stack desktop application. We will create a form processing application, which takes user input from a form and injects the data into a word document.
Node.js modules required
To build our form application, we will need Express.js, Docxtemplater, and Pizzip. Express will handle the middleware routing when the form is submitted. Docxtemplater and Pizzip is used for our backend functionality/data injection. Also, to parse and split the data from when it is sent from the front-end to the back-end, we need body-parser. We will serve our form as a .ejs
file as a separate view. This will allow us to scale out our views as the application grows larger.
Install dependencies with the following command:
npm install --save express docxtemplater pizzip body-parser
Folder structure
Now let’s create our folder structure. It should look like this:
Most folders you should have with the template. I have labeled *
against the folders/files we will create. This will be the Express.js app we will be building inside our Electron desktop app.
Let’s build our Express app now, by performing the following. Create the directories for routes and views:
mkdir routes
mkdir views
Build out the routes, don’t worry there is only one
We will create our route inside our Express app by creating an index.js
file inside the routes folder, and adding:
var express = require('express');
var router = express.Router();
var PizZip = require('pizzip');
var Docxtemplater = require('docxtemplater');
var fs = require('fs');
var path = require('path');
// Load the docx file as a binary
var content = fs
.readFileSync(path.resolve(__dirname, 'template.docx'), 'binary');
var zip = new PizZip(content);
var doc = new Docxtemplater();
async function convertDocument(status, name, surname, postal, suburb, state, phone, mobile, email, postcode, fax, x, y) {
doc.loadZip(zip);
var mr = false;
var mrs = false;
var ms = false;
if(status === "mr") mr = true;
if(status === "mrs") mrs = true;
if(status === "ms") ms = true;
// set the templateVariables
doc.setData({
hasMr: mr,
hasMrs: mrs,
hasMs: ms,
name: name,
surname: surname,
postal: postal,
suburb: suburb,
state: state,
phone: phone,
mobile: mobile,
email: email,
postcode: postcode,
fax: fax
});
try {
// renders the document (replacing all occurences of {first_name} by James, {last_name} by Smith, ...)
doc.render()
}
catch (error) {
var e = {
message: error.message,
name: error.name,
stack: error.stack,
properties: error.properties,
}
console.log(JSON.stringify({error: e}));
if (error.properties && error.properties.errors instanceof Array) {
const errorMessages = error.properties.errors.map(function (error) {
return error.properties.explanation;
}).join("\n");
console.log('errorMessages', errorMessages);
}
throw error;
}
var buf = doc.getZip()
.generate({type: 'nodebuffer'});
// buf is a nodejs buffer, you can either write it to a file or do anything else with it.
fs.writeFileSync(path.resolve(__dirname, '..\\output.docx'), buf);
}
/* GET the home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
/***
* Form transformation - inject data into a word document
*/
router.post('/form-submit', function(req, res, next) {
var status, name, surname, postal, suburb, state, phone, mobile, email, postcode, fax;
status = req.body.status;
name = req.body.name;
surname = req.body.surname;
postal = req.body.postal;
suburb = req.body.suburb;
state = req.body.state;
phone = req.body.phone;
mobile = req.body.mobile;
email = req.body.email;
postcode = req.body.postcode;
fax = req.body.fax;
convertDocument(status, name, surname, postal, suburb, state, phone, mobile, email, postcode, fax);
});
module.exports = router;
Next, save the word document we will use as a template for the data injection. The word doc must be saved as template.docx
inside the routes folder.
Title [ {#hasMr}X{/hasMr} ] Mr [ {#hasMrs}X{/hasMrs}] Mrs [ {#hasMs}X{/hasMs}] Ms [ ] Other please specify:
First name {name}
Surname {surname}
Postal address {postal}
Suburb {suburb}
State or territory {state} Postcode {postcode}
Phone number {phone} Fax number {fax}
Mobile number {mobile}
Email address {email}
The template.docx
file will be the file we will use as a baseline to generate each form.
Build out the view folder
Next, we will create our view files inside our view folder. Create the following file, index.ejs and copy and paste the code from below.
<html>
<head>
<title>Node.js Desktop Application using Electron, Bootstrap and Express.js</title>
<link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css' />
<link rel='stylesheet' href='style.css' />
</head>
<body>
<div class="container">
<br>
<br>
<br>
<br>
<div class="form-style-6">
<h1>Enter client details</h1>
<form class="border border-light p-5" method="post" action="/form-submit" id="clientform">
<div class="form-group">
<br>
<label>Status</label>
<select name="status" form="clientform">
<option value="mr">Mr</option>
<option value="mrs">Mrs</option>
<option value="ms">Ms</option>
</select>
<label for="nameInput">First name</label>
<input type="type" class="form-control" id="nameInput" placeholder="First name" name="name">
<br>
<label for="surnameInput">Surname</label>
<input type="type" class="form-control" id="surnameInput" placeholder="Surname" name="surname">
<br>
<label for="nameInput">Postal address</label>
<input type="type" class="form-control" id="postalInput" placeholder="Postal address" name="postal">
<br>
<label for="suburbInput">Suburb</label>
<input type="type" class="form-control" id="nameInput" placeholder="Suburb" name="suburb">
<br>
<label for="stateInput">State</label>
<input type="type" class="form-control" id="stateInput" placeholder="State" name="state">
<br>
<label for="phoneInput">Phone</label>
<input type="type" class="form-control" id="phoneInput" placeholder="Phone" name="phone">
<br>
<label for="mobileInput">Mobile</label>
<input type="type" class="form-control" id="mobileInput" placeholder="Mobile" name="mobile">
<br>
<label for="emailInput">Email</label>
<input type="type" class="form-control" id="emailInput" placeholder="Email" name="email">
<br>
<label for="postcodeInput">Postcode</label>
<input type="type" class="form-control" id="postcodeInput" placeholder="Postcode" name="postcode">
<br>
<label for="faxInput">Fax</label>
<input type="type" class="form-control" id="faxInput" placeholder="Fax" name="fax">
<br>
</div>
<button type="submit" class="btn btn-primary" id="send" onclick="alert('thank you!')">Submit</button>
</form>
<br>
<br>
</div>
</div>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
</body>
</html>
Let’s also create an error file incase anything goes wrong, create the error.ejs file:
<h1><%= message %></h1>
<h1><%= error.status %></h1>
<pre><%= error.stack %></pre>
Create the Express App.js
Now let’s finish off our Express.js application, create the app.js file in the top level of the project.
app.js
var express = require('express');
var path = require('path');
var bodyParser = require('body-parser');
var index = require('./routes/index');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', index);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// error handlers
// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err
});
});
}
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {}
});
});
const server = app.listen(3000, () => console.log(`Express server listening on port 3000`));
module.exports = app;
<div id='waldo-tag-14053'></div>
## Modify the main.js file to include Express.js
I have copied the code for convenience, however it is only two lines which need to be added. These are displayed in caps, as :
** // ADD THIS**.
```javascript
const {app, BrowserWindow} = require('electron')
const server = require('./app'); //ADD THIS
let mainWindow;
function createWindow () {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
})
mainWindow.loadURL('http://localhost:3000') //ADD THIS
mainWindow.on('closed', function () {
mainWindow = null
})
}
app.on('ready', createWindow)
app.on('resize', function(e,x,y){
mainWindow.setSize(x, y);
});
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', function () {
if (mainWindow === null) {
createWindow()
}
})
End-to-end solution
Ok, well done! you have made it this far. You have successfully built a modern Node.js express application using Electron, Bootstrap and Express.js. Last but not least, start your desktop application by using npm start
:
Now in your project folder, you should see an output.docx
:
You can view the full source code for this tutorial here
Conclusion
This is a Node.js desktop solution for both front-end and back-end, taking data from a form on the front-end and mapping it to specific locations in a word document (data processing). However, this is only one use-case, there are many other uses for Node.js desktop applications out there.
Final Notes
Introduction to NW.js
NW.js, formerly known as node-webkit, is an open-source framework that enables developers to create cross-platform desktop applications using web technologies such as HTML, CSS, and JavaScript. NW.js combines the flexibility of web development with the power of Node.js, allowing developers to access native APIs and build feature-rich desktop applications.
Getting Started with NW.js
To kickstart your journey with NW.js, you’ll need to have Node.js installed on your machine. Once Node.js is set up, you can install NW.js using npm, the Node.js package manager, with a simple command:
npm install nw
After installing NW.js, you can create a new project and structure it similar to a web application, with HTML, CSS, and JavaScript files. NW.js provides a familiar development environment for web developers, enabling them to leverage their existing skills and tools.
Building Desktop GUI Applications
Now, let’s dive into the process of building a desktop GUI application with Node.js and NW.js. We’ll outline the essential steps and provide examples to illustrate each concept.
Creating the User Interface (UI)
Start by designing the user interface using HTML and CSS. You can use popular front-end frameworks like Bootstrap or Materialize to streamline the UI development process.
Example:
<!DOCTYPE html> <html> <head> <title>My NW.js App</title> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"> </head> <body> <div class="container"> <h1>Hello NW.js!</h1> </div> </body> </html>
Adding Functionality with JavaScript
Next, enhance your application’s functionality using JavaScript. You can use Node.js modules to perform file I/O operations, interact with databases, or implement other backend functionality.
Example:
const fs = require('fs'); fs.writeFile('example.txt', 'Hello, NW.js!', (err) => { if (err) throw err; console.log('File created successfully!'); });
Accessing Native APIs
NW.js allows you to access native APIs using Node.js modules, enabling seamless integration with the underlying operating system. You can leverage these APIs to access system resources, interact with hardware peripherals, or perform other system-level tasks.
Example:
const { app, BrowserWindow } = require('nw'); let mainWindow; app.on('ready', () => { mainWindow = new BrowserWindow({ width: 800, height: 600 }); mainWindow.loadURL('https://example.com'); });
Packaging and Distribution
Once your desktop application is ready, you can package it for distribution across different platforms. NW.js provides built-in tools for packaging your application as standalone executables for Windows, macOS, and Linux.
Example:
nwjs-builder . -p [platform]
Real-World Examples
Slack
Slack, the popular messaging platform, utilizes NW.js to deliver its desktop application across multiple operating systems. By leveraging NW.js, Slack offers a seamless user experience with access to native features and functionalities.
Slack Desktop App
Visual Studio Code
Microsoft’s Visual Studio Code, a widely-used code editor, is built using NW.js. Visual Studio Code combines the power of a native desktop application with the flexibility of web technologies, providing developers with a feature-rich coding environment.
Visual Studio Code
Atom
Atom, a customizable text editor developed by GitHub, is another example of a desktop application built with NW.js. Atom’s extensible architecture, powered by web technologies, enables developers to personalize their coding experience with various plugins and themes.
Atom Text Editor
Conclusion
Node.js and NW.js offer a compelling solution for building desktop GUI applications with web technologies. By leveraging the power of Node.js and combining it with the flexibility of web development, developers can create cross-platform desktop applications with ease. Whether you’re building productivity tools, communication platforms, or creative applications, Node.js and NW.js provide the tools and resources you need to bring your ideas to life. Happy coding!
External Resources:
- NW.js Official Documentation
- Node.js Official Website
- NW.js GitHub Repository