Статья представляет собой пошаговое описание моего опыта создания кроссплатформенного десктопного приложения с помощью Webix, Electron и Node.js.
Однажды мне пришла в голову светлая мысль создать десктопное приложение на базе стэка веб-технологий, который мне хорошо знаком. Знаю, что программисты, пишущие под десктоп, обычно используют C++, Java, C#, а на стэк веб-технологий для этих целей смотрят свысока. Но, поскольку я писал приложение для себя, то справедливо решил, что использование знакомых инструментов ускорит процесс. Ну и конечно захотелось «скрестить ужа с ежом» и посмотреть что получится. Если вкратце, то получившийся результат можно запускать и как обычное веб-приложение, и как десктоп.
Код приложения можно
скачать с GitHub.
Что будет делать наше приложение… Это TODO-list (а как же иначе...), в который мы сможем добавлять события, редактировать их и удалять. Событие будет иметь заголовок, содержание, место проведения, дату и приоритет. Также будет доступна возможность перевода интерфейса на русский и английский языки. Назовем его «Data master».
Для создания веб-приложения я использовал
Webix. Он представляет собой кроссплатформенную и кроссбраузерную UI библиотеку, использующие компоненты для быстрого построения приложения с использованием JavaScript синтаксиса. Для компиляции веб-приложения в десктоп использовался
Electron. Это кроссплатформенный инструмент, работающий на базе Node.js и позволяющий компилировать веб-приложение для запуска на различных платформах различной разрядности: Windows, Linux, Mac. Для всяких вспомогательных вещей используются инструменты на базе Node.js.
Начнем со структуры папок. В корне проекта я создал ее в таком виде:
- css — стили
- data — бэкенд
- img — изображения
- js — скрипты JavaScript
После установки модулей Node.js добавится папка «node_modules», для Webix будет использоваться папка «codebase», в папке "~/release/DataMaster" будут версии десктопного приложения для различных платформ.
В итоге структура проекта у меня получилась такая:
Корневая папка проекта должна быть расположена на сервере. В моем случае это Apache.
Итак, для начала я зашел на
страницу загрузки Webix и нажал «Скачать Webix Standard». Это бесплатная версия библиотеки (лицензия «GNU GPLV3»), которая вполне подойдет для наших нужд. Имеется еще коммерческая версия «Webix PRO», которая отличается главным образом расширенной библиотекой виджетов, а также возможностями техподдержки. Из полученного архива «webix.zip» копируем папку «codebase» в корень нашего проекта. Внутри папки «codebase» обратите внимание на файлы webix.js и webix.css. Подключение этих файлов в приложении позволяет работать с Webix. В папке «skins» содержатся css-файлы с темами.
Создадим в корне проекта файл index.html.
index.html<!DOCTYPE HTML>
<html>
<head>
<link rel="stylesheet" href="codebase/skins/contrast.css" type="text/css">
<link rel="stylesheet" href="css/main.css" type="text/css">
<script src="codebase/webix.js" type="text/javascript"></script>
<script src="codebase/i18n/en.js" type="text/javascript"></script>
<script src="codebase/i18n/ru.js" type="text/javascript"></script>
</head>
<body>
<script src="bundle.js" type="text/javascript"></script>
</body>
</html>
Добавим webix.js. Подключение webix.css дает нам возможность использовать стандартную тему. Я же решил подключить симпатичную темненькую тему, которая лежит в «codebase/skins/contrast.css». Также мы подключили файлы из папки «codebase/i18n» для использования встроенной возможности локализации Webix. В индексного файла подключаем файл «bundle.js». Там будет находиться сборка всего нашего js-кода. Для сборки нам понадобится Node.js и Gulp.
Если у вас еще не установлена Node.js, то сделать это можно
отсюда. Командами
$ node -v
и
$ npm -v
проверьте корректность установки Node.js и пакетного менеджера платформы — NPM.
Теперь в папке «js» мы будем создавать основную логику приложения. Файл internalization.js содержит объект для интернационализации интерфейса приложения. По аналогии с уже имеющимися языками (русский, английский) вы можете добавить туда другие языки в случае необходимости.
internalization.jsvar translations = {
// English
"en-US": {
localeName: "en-US",
headerTitle: "Data master",
resetFilters: "Reset filters",
changeLocale: "Change language:",
loadData: "Load data",
addRow: "Add row",
clearSelection: "Clear selection",
deleteRow: "Delete row",
saveData: "Save data",
title: "Title",
noItemSelected: "No item selected",
dataSaved: "Data saved",
reservedButton: "Reserved botton"
},
// Russian
"ru-RU": {
localeName: "ru-RU",
headerTitle: "Мастер данных",
resetFilters: "Сбросить фильтры",
changeLocale: "Сменить язык:",
loadData: "Загрузить данные",
addRow: "Добавить ряд",
clearSelection: "Снять выделение",
deleteRow: "Удалить ряд",
saveData: "Сохранить",
title: "Название",
noItemSelected: "Нет выбранных рядов",
dataSaved: "Данные сохранены",
reservedButton: "Зарезервировано..."
}
};
В файле logic.js содержатся функции, назначение которых вы можете понять из их названия и из комментариев к коду.
logic.js
var defaultLocale = "en-US";
// object from translations.js
var localizator = translations[defaultLocale];
/**
* Get data from backend and fill datatable grid
*/
function getData() {
$$("dataFromBackend").clearAll();
$$("dataFromBackend").load("http://localhost/data_master/data/data.php");
}
/**
* Add new row to datatable
*/
function addRow() {
$$("dataFromBackend").add(
{
title: "-----",
content: "-----",
place: "-----"
//date: "-----",
//priority: "-----"
}
);
}
/**
* Reset selection in datatable grid
*/
function clearSelection() {
$$("dataFromBackend").unselectAll();
}
/**
* Delete selected row
*/
function deleteRow() {
if (!$$("dataFromBackend").getSelectedId()) {
webix.alert(localizator.noItemSelected);
return;
}
//removes the selected item
$$("dataFromBackend").remove($$("dataFromBackend").getSelectedId());
}
/**
* Save data to backend from datatable grid
*/
function saveData() {
var grid = $$("dataFromBackend");
var serializedData = grid.serialize();
webix.ajax().post("http://localhost/data_master/data/save.php", {data: serializedData});
webix.alert(localizator.dataSaved);
}
/**
* Reset filters settings
*/
function resetFilters() {
$$("dataFromBackend").getFilter("title").value = null;
$$("dataFromBackend").getFilter("content").value = null;
$$("dataFromBackend").getFilter("place").value = null;
$$("dataFromBackend").getFilter("date").value = null;
$$("dataFromBackend").getFilter("priority").value = null;
// reload grid
$$("dataFromBackend").clearAll();
$$("dataFromBackend").load("http://localhost/data_master/data/data.php");
}
/**
* Change translation to selected
*/
function changeLocale(locale) {
localizator = translations[locale];
$$("headerContainer").define("template", localizator.headerTitle);
$$("headerContainer").refresh();
$$("resetFiltersContainer").define("value", localizator.resetFilters);
$$("resetFiltersContainer").refresh();
$$("changeLocale").define("label", localizator.changeLocale);
$$("changeLocale").refresh();
$$("loadData").define("value", localizator.loadData);
$$("loadData").refresh();
$$("addRow").define("value", localizator.addRow);
$$("addRow").refresh();
$$("clearSelection").define("value", localizator.clearSelection);
$$("clearSelection").refresh();
$$("deleteRow").define("value", localizator.deleteRow);
$$("deleteRow").refresh();
$$("saveData").define("value", localizator.saveData);
$$("saveData").refresh();
$$("reservedButton").define("value", localizator.reservedButton);
$$("reservedButton").refresh();
webix.i18n.setLocale(locale);
}
/**
* Function for reserved button
*/
function reservedButton() {
// your code...
}
Большинство функций являются обработчиками события «onclick» кнопок. Код функций в основном представляет собой способы работы с Webix-элементами. В общих чертах он интуитивно понятен, если нужна более подродная информация — добро пожаловать на страницу
документации Webix.
В файле objects.js планировалось хранить функции-конструкторы, которые являются обертками над стандартными компонентами Webix. Я планировал поместить туда часто используемые в приложении виджеты, но ограничился лишь одним — наиболее повторяющимся — элементом Button. Чуть ниже я поясню его использование.
objects.js
/**
* Create object with type "Button"
*
* @constructor
*/
function Button(id, value, type, width, onClickFunction) {
this.view = "button";
this.id = id;
this.value = value;
this.type = type;
this.width = width;
this.on = {
"onItemClick": function(){
onClickFunction();
}
}
}
Как это работает… В метод webix.ui() передается объект, имеющий многоуровневую структуру. Свойство view определяет тип виджета Webix: в нашем случае «layout». Этих типов очень много, каждый из них имеет свои методы и свойства. Кроме того, мы можем расширять стандартные компоненты Webix с помощью метода webix.protoUI(), добавляя или переопределяя необходимую нам функциональность. Как видите, работа с Webix осуществляется с помощью Javascript, поэтому весь код работы с этой библиотекой мы помещаем в теги
комментарии (34)
igormatyushkin1, (был удалён)