テキストエディターを作ってElectronの基礎を学ぼう!
HTML5でデスクトップアプリケーション開発入門

184

前回の記事「Electron入門」では、ElectronというHTML/JavaScriptのフレームワークを使って、デスクトップアプリケーションを作成するまでの手順を紹介しました。

今回は応用サンプルとしてテキストエディターの作り方を解説します。テキストエディターを作成することで、Node.jsのファイルの読み込み/書き込みや、ダイアログモジュールなどアプリケーション開発で必須となるElectronの技術を習得できます。15分程度で試せる内容になってますので、ぜひご覧ください。

※今回はElectron v1.8を使用し、macOS 10.13 High SierraおよびWindows 10にて動作検証を行いました。

完成デモの紹介

今回のサンプルのテキストエディターのデモをご覧ください。このオリジナルのテキストエディターではテキストの編集ができ、JavaScriptファイルのカラーリングにも対応しています。このテキストエディターを一緒に作っていきましょう!

デモのソースコードはGitHubで公開してます。ダウンロードして読み進めてください。

テキストエディターを作ってみよう!

1. ファイル構成

Electronで必要なファイルやアプリケーション作成手順については記事「Electron入門」にまとめています。本記事では今回追加したファイルについて説明します。

前回の記事から内容に変更・追加があったファイルは、画面構成用のindex.html、実装を記述するeditor.js、デザインを記述するためのmain.cssです。BootstrapとAce.jsはCDNを利用します。

2. 画面構成

今回作成したテキストエディターは、[保存する]ボタン・[読み込む]ボタン・[テキスト入力領域]・[フッター]と4つの部品で構成されています。

index.html ファイル

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8"/>
  <title>Electron Text Editor</title>

  <link rel="stylesheet"
        href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
        integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
        crossorigin="anonymous">
  <link href="main.css" rel="stylesheet"/>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.3.3/ace.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.3.3/mode-javascript.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.3.3/theme-twilight.js"></script>
  <script src="editor.js"></script>
</head>
<body>
  <div id="header_fixed">
    <button type="button"
            class="btn-sm btn-primary"
            id="btnLoad">
      読み込む
    </button>
    <button type="button"
            class="btn-sm
            btn-primary"
            id="btnSave">
      保存する
    </button>
  </div>
  <div id="footer_fixed"></div>
  <div id="input_area">
    <div id="input_txt"></div>
  </div>
</body>
</html>

3. テキストエディターライブラリを組み込む

テキスト入力部分には、AceというJSライブラリを使用しました。AceはJavaScriptライブラリで、テキストのシンタックスハイライト、シンタックスチェック、自動補完、タブインデント、入力文字の「やり直し」や「元に戻す」が行える履歴保持機能、文字列の検索などができます。

Aceを使用するには以下のコードで読み込みます。ace.jsがメインのファイル、mode-javascript.jsがシンタックス設定用のファイル、theme-twilight.jsがカラーテーマ用のファイルです。公式サイトに記載もありますが、CDNのサービスであるcloudflareで提供されているので、これを利用します。

index.htmlファイルの該当箇所の抜粋

<script defer src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.3.3/ace.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.3.3/mode-javascript.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.3.3/theme-twilight.js"></script>

次に入力エリアを作成します。ace.edit()関数に入力エリアとなるdiv要素のid属性を引数で渡し、戻り値として入力エリアを操作するためのeditor変数を獲得します。editor.getSession().setMode()関数でシンタックスをJavaScriptに変更し、editor.setTheme()関数でカラーテーマを設定しています。

editor.js ファイルの該当箇所の抜粋

editor = ace.edit("input_txt");
editor.getSession().setMode("ace/mode/javascript");
editor.setTheme("ace/theme/twilight");

※Aceで設定できる詳しいパラメーターは「Ace - How-To Guid」や「Ace - API Reference」をご参照ください。

4. [ファイル選択]ダイアログを使用して、読み込み先ファイルを選択する

[読み込み]ボタンを押した時にopenLoadFile()関数が呼ばれます。この関数内でdialogモジュールの[showOpenDialog()](https://github.com/atom/electron/blob/master/docs/api/dialog.md#dialogshowopendialogbrowserwindow-options-callback)関数を使用して、現在開いているブラウザウィンドウから[ファイル選択]ダイアログを開きます。

第1引数には現在フォーカスされているウィンドウ、第2引数にダイアログのオプション、第3引数にダイアログが閉じられたあとのコールバック関数を指定します。ダイアログでファイルが選択された場合にコールバック関数の第1引数にファイルパスの配列が渡されます。配列が渡ってきた場合に実際にファイルの内容を読み込むreadFile()関数を呼び出します。

editor.js ファイルの該当箇所の抜粋

const {
  BrowserWindow,
  dialog
} = require("electron").remote;

/**
 * ファイルを開きます。
 */
function openLoadFile() {
  const win = BrowserWindow.getFocusedWindow();

  dialog.showOpenDialog(
    win,
    // どんなダイアログを出すかを指定するプロパティ
    {
      properties: ["openFile"],
      filters: [
        {
          name: "Documents",
          extensions: ["txt", "text", "html", "js"]
        }
      ]
    },
    // [ファイル選択]ダイアログが閉じられた後のコールバック関数
    fileNames => {
      if (fileNames) {
        readFile(fileNames[0]);
      }
    }
  );
}

※dialogモジュールの関数で必要な詳しいパラメーターは「electron - dialog」をご参照ください。

5. ファイル内容を読み込む

fsモジュールをrequire()関数で獲得し、fsモジュールのreadFile()関数からファイルの内容を読み込みます。

第1引数で読み込みパス、第2引数で読み込みが終わった際によばれるコールバック関数を指定します。コールバック関数にはエラーが発生した場合のerror(エラーオブジェクト)とtext(読み込んだテキスト)が渡されます。

editor.setValue()関数でテキストエディター部分に文字列を設定します。第1引数で文字列を設定し、第2引数でカーソルの位置を設定します。

※ -1と入れるとテキストの開始地点へ移動し、1とするとテキストの終了位置に移動します。

editor.js ファイルの該当箇所の抜粋

const fs = require("fs");

/**
 * テキストを読み込み、テキストを入力エリアに設定します。
 */
function readFile(path) {
  currentPath = path;
  fs.readFile(path, (error, text) => {
    if (error != null) {
      alert("error : " + error);
      return;
    }
    // フッター部分に読み込み先のパスを設定する
    footerArea.innerHTML = path;
    // テキスト入力エリアに設定する
    editor.setValue(text.toString(), -1);
  });
}

6. [メッセージ]ダイアログを使用して、保存確認をする

保存確認のメッセージの表示には、dialogモジュールのshowMessageBox()関数を使用します。

第1引数には現在フォーカスされているウィンドウ、第2引数にはダイアログのオプション設定、第3引数にはダイアログ選択後のコールバック関数を指定します。コールバック関数の第1引数に選択されたボタンのインデックスが返って来ます。インデックスが「0」つまり「OK」のボタンを押された場合、実際にファイルへ書き込みを行うwriteFile()関数を呼び出します。

writeFile()関数には現在開いているファイルのパスと、テキストエディターに設定されている文字列を引数に指定します。テキストエディターに設定されている文字列はeditor.getEditor()関数で取得します。

editor.js ファイルの該当箇所の抜粋

/**
 * ファイルを保存します。
 */
function saveFile() {
  // 初期の入力エリアに設定されたテキストを保存しようとしたときは新規ファイルを作成する
  if (currentPath === "") {
    saveNewFile();
    return;
  }

  const win = BrowserWindow.getFocusedWindow();

  dialog.showMessageBox(
    win,
    {
      title: "ファイルの上書き保存を行います。",
      type: "info",
      buttons: ["OK", "Cancel"],
      detail: "本当に保存しますか?"
    },
    // メッセージボックスが閉じられた後のコールバック関数
    response => {
      // OKボタン(ボタン配列の0番目がOK)
      if (response === 0) {
        const data = editor.getValue();
        writeFile(currentPath, data);
      }
    }
  );
}

7. ファイルへ書き込む

fsモジュールのwriteFile()関数でファイルを保存します。

第1引数で保存先のパス、第2引数で書き込むデータ、第3引数で保存後のコールバック関数を指定します。コールバック関数経由でエラーが発生した場合のerror(エラーオブジェクト)が返ってきます。

editor.js ファイルの該当箇所の抜粋

var fs = require("fs");

/**
 * ファイルを書き込みます。
 */
function writeFile(path, data) {
  fs.writeFile(path, data, error => {
    if (error != null) {
      alert("error : " + error);
    }
  });
}

おまけ - ドラッグ&ドロップを実装する

デモでは上記で説明した内容以外に、ファイルをドラッグ&ドロップして読み込む処理を実装しました。ドラッグ&ドロップを実装するための記事をQiitaに投稿しましたので、気になった方はご参照ください。

動画で振り返る開発手順

ここまでの開発手順を動画に撮影しました。もし記事を読み進めてわからなかった箇所があれば、動画を参考ください。

終わりに

アプリケーション開発で必須となるファイル入出力やダイアログ表示について、理解できましたでしょうか? みなさんもぜひ素敵なアプリケーションを作成してみてくださいね。

※この記事が公開されたのは3年11ヶ月前ですが、 平成30年3月29日に内容をメンテナンスしています。