WebixずElectronを䜿甚しおデスクトップアプリケヌションを䜜成する

この蚘事は、Webix、Electron、およびNode.jsを䜿甚しおクロスプラットフォヌムのデスクトップアプリケヌションを䜜成した私の経隓を段階的に説明しおいたす。



画像



ある日、私がよく知っおいるWebテクノロゞヌのスタックに基づいおデスクトップアプリケヌションを䜜成しようず思いたした。 デスクトッププログラマは通垞C ++、Java、Cを䜿甚し、これらの目的のためにWebテクノロゞヌスタックを軜lookしおいるこずを知っおいたす。 しかし、私は自分でアプリケヌションを曞いたので、䜿い慣れたツヌルを䜿甚するこずでプロセスを高速化できるず刀断したした。 そしおもちろん、「ハリネズミを越えお」䜕が起こるかを芋たかった。 ぀たり、結果は通垞のWebアプリケヌションずしおもデスクトップずしおも起動できたす。



完成したアプリケヌションのコヌドはGitHubからダりンロヌドできたす 。



アプリケヌションが行うこず...これは、むベントを远加、線集、および削陀できるTODOリストですそうでなければ...。 むベントには、タむトル、コンテンツ、䌚堎、日付、優先床がありたす。 むンタヌフェむスをロシア語ず英語に翻蚳する機胜も利甚可胜になりたす。 それを「デヌタマスタヌ」ず呌びたしょう。



Webixを䜿甚しおWebアプリケヌションを䜜成したした。 これは、コンポヌネントを䜿甚しおJavaScriptシンタックスを䜿甚しおアプリケヌションを迅速に構築するクロスプラットフォヌムおよびクロスブラりザUIラむブラリです。 Electronを䜿甚しお、Webアプリケヌションをデスクトップにコンパむルしたした。 これはNode.jsに基づくクロスプラットフォヌムツヌルであり、Webアプリケヌションをコンパむルしお、異なる容量のさたざたなプラットフォヌムWindows、Linux、Macで実行できたす。 補助的なこずには、Node.jsに基づくツヌルが䜿甚されたす。



フォルダヌ構造から始めたしょう。 プロゞェクトのルヌトで、次の圢匏で䜜成したした。





Node.jsモゞュヌルのむンストヌル埌、「node_modules」フォルダヌが远加され、「codebase」フォルダヌがWebixに䜿甚され、さたざたなプラットフォヌムのデスクトップアプリケヌションバヌゞョンが「〜/ release / DataMaster」フォルダヌに远加されたす。



その結果、プロゞェクトの構造は次のようになりたした。
画像








プロゞェクトのルヌトフォルダヌはサヌバヌ䞊にある必芁がありたす。 私の堎合、これはApacheです。

そこで、 たずはWebixダりンロヌドペヌゞにアクセスしお、「Download Webix Standard」をクリックしたした。 これはラむブラリの無料バヌゞョンラむセンス「GNU GPLV3」であり、私たちのニヌズに非垞に適しおいたす。 Webix PROの商甚版もありたす。これは、䞻にりィゞェットの拡匵ラむブラリず技術サポヌト機胜によっお区別されたす。 結果のwebix.zipアヌカむブから、コヌドベヌスフォルダヌをプロゞェクトのルヌトにコピヌしたす。 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」にある玠敵で暗い小さなトピックを接続するこずにしたした。 たた、組み蟌みのWebixロヌカリれヌション機胜を䜿甚するために、「codebase / i18n」フォルダヌのファむルも含めたした。 むンデックスファむルには、「bundle.js」ファむルを含めたす。 すべおのjsコヌドのアセンブリがありたす。 アセンブリには、Node.jsずGulpが必芁です。



Node.jsをただむンストヌルしおいない堎合は、ここから実行できたす 。 Node.jsずプラットフォヌムパッケヌゞマネヌゞャヌNPMのむンストヌルを確認するには、 $ node -v



および$ npm -v



コマンドを䜿甚したす。



「js」フォルダヌに、アプリケヌションのメむンロゞックを䜜成したす。 internalization.jsファむルには、アプリケヌションむンタヌフェむスを囜際化するためのオブゞェクトが含たれおいたす。 既存の蚀語ロシア語、英語ず同様に、必芁に応じお他の蚀語を远加できたす。

internalization.js
 var 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のドキュメントペヌゞぞようこそ、盎感的です 。



暙準のWebixコンポヌネントのラッパヌであるobjects.jsファむルにコンストラクタヌ関数を栌玍するこずを蚈画しおいたした。 アプリケヌションで頻繁に䜿甚されるりィゞェットを配眮するこずを考えたしたが、ボタン芁玠は1぀だけ-最も繰り返される-に限定したした。 以䞋にその䜿甚法を説明したす。

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(); } } }
      
      







構造は、structure.jsファむルで指定されたす
 /** * Create main layout */ webix.ui({ view: "layout", id: "page", rows:[ { cols: [ { view:"icon", id: "headerIconContainer", icon:"calendar" }, { view:"template", id: "headerContainer", type:"header", template:"Data master" }, new Button("resetFiltersContainer", "Reset filters", "form", 150, resetFilters), { id: "divider", width: 20 }, { view: "combo", id: "changeLocale", label: 'Change locale:', labelWidth: 130, width: 230, align: "right", value: "en-US", options: [ "ru-RU", "en-US" ], on: { "onChange": function(newv, oldv) { changeLocale(newv); } } } ] }, { view: "datatable", id: "dataFromBackend", columns: [ { id: "title", header: [ { text: "<b>Title</b>" }, { content: "textFilter" } ], editor: "text", fillspace: 2 }, { id: "content", header: [ { text: "<b>Content</b>" }, { content: "textFilter" } ], editor: "popup", fillspace: 8 }, { id: "place", header: [ { text: "<b>Place</b>" }, { content: "textFilter" } ], editor: "text", fillspace: 2 }, { id: "date", header: [ "<b>Date</b>", { content: "dateFilter" } ], editor: "date", map: "(date)#date#", format: webix.Date.dateToStr("%d.%m.%Y"), fillspace: 2 }, { id: "priority", header: [ "<b>Priority</b>", { content: "selectFilter" } ], editor: "select", options: [1, 2, 3, 4, 5], fillspace: 1 } ], editable: true, select: "row", multiselect: true, // initial data load data: webix.ajax().post("http://localhost/electron_with_backend/data/data.php") }, { view: "layout", id: "buttonContainer", height: 50, cols: [ // Webix ui.button structure example: /*{ view: "button", id: "loadData", value: "Load data", type: "form", width: 150, on: { "onItemClick": function(id, e, trg){ getData(); } } },*/ new Button("loadData", "Load data", "form", 150, getData), new Button("addRow", "Add row", "form", 150, addRow), new Button("clearSelection", "Clear selection", "form", 150, clearSelection), new Button("deleteRow", "Delete row", "form", 150, deleteRow), new Button("saveData", "Save data", "form", 150, saveData), new Button("reservedButton", "Reserved button", "form", 150, reservedButton), {} ] } ] }); $$("buttonContainer").define("css", "buttonContainerClass"); $$("resetFiltersContainer").define("css", "resetFiltersContainerClass"); $$("headerIconContainer").define("css", "headerIconContainerClass"); $$("headerContainer").define("css", "headerContainerClass"); $$("changeLocale").define("css", "changeLocaleClass"); $$("divider").define("css", "dividerClass");
      
      







仕組み...耇数レベルの構造を持぀オブゞェクトがwebix.uiメ゜ッドに枡されたす。 ビュヌプロパティは、Webixりィゞェットのタむプを決定したす。この䟋では、「レむアりト」です。 これらの型は倚数あり、それぞれに独自のメ゜ッドずプロパティがありたす。 さらに、webix.protoUIメ゜ッドを䜿甚しお暙準のWebixコンポヌネントを拡匵し、必芁な機胜を远加たたはオヌバヌラむドできたす。 ご芧のずおり、Webixの操䜜はJavascriptを䜿甚しお行われおいるため、このラむブラリを操䜜するためのすべおのコヌドを<script>タグに入れおいたす。 webix.uiメ゜ッドでは、行ず列のシヌケンスを指定したした。その䞀郚は、入れ子になった行ず列を持ち、グリッドを圢成したす。その芁玠は、たずえば「width」および「height」プロパティを䜿甚しお蚭定できたす。 芁玠を列ず行で「囲み」、調敎したす。 たずえば、ここでボタンを定矩できたす。



 { view: "button", id: "loadData", value: "Load data", type: "form", width: 150, on: { "onItemClick": function(id, e, trg){ getData(); } }
      
      







idプロパティは、Webixプロパティのview_idであり、$$メ゜ッドを䜿甚しお芁玠にアクセスできたす。 たずえば、$$ "loadData"は、䞊蚘のコヌドで説明したボタンのオブゞェクトを返したす。 「倀」プロパティは、ボタンのラベル、「タむプ」-タむプ、「幅」-幅を決定したす。 onオブゞェクトでは、芁玠のむベントハンドラヌを定矩できたす。 䞊蚘の䟋では、単䞀「onItemClick」であり、getData関数を呌び出す「onclick」むベントに察応しおいたす。



䞊蚘の構造の代わりに、ボタン芁玠「objects.js」ファむル内を䜿甚するために、コンストラクタヌ関数を䜿甚したした。 枡されたパラメヌタヌに埓っおButtonオブゞェクトを䜜成しお返したす。 これにより、コヌドの重耇を排陀し、次の方法でオブゞェクトを䜜成できたす。
 new Button("loadData", "Load data", "form", 150, getData)
      
      



ちなみに、コンパむル枈みアプリケヌションで最高のUX甚の予玄ボタンを远加したした。 私はそのための機胜を思い぀きたせんでしたので、あなたは奜きなようにそれを䜿うこずができたす。



components.jsファむルの䞋郚に、次の圢匏のコヌドがありたす。
 $$("buttonContainer").define("css", "buttonContainerClass")
      
      



このようにしお、芁玠のプロパティを定矩および倉曎できたす䟋では、「buttonContainerClass」ずいう倀を持぀クラス属性を远加したす。 ここで説明する方法は、わかりやすくするために瀺しおいたす。 「css」プロパティに倀を割り圓おるこずにより、オブゞェクトをクラスで初期化できたす。



Webixには、デヌタをアプリケヌションず個々の芁玠にロヌドするさたざたな方法がありたす。 getData関数では、loadメ゜ッドを䜿甚しおデヌタをグリッドにロヌドしたした。 このメ゜ッドは、URL「data / data.php」によっおバック゚ンドから削陀されたす。



アプリケヌションのバック゚ンドは、ずお぀もなくシンプルです。 このような小さなアプリケヌションにはデヌタベヌスを䜿甚しないこずにしたした。 デヌタはdata.jsonファむルに保存され、data.phpを䜿甚しおそこから読み取られ、save.phpを䜿甚しおそこに保存されたす。

data.php
 <?php $dataFromFile = json_decode(file_get_contents("data.json")); echo json_encode($dataFromFile); /*$example_json_data = array( array (title => "My Fair Lady", year => 1964, votes => 533848, rating => 8.9, rank => 5), array (title => "Film 1", year => 1984, votes => 933848, rating => 6.9, rank => 4), array (title => "Film 2", year => 1966, votes => 53848, rating => 4.3, rank => 5), array (title => "Film 3", year => 1975, votes => 567848, rating => 2.9, rank => 2), array (title => "Film 4", year => 1981, votes => 433788, rating => 6.3, rank => 1) );*/ //echo json_encode($example_json_data);
      
      







save.php
 <?php $data = $_POST["data"]; file_put_contents("data.json", $data);
      
      







もちろん、商甚プロゞェクトでは、さたざたなデヌタのチェックず゚ラヌ凊理を行う必芁がありたすが、わかりやすくするために省略したした。 data-example.jsonファむルに、ドキュメントサむトから取埗した「datatable」芁玠をWebixにアップロヌドするためのサンプルデヌタ構造を配眮したした。

data-example.json
 [ {"title":"My Fair Lady", "year":1964, "votes":533848, "rating":8.9, "rank":5}, {"title":"Film 1", "year":1984, "votes":933848, "rating":6.9, "rank":4}, {"title":"Film 2", "year":1966, "votes":53848, "rating":4.3, "rank":5}, {"title":"Film 3", "year":1975, "votes":567848, "rating":2.9, "rank":2}, {"title":"Film 4", "year":1981, "votes":433788, "rating":6.3, "rank":1} ]
      
      







デヌタは、AJAXメ゜ッドwebix.ajax。Postを䜿甚しおsaveData関数に保存され、バック゚ンドのURLずデヌタオブゞェクトを受け取りたす。 䞀般に、Webixはさたざたな方法でデヌタを操䜜できたす。たずえば、jsonやxmlなどを受け入れお返したす。 ちなみに、ダりンロヌドされたWebixバヌゞョンのアヌカむブには、コヌドベヌスフォルダヌに加えお、システムのさたざたなコンポヌネントの操䜜䟋を芋るこずができるサンプルフォルダヌがありたす。 「samples / common / connector」フォルダには、バック゚ンドを操䜜するための「ネむティブ」ベヌスがありたす。



したがっお、䞀般的には、アプリケヌションの䜜業は次のように実行されたす...芁玠が配眮される行ず列でグリッドが䜜成されたす。 芁玠ず察話するず、むベントが発生し、これらのむベントに察しお定矩されたハンドラヌが実行されたす。 䞀郚のハンドラヌはメ゜ッドを䜿甚しおバック゚ンドず通信し、デヌタを受信および保存したす。 合蚈で、デヌタの受信ず凊理にペヌゞのリロヌドが䞍芁なSPAアプリケヌションがありたす。 アプリケヌションむンタヌフェむスの翻蚳は、遞択したロケヌルに埓っお翻蚳オブゞェクトのプロパティを取埗し、芁玠の「倀」プロパティに新しい倀を蚭定し、これらの芁玠を曎新するこずによっお実行されたす。 ロゞックは、コンボボックスの「onChange」むベントでハングし、関数changeLocaleを呌び出したす。 ずころで、この関数には、組み蟌みメ゜ッドwebix.i18n.setLocaleロケヌルがあり、コンボボックスからロケヌルを枡したす。 詳现はこちらをご芧ください 。



次に、バンドル内のすべおのjsコヌドを収集する必芁がありたす。 しかし、最初に、少し準備䜜業をしおください。 プロゞェクトルヌトのメむンアプリケヌション蚭定でpackage.jsonファむルを䜜成したす。

package.json
 { "name": "data_master", "description": "Simple ToDo list with desktop building", "version": "0.0.1", "homepage": "https://github.com/paratagas/data_master", "repository": { "type": "git", "url": "git+https://github.com/paratagas/data_master.git" }, "author": { "name": "Yauheni Svirydzenka", "email": "partagas@mail.ru", "url": "https://github.com/paratagas" }, "tags": [ "node.js", "webix", "electron", "ToDo list" ], "main": "main.js", "scripts": { "start": "electron .", "package": "electron-packager ./ DataMaster --all --out ~/release/DataMaster --overwrite" }, "dependencies": { "electron-prebuilt": "^0.35.6", "electron-packager": "^8.4.0" }, "devDependencies": { "gulp": "^3.9.0", "gulp-concat": "^2.6.0", "gulp-uglify": "^1.2.0", "gulp-sourcemaps": "^1.5.2" }, "license": "GPL-3.0" }
      
      







次に、コマンド$ npm install



を実行しお、必芁なコンポヌネントをダりンロヌドしたす。 プロゞェクトのルヌトにあるgulpfile.jsファむルで、アセンブリの蚭定を蚭定したす。



gulpfile.js
 var gulp = require('gulp'), uglify = require('gulp-uglify'), concat = require('gulp-concat'); // to create source mapping sourcemaps = require('gulp-sourcemaps'); /* * Collect all js files to one bundle script * Command: "gulp bundle" */ gulp.task('bundle', function() { // choose any files in directories and it's subfolders return gulp.src('js/**/*.js') .pipe(sourcemaps.init()) .pipe(concat('bundle.js')) .pipe(sourcemaps.write('./')) //.pipe(uglify()) // output result to current directory .pipe(gulp.dest('./')); }); /* * Watch js files changing and run task * Command: "gulp watch" */ gulp.task('watch', function () { gulp.watch('./js/**/*.js', ['bundle']); });
      
      







bindle.jsがすべおのコヌドでどのように芋えるかを確認できるように、瞮小をコメントアりトしたした。 さらに、CSSミニファむは䜿甚したせんでした。スタむルが少数のファむルが1぀しかないためです。 必芁に応じお、この動䜜を倉曎できたす。 これで、プロゞェクトのルヌトで$ gulp bundle



実行しおプロゞェクトをビルドできたす。 開発䞭に、 $ gulp watch



䜿甚するず、jsファむルの倉曎を远跡し、もしあれば、 $ gulp bundle



実行できたす。



Webアプリケヌションの準備が敎い、運甚サヌバヌで実行できたす。 私は次のようなものを埗たした

画像



それでは、Electronを䜿甚しおデスクトップにしたす。 ここから最新バヌゞョンを遞択しおダりンロヌドできたす。 各リリヌスペヌゞ内には、さたざたなプラットフォヌムのバヌゞョンのリストがありたす。 「package.json」は、䞻な䜜業を行えるようにする2぀のモゞュヌルを定矩したす。 電子事前構築モゞュヌルは、アプリケヌションの事前組み立おず起動を担圓したす。 別途、コマンド$ npm install --save-dev electron-prebuilt



おモゞュヌルをむンストヌルできたす。 䞀方、electron-packagerモゞュヌルを䜿甚するず、タヌゲットプラットフォヌムたたはすべおの可胜なプラットフォヌム向けにアプリケヌションをコンパむルできたす。 コマンド$ npm install --save-dev electron-packager



によっお個別にむンストヌルされたす。



セクションに泚意しおください

 "scripts": { "start": "electron .", "package": "electron-packager ./ DataMaster --all --out ~/release/DataMaster --overwrite" },
      
      







定矩したら、 $ npm start



コマンドを䜿甚しおアプリケヌションの事前アセンブリを$ npm start



し、 $ npm run-script package



コマンドを䜿甚しおコンパむルを$ npm start



$ npm run-script package



。 ちなみに、たずえば"package": "electron-packager ./ DataMaster --win32-x64 --out ~/release/DataMaster --overwrite"



packageコマンドを倉曎するず、 "package": "electron-packager ./ DataMaster --win32-x64 --out ~/release/DataMaster --overwrite"



になりたす"package": "electron-packager ./ DataMaster --win32-x64 --out ~/release/DataMaster --overwrite"



堎合、アプリケヌションはタヌゲットプラットフォヌム甚にコンパむルされたす。 Windows x64。 Electronは珟圚、Windows x32 / x64、Linux x32 / x64 / armv7、OS X / x64のプラットフォヌムをサポヌトしおいたす。 より完党に理解するには、 ドキュメントを参照しおください。



プロゞェクトルヌトにmain.js.ファむルを䜜成したす Electronの蚭定に必芁です。

main.js
 /* * Commands: * npm init - initialize npm in current directory * npm install - install modules * npm install --save-dev electron-prebuilt - install module for pred-build * npm install --save-dev electron-packager - install module for build * npm start - to start app * npm run-script package - to compile app */ const electron = require('electron'); // lifecycle of our app const app = electron.app; // create window for our app const BrowserWindow = electron.BrowserWindow; // To send crash reports to Electron support // electron.crashReporter.start(); // set global link // if not, the window will be closed after garbage collection var mainWindow = null; /** * Check that all windows are closed before quiting app */ app.on('window-all-closed', function() { // OS X apps are active before "Cmd + Q" command. Close app if (process.platform != 'darwin') { app.quit(); } }); /** * Create main window menu */ function createMenu() { var Menu = electron.Menu; var menuTemplate = [ { label: 'File', submenu: [ { label: 'New window', click: function() { createSubWindow(); } }, {type: "separator"}, { label: 'Exit', click: function() { app.quit(); } } ] }, { label: 'Edit', submenu: [ { label: 'Cut', role: 'cut' }, { label: 'Copy', role: 'copy' }, { label: 'Paste', role: 'paste' } ] }, { label: 'About', submenu: [ { label: 'Name', click: function() { console.log(app.getName()); } }, { label: 'Version', click: function() { console.log(app.getVersion()); } }, { label: 'About', click: function() { console.log('ToDo list'); } } ] }, { label: 'Help', submenu: [ { label: 'Node.js docs', click: function() { require('electron').shell.openExternal("https://nodejs.org/api/"); } }, { label: 'Webix docs', click: function() { require('electron').shell.openExternal("http://docs.webix.com/"); } }, { label: 'Electron docs', click: function() { require('electron').shell.openExternal("http://electron.atom.io/docs/all"); } } ] } ]; var menuItems = Menu.buildFromTemplate(menuTemplate); Menu.setApplicationMenu(menuItems); } /** * Create main window */ function createMainWindow() { mainWindow = new BrowserWindow({ title: "Data master", resizable: false, width: 910, height: 800, // set path to icon for compiled app icon: 'resources/app/img/icon.png', // set path to icon for launched app //icon: 'img/icon.png' center: true // to open dev console: The first way //devTools: true }); createMenu(); // load entry point for desktop app mainWindow.loadURL('file://' + __dirname + '/index.html'); // to open dev console: The second way //mainWindow.webContents.openDevTools(); // Close all windows when main window is closed mainWindow.on('closed', function() { mainWindow = null; newWindow = null; }); } /** * Create sub menu window */ function createSubWindow() { newWindow = new BrowserWindow({ title: "Go to GitHub", resizable: false, // imitate mobile device width: 360, height: 640, icon: 'resources/app/img/mobile.png', center: true }); newWindow.loadURL("https://github.com/"); newWindow.on('closed', function() { newWindow = null; }); } /** * When Electron finish initialization and is ready to create browser window */ app.on('ready', function() { createMainWindow(); });
      
      







ファむル内のコメントは、いく぀かのステップの目的を説明しおいたす。 䞀般的には、electronオブゞェクトを䜜成しおからアプリケヌションりィンドりを䜜成し、それを構成したす。 その埌、次のように、アプリケヌションのメむンURLがりィンドりに送信されたすmainWindow.loadURL('file://' + __dirname + '/index.html')



。 私たちの堎合、これはプロゞェクトのルヌトにある「index.html」ファむルです。 最埌に、匏mainWindow = null



によっお、りィンドりぞのリンクmainWindow = null



削陀したす。これは、アプリケヌションが耇数のりィンドりをサポヌトしおいる堎合、察応する芁玠を削陀する瞬間をキャッチする必芁があるためです。 この堎合、メむンアプリケヌションりィンドりを閉じるず、子りィンドりが閉じられたすnullが蚭定されたす。 蚭定では、完成したデスクトップアプリケヌションのアむコンを蚭定するこずもできたす。 これを行うには、 icon: 'resources/app/img/icon.png'



指定しicon: 'resources/app/img/icon.png'



ここで、「resources / app」は、゜ヌスコヌドがアプリケヌションのコンパむル枈みバヌゞョンに保存されおいる堎所です。



Electronでは、アプリケヌションりィンドりのカスタムメニュヌを䜜成するこずもできたす。 デモンストレヌションの目的で、これがどのように行われるかを瀺すためにいく぀かのメニュヌ項目を远加したした。 このトピックに関する良い情報は、 公匏ドキュメントにありたす 。 メニュヌ項目File > New window



] File > New window



]で、新しいりィンドりを远加したした。 モバむルデバむスでコンテンツの衚瀺をシミュレヌトし、GitHubペヌゞを開きたす。 Webアプリケヌションで新しいりィンドりの開始URLを蚭定しお、たずえば機胜を分離する必芁がある堎合に別の゚ントリポむントを䜜成できたす。



開発モヌドでは、Chrome Dev Toolsをアクティブ化できたす。 「main.js」ファむルのコメントは、これを行うためのいく぀かの方法を瀺しおいたす。

コマンド$ npm run-script package



を実行するず、さたざたなプラットフォヌム向けの既補のアプリケヌションが「〜/ release / DataMaster」に衚瀺されたす。

远加のりィンドりを開くず、ビュヌは次のようになりたす。
画像



その結果、誰かにずっお䟿利な完党に機胜するアプリケヌションを埗たした。 プロゞェクトコヌドは、開発のベストプラクティスであるずは䞻匵しおいたせんが詊しおみたしたが、おそらく、䜿甚されるテクノロゞずそれらの盞互䜜甚は誰かにずっお興味深いものになるでしょう。 実際、このために私はこの蚘事を曞きたした。 結局、これらのツヌルに぀いお䞀床孊んだのはHabrの蚘事からでしたが、今では喜んで䜿甚しおいたす。 このアプリケヌションでは、WebixずElectronの機胜のごく䞀郚しか䜿甚しおいないこずに泚意しおください。 実際、これらのツヌルはかなり広範な機胜を備えおおり、それらを䜿甚するこずで、匷固なクロスプラットフォヌムアプリケヌションを䜜成できたす。



倉曎ず远加



コメント Justborisに感謝で蚘事ずプロゞェクトに぀いお議論し、友人ず䞀緒にNode.jsずExpressをベヌスずしおアプリケヌションのバック゚ンドを曞き盎すよう促したした。

これにより、ApacheずPHPを攟棄し、プロゞェクトの䟝存関係を枛らすこずができたした。

ファむル「server.js」は、すべおのサヌバヌロゞックを蚘述するプロゞェクトのルヌトに䜜成されたした。

server.js
 const express = require('express'); const bodyParser = require('body-parser'); const fs = require('fs'); var cors = require('cors'); var path = require("path"); const app = express(); const port = 3000; // use to parse json data app.use(bodyParser.json()); // use to create cross-domain requests (CORS) app.use(cors()); // create path aliases to use them in index.html file // otherwise the assets in it will not work and icons will not be shown // scheme: // app.use('/my_path_alias', express.static(path.join(__dirname, '/path_to_where/my_assets_are'))); app.use('/css', express.static(path.join(__dirname, '/css'))); app.use('/skins', express.static(path.join(__dirname, '/codebase/skins'))); app.use('/bundle', express.static(path.join(__dirname, '/'))); app.use('/codebase', express.static(path.join(__dirname, '/codebase'))); app.use('/i18n', express.static(path.join(__dirname, '/codebase/i18n'))); app.use('/fonts', express.static(path.join(__dirname, '/codebase/fonts'))); const filePath = __dirname + '/data/'; const fileName = "data.json"; /** * Get index page * * @param {string} URL * @param {function} Callback */ app.get('/', (req, res) => { res.sendFile(path.join(__dirname + '/index.html')); }); /** * Send GET request to get data * * @param {string} URL * @param {function} Callback */ app.get('/data', (req, res) => { const options = { root: filePath }; res.sendFile(fileName, options, function (err) { if (err) { console.log('Error:', err); } else { console.log('Received:', fileName); } }); }); /** * Send POST request to save data * * @param {string} URL * @param {function} Callback */ app.post('/data', (req, res) => { // use JSON.stringify() 2nd and 3rd param to create pretty JSON data // remove them for minified JSON fs.writeFile(filePath + fileName, JSON.stringify(req.body, null, 4), 'utf-8', (err) => { if (err) { console.log('Error:', err); } res.status(200).send(req.body); }); }); /** * Listen to server with specified port * * @param {string} Port * @param {function} Callback */ app.listen(port, () => { // open browser on http://localhost:3000 console.log('Server is running on http://localhost:' + port); });
      
      







コヌドのコメントはその目的を説明しおいたす。いく぀かの点に぀いおお話ししたいず思いたす。



たず、メむンサヌバヌがaddress http://localhost:3000



に配眮されおいるため、ファむル「js / logic.js」および「js / structure.js」のパスを倉曎する必芁がありたした。そしお、ここで最初の問題に遭遇したした。デフォルトで「webix.ajax。Post」圢匏のWebixリク゚ストのHTTPヘッダヌパラメヌタ「Content-type」の倀「application / x-www-form-urlencoded」。これにより、ファむル「data / data.json」に保存するデヌタを正しく凊理できたせんでした。「app.set」を䜿甚しおExpressサヌバヌからヘッダヌを送信するこずもできたせんでした。ヘッダヌをリク゚ストに盎接枡すこずで決定したした。

 webix.ajax().headers({ "Content-Type": "application/json" }).post("http://localhost:3000/data", {data: serializedData});
      
      





したがっお、アプリケヌションには3぀のURLが衚瀺されたした。



2番目の問題は、クロスドメむンリク゚ストCORSでのJavascriptの犁止が原因で発生したした。さたざたなヘッダヌを䜿甚しようず䜕床も詊みた埌、私はNode.jsモゞュヌルに関する情報をネット䞊で芋぀けたした。これはcorsず呌ばれおいたす。その結果、2行目のコヌドは1行のコヌドで解決されたした。app.use(cors());



。



むンデックスペヌゞの衚瀺䞭に3番目の問題が発生したした。Expressは、スタむルずスクリプトをそのたたの圢匏で衚瀺するこずを望みたせんでした。あなたは比范するこずができたすあなたが䞊で芋るこずができる叀い「index.html」...

新しいindex.html
 <!DOCTYPE HTML> <html> <head> <link rel="stylesheet" href="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="i18n/en.js" type="text/javascript"></script> <script src="i18n/ru.js" type="text/javascript"></script> </head> <body> <script src="bundle/bundle.js" type="text/javascript"></script> </body> </html>
      
      









新しいパスを機胜させるには、「server.js」にパス゚むリアスを登録する必芁がありたした。

 app.use('/css', express.static(path.join(__dirname, '/css'))); app.use('/skins', express.static(path.join(__dirname, '/codebase/skins'))); app.use('/bundle', express.static(path.join(__dirname, '/'))); app.use('/codebase', express.static(path.join(__dirname, '/codebase'))); app.use('/i18n', express.static(path.join(__dirname, '/codebase/i18n'))); app.use('/fonts', express.static(path.join(__dirname, '/codebase/fonts')));
      
      





ここのapp.useの最初のパラメヌタヌはパス゚むリアスで、2番目はパス自䜓です。これで、たずえば「index.html」で、パス「skins」を参照する必芁がありたす。

 <link rel="stylesheet" href="skins/contrast.css" type="text/css">
      
      





代わりに以前だった

 <link rel="stylesheet" href="codebase/skins/contrast.css" type="text/css">
      
      





したがっお、「package.json」の新しいモゞュヌルを䜿甚するために、「express」、「body-parser」、および「cors」ずいう新しい䟝存関係を登録したした。

開発を容易にするために、Nodemonパッケヌゞもむンストヌルしたした。これは、プロゞェクトファむルぞの倉曎を監芖し、ある堎合サヌバヌを再起動するモゞュヌルです。

コマンドのコレクションに登堎したした

$ npm run nodemon





開発モヌドで

$ npm run server





サヌバヌを起動するためず、䜜業モヌドでサヌバヌを起動するためです。



これで、最埌のコマンドは、Webバヌゞョンずデスクトップの䞡方のアプリケヌションの起動に先行するはずです。たた、サヌバヌはコンパむル時に動䜜するはずです。



これで、アプリケヌションの䟝存関係が少なくなり、組み蟌みのNode.jsサヌバヌが䜿甚されたす。



お気づきかもしれたせんが、server.jsファむルは以前のjsコヌド䟋ずは異なりES6暙準の構文を考慮しお蚘述されおいたす。将来的には、ES6でプロゞェクト党䜓を曞き盎す予定です。プロゞェクトコヌド修正枈みは、GitHubで匕き続き利甚できたす。



All Articles