Использование AG Grid в приложениях Electron

Эта статья подготовлена для блога AG Grid Ареком Наво

Electron — это кроссплатформенный фреймворк для создания нативных настольных приложений с использованием веб-технологий. Он построен на базе Node.js и браузера Chromium, что позволяет использовать все последние возможности и улучшения браузера и языка JavaScript.

Electron позволяет вам создать наилучший пользовательский опыт, одновременно заботясь обо всех сложных деталях создания нативного приложения. С помощью Electron вы можете повторно использовать свои знания и кодовую базу в Интернете и на всех настольных платформах. Если вы уже умеете разрабатывать фронтенд веб-приложений и бэкенд Node.js, вы будете чувствовать себя как дома с Electron, поскольку он, по сути, объединяет эти два компонента в одно приложение.

В этой статье вы узнаете, как интегрировать AG Grid — продвинутую и производительную библиотеку сеток JavaScript — в ваше приложение Electron. Вы создадите простое приложение для выполнения дел со встроенной функциональностью для сохранения и восстановления его состояния из файла JSON.

Вы можете следить за ходом работы с помощью этого репозитория GitHub.

Как работает Electron?

Базовое приложение Electron состоит из двух процессов. Главный процесс действует как точка входа для приложения, имея доступ к API Node.js и модулям (включая встроенные). Он также контролирует жизненный цикл приложения и управляет его окнами с помощью API, предоставляемых Electron.

Таким образом, если основной процесс похож на бэкенд вашего веб-приложения, то процесс рендеринга больше похож на фронтенд. Он отвечает за рендеринг пользовательского интерфейса приложения и запускается в каждом открытом окне. Таким образом, он должен следовать веб-стандартам (таким как HTML5, CSS3 и ECMAScript) и использовать веб-интерфейсы браузера. Никакой код, специфичный для Node, не допускается.

Для взаимодействия между процессами (например, для обработки передачи данных или вызова собственных функций из пользовательского интерфейса) можно использовать скрипты предварительной загрузки и межпроцессное взаимодействие (IPC). Скрипты предварительной загрузки запускаются в процессе рендеринга перед основным скриптом и имеют доступ к API Node.js. Вы можете использовать их с модулем contextBridge Electron, чтобы безопасно открывать привилегированные API для процесса рендеринга. В частности, вы можете создавать помощников, используя модуль ipcRenderer для взаимодействия с основным процессом.

Использование AG Grid с Electron

Хорошим способом начать работу с Electron является Electron Forge — «полный инструмент для создания, публикации и установки современных приложений Electron». В Electron Forge есть все необходимое для работы над приложением Electron, включая шаблон на базе Webpack.

Настройка проекта

Чтобы начать новый проект в Electron Forge, убедитесь, что у вас установлен git и Node.js версии 12.13.0 или новее. Затем выполните следующие команды для создания проекта, установки дополнительных зависимостей и запуска сервера разработки:

npx create-electron-app@latest project --template=webpack
cd project
npm install ag-grid-community
npm run start

Войти в полноэкранный режим Выйти из полноэкранного режима

Управление окном приложения

По умолчанию шаблон включает файлы для основного процесса и процесса рендеринга. Внутри файла src/main.js вы увидите начальную точку вашего приложения. Ниже мы рассмотрим функцию createWindow() и слушателей событий:

const { app, BrowserWindow } = require("electron");
// ...

const createWindow = () => {
  const mainWindow = new BrowserWindow();

  mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
  mainWindow.webContents.openDevTools();
};

app.on("ready", createWindow);
app.on("window-all-closed", () => {
  if (process.platform !== "darwin") {
    app.quit();
  }
});
app.on("activate", () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow();
  }
});

Вход в полноэкранный режим Выход из полноэкранного режима

Модуль app управляет жизненным циклом событий вашего приложения. Когда приложение готово, оно создает новое окно, вызывая createWindow(), которая в свою очередь создает новый экземпляр BrowserWindow. Используя метод loadURL(), окно загружает содержимое, обслуживаемое сервером разработки Webpack. Оно также открывает инструменты разработки для облегчения отладки с помощью метода webContents.openDevTools().

Другие слушатели событий обрабатывают специфические для macOS побочные ситуации, такие как сохранение приложения открытым без окон (window-all-closed) или открытие нового окна при активации из дока (activate).

Добавление сценария предварительной загрузки

Чтобы обеспечить доступ к нативным API из процесса рендеринга, вам придется раскрыть некоторые функции в скрипте предварительной загрузки. Хотя шаблон не включает его по умолчанию, добавить его самостоятельно очень просто.

Создайте новый файл src/preload.js и отредактируйте поле config.forge.plugins в package.json, чтобы сообщить Electron Forge о скрипте предварительной загрузки:

{
  /* ... */
  "config": {
    "forge": {
      "packagerConfig": {},
      /* ... */
      "plugins": [
        [
          "@electron-forge/plugin-webpack",
          {
            "mainConfig": "./webpack.main.config.js",
            "renderer": {
              "config": "./webpack.renderer.config.js",
              "entryPoints": [
                {
                  "html": "./src/index.html",
                  "js": "./src/renderer.js",
                  "preload": {
                    "js": "./src/preload.js"
                  },
                  "name": "main_window"
                }
              ]
            }
          }
        ]
      ]
    }
  }
  /* ... */
}

Вход в полноэкранный режим Выйти из полноэкранного режима

Чтобы инициализировать скрипт, укажите его при создании BrowserWindow в файле src/main.js с помощью глобальной переменной, предоставляемой Webpack:

// ...
const createWindow = () => {
  const mainWindow = new BrowserWindow({
    webPreferences: {
      preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
    },
  });

  // ...
};
// ...

Войти в полноэкранный режим Выйти из полноэкранного режима

Создание пользовательского интерфейса процесса рендеринга

Когда основное управление окнами готово, можно приступать к работе над процессом рендеринга. Вы будете использовать AG Grid для создания простого списка дел с возможностью добавлять, удалять и отмечать выполненные пункты.

Разработка процесса рендеринга очень похожа на создание фронтенд веб-приложения. Вы можете использовать все фронтенд-фреймворки и API, доступные в среде браузера. В этом руководстве вы будете использовать обычный HTML, JS и CSS, чтобы все было просто.

Начните с создания структуры пользовательского интерфейса в файле src/index.html:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>AG Grid + Electron</title>
  </head>
  <body>
    <div class="container">
      <h1>Electron TO-DO</h1>
      <div class="btn-container">
        <button id="save-btn" class="btn">Save</button>
        <button id="restore-btn" class="btn">Restore</button>
        <button id="add-btn" class="btn add-btn">Add</button>
      </div>
      <div class="divider"></div>
      <div id="grid" class="ag-theme-alpine"></div>
    </div>
  </body>
</html>

Вход в полноэкранный режим Выход из полноэкранного режима

Затем добавьте необходимые стили в src/index.css :

html,
body {
  height: 100%;
  margin: 0;
}
#grid {
  width: 100%;
  flex: 1;
}
.container {
  margin: auto;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  height: calc(100% - 2rem);
  padding: 1rem;
  width: 30rem;
}
.btn-container {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
  gap: 0.5rem;
  width: 100%;
}
.divider {
  margin: 0.5rem 0;
  background: #e6e6e6;
  height: 0.125rem;
}
.checkbox {
  height: 100%;
  margin: 0;
  width: 1.25rem;
}
.btn {
  flex: 1;
  padding: 0.5rem;
  background: #6b7280;
  border: none;
  color: #fff;
  font-size: 1rem;
}
.btn:hover {
  background: #9ca3af;
  cursor: pointer;
}
.add-btn {
  padding: 0.5rem;
  background: #f97316;
  border: none;
}
.add-btn:hover {
  background: #fb923c;
  cursor: pointer;
}
.remove-btn {
  display: inline-flex;
  max-height: 1.25rem;
  max-width: 1.25rem;
  font-size: 1.25rem;
  justify-content: center;
  align-items: center;
  background-color: #ef4444;
}
.remove-btn:hover {
  background-color: #f87171;
}

Вход в полноэкранный режим Выйти из полноэкранного режима

С этой настройкой вы можете перейти к файлу src/renderer.js для инициализации сетки:

import "ag-grid-community/dist/styles/ag-grid.css";
import "ag-grid-community/dist/styles/ag-theme-alpine.css";
import "./index.css";
import { Grid } from "ag-grid-community";

let rowData = [];

const columnDefs = [
  // ...
];
const gridOptions = {
  columnDefs,
  rowData,
};
const saveBtn = document.getElementById("save-btn");
const restoreBtn = document.getElementById("restore-btn");
const addBtn = document.getElementById("add-btn");
const addTodo = () => {
  // ...
};
const removeTodo = (rowIndex) => {
  // ...
};
const saveToFile = () => {
  // ...
};
const restoreFromFile = async () => {
  // ...
};
const setupGrid = () => {
  const gridDiv = document.getElementById("grid");

  new Grid(gridDiv, gridOptions);
  addBtn.addEventListener("click", addTodo);
  saveBtn.addEventListener("click", saveToFile);
  restoreBtn.addEventListener("click", restoreFromFile);
};

document.addEventListener("DOMContentLoaded", setupGrid);

Войти в полноэкранный режим Выйти из полноэкранного режима

Вся настройка, включая создание экземпляра Grid и добавление обработчиков событий, происходит после загрузки DOM. Сетка создается с использованием предоставленной конфигурации, определяя ее столбцы и входные данные:

// ...
let rowData = [];

const columnDefs = [
  { field: "task", editable: true, flex: 1 },
  {
    field: "completed",
    width: 120,
    cellRenderer(params) {
      const input = document.createElement("input");

      input.type = "checkbox";
      input.checked = params.value;
      input.classList.add("checkbox");
      input.addEventListener("change", (event) => {
        params.setValue(input.checked);
      });

      return input;
    },
  },
  {
    field: "remove",
    width: 100,
    cellRenderer(params) {
      const button = document.createElement("button");

      button.textContent = "✕";
      button.classList.add("btn", "remove-btn");
      button.addEventListener("click", () => removeTodo(params.rowIndex));

      return button;
    },
  },
];
// ...

Вход в полноэкранный режим Выход из полноэкранного режима

Колонки определяются определенными параметрами, такими как имя поля, ширина, или пользовательский cellRenderer, если вы хотите отобразить данные по-другому. editable включает встроенную поддержку редактирования, позволяя пользователю изменять название задачи. В то же время, flex является альтернативой width, указывая на то, что колонка должна заполнять оставшееся пространство.

Для столбцов «выполнено» и «удалить» пользовательские рендеры ячеек отображают флажок и кнопку, чтобы, соответственно, изменить статус задачи или полностью удалить ее из списка. Фактическое изменение данных сетки осуществляется с помощью params.setValue() и отдельной функции removeTodo().

И addTodo(), и removeTodo() работают с использованием объекта gridOptions.api. После предоставления гриду, gridOptions получает свойство api, позволяющее управлять гридом:

// ...
const addTodo = () => {
  rowData = [...rowData, { task: "New Task", completed: false }];
  gridOptions.api.setRowData(rowData);
};
const removeTodo = (rowIndex) => {
  rowData = rowData.filter((value, index) => {
    return index !== rowIndex;
  });
  gridOptions.api.setRowData(rowData);
};
// ...

Войти в полноэкранный режим Выйти из полноэкранного режима

Значения из элементов rowData также привязываются к сетке, то есть если пользователь изменит статус завершения или название задачи, новое значение будет отражено в одном из элементов rowData.

После всех этих изменений пользовательский интерфейс приложения стал полностью функциональным и выглядит следующим образом:

Осталось только реализовать функцию сохранения и восстановления. Для этого вам придется вернуться к основному процессу.

Добавление нативной функциональности

Внутри файла src/main.js создайте новую функцию, handleCommunication(), для обработки реализации IPC:

const { app, BrowserWindow, ipcMain, dialog } = require("electron");
// ...

const handleCommunication = () => {
  ipcMain.removeHandler("save-to-file");
  ipcMain.removeHandler("restore-from-file");
  ipcMain.handle("save-to-file", async (event, data) => {
    try {
      const { canceled, filePath } = await dialog.showSaveDialog({
        defaultPath: "todo.json",
      });

      if (!canceled) {
        await fs.writeFile(filePath, data, "utf8");

        return { success: true };
      }
      return {
        canceled,
      };
    } catch (error) {
      return { error };
    }
  });
  ipcMain.handle("restore-from-file", async () => {
    try {
      const { canceled, filePaths } = await dialog.showOpenDialog({
        properties: ["openFile"],
        filters: [
          {
            name: "json",
            extensions: ["json"],
          },
        ],
      });

      if (!canceled) {
        const [filePath] = filePaths;
        const data = await fs.readFile(filePath, "utf8");

        return { success: true, data };
      } else {
        return { canceled };
      }
    } catch (error) {
      return { error };
    }
  });
};
// ...

Вход в полноэкранный режим Выход из полноэкранного режима

Сначала, используя ipcMain.removeHandler(), убедитесь, что к используемым каналам не прикреплены существующие обработчики на случай повторной активации окна (специфично для macOS). Метод ipcMain.handle() позволяет вам обрабатывать определенные события и отвечать данными, просто возвращая значение из обработчика.

В данном приложении используются каналы "save-to-file" и "restore-from-file". Их обработчики используют модуль dialog для вызова встроенных диалогов открытия или сохранения системы. Полученные пути затем передаются встроенному в Node.js модулю fs для чтения из файла или записи в файл.

handleCommunication() следует вызывать из функции createWindow():

// ...
const createWindow = () => {
  const mainWindow = new BrowserWindow({
    webPreferences: {
      preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
    },
  });

  mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
  mainWindow.webContents.openDevTools();
  handleCommunication();
};
// ...

Вход в полноэкранный режим Выход из полноэкранного режима

Чтобы иметь возможность отправить IPC-сообщение из процесса рендеринга, вам придется использовать скрипт предварительной загрузки и модуль contextBridge:

// src/preload.js
const { contextBridge, ipcRenderer } = require("electron");

contextBridge.exposeInMainWorld("electronAPI", {
  saveToFile(data) {
    return ipcRenderer.invoke("save-to-file", data);
  },
  restoreFromFile() {
    return ipcRenderer.invoke("restore-from-file");
  },
});

Вход в полноэкранный режим Выход из полноэкранного режима

contextBridge.exposeInMainWorld() безопасно раскрывает предоставленный API для процесса рендеринга. Помните, что в сценарии предварительной загрузки вы получаете доступ к привилегированным API, которые, по соображениям безопасности, должны быть свободно доступны из фронтенда вашего приложения Electron.

Открытые методы используют модуль ipcRenderer для отправки сообщений слушателю ipcMain на другом процессе. В случае канала "save-to-file" предоставляются дополнительные данные в виде строки JSON для сохранения.

Когда мост готов, вы можете вернуться к процессу рендеринга и завершить интеграцию, добавив соответствующие обработчики для двух последних кнопок:

// ...
const saveToFile = () => {
  window.electronAPI.saveToFile(JSON.stringify(rowData));
};
const restoreFromFile = async () => {
  const result = await window.electronAPI.restoreFromFile();

  if (result.success) {
    rowData = JSON.parse(result.data);
    gridOptions.api.setRowData(rowData);
  }
};
// ...

Вход в полноэкранный режим Выход из полноэкранного режима

Каждый обработчик использует методы из объекта electronAPI, доступного на window. В saveToFile(), rowData строится и отправляется в основной процесс для записи в файл. В случае операции восстановления сначала await получает строгированное содержимое файла, чтобы затем разобрать и присвоить его сетке.

Теперь ваше приложение может использовать родной диалог файла для восстановления и сохранения своего состояния:

Итоговое приложение выводит JSON-файл, как показано ниже:

[{"task":"Learn Electron","completed":true},{"task":"Learn AG Grid","completed":false}]

Вход в полноэкранный режим Выход из полноэкранного режима

Заключение

Теперь вы знаете, как использовать AG Grid в приложении Electron. Вы узнали о процессах Electron — что это такое и как они работают. Наконец, вы установили IPC для взаимодействия между ними, чтобы реализовать в вашем приложении встроенную функциональность.

Как вы видели, благодаря Electron вы можете быстро превратить свое веб-приложение в устанавливаемое нативное приложение для настольных компьютеров со всеми сопутствующими функциональными возможностями.

AG Grid — это высокопроизводительная библиотека таблиц JavaScript, которую легко настроить. Она хорошо интегрируется с вашими любимыми фреймворками, такими как React, и отлично работает с другими частями экосистемы JavaScript, такими как Electron. Ознакомьтесь с официальной документацией, чтобы узнать больше.

Оцените статью
devanswers.ru
Добавить комментарий