<!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>
$ node -v
and $ npm -v
check the correctness of the installation of Node.js and the package manager of the platform - NPM. 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: "..." } };
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... }
/** * 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(); } } }
/** * 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");
{ 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)
By the way, I added a reserved button for the best UX in the compiled application. I did not invent functionality for it, so you can use it as you like. $$("buttonContainer").define("css", "buttonContainerClass")
In this way, we can define and change the properties of the elements (in the example: adding an attribute class with the value “buttonContainerClass”). The method shown here is for clarity. We can initially initialize an object with some class, by assigning the value to the “css” property. <?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);
<?php $data = $_POST["data"]; file_put_contents("data.json", $data);
[ {"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} ]
{ "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
command to download the necessary components. In the gulpfile.js file in the root of the project we will define the settings of our assembly. 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']); });
$ gulp bundle
at the root of the project. During development, the $ gulp watch
allows you to track changes to js files and, if available, execute the $ gulp bundle
.$ npm install --save-dev electron-prebuilt
. In turn, the module "electron-packager" allows you to compile applications for the target platform or for all possible platforms. Separately installed by the command $ npm install --save-dev electron-packager
. "scripts": { "start": "electron .", "package": "electron-packager ./ DataMaster --all --out ~/release/DataMaster --overwrite" },
$ npm start
command, and the compilation with the $ npm run-script package
command. By the way, if we change the package command, for example, to "package": "electron-packager ./ DataMaster --win32-x64 --out ~/release/DataMaster --overwrite"
then the application will be compiled for the target platform - in our case Windows x64. Currently Electron supports platforms: Windows x32 / x64, Linux x32 / x64 / armv7, OS X / x64. For a more complete understanding, you can look at the documentation . /* * 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(); });
mainWindow.loadURL('file://' + __dirname + '/index.html')
. In our case, this is the “index.html” file in the project root. At the end, the expression mainWindow = null
removes the link to the window, because if the application supports multiple windows, then you need to catch the moment when you should delete the corresponding element. Closing the main application window in our case closes (assigns null) the child window. In the settings, you can also set the icon of the finished desktop application. To do this, specify the icon: 'resources/app/img/icon.png'
, where “resources / app” is the place where the source code is stored in the already compiled version of the application.File > New window
I added a new window. It simulates viewing content on a mobile device and opens the GitHub page. You can set the starting URL for the new window and in our web application, thus creating another entry point if, for example, you need to isolate any functionality.$ npm run-script package
command and ready-made applications for various platforms appear in "~ / release / DataMaster". 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); });
http://localhost:3000
, I had to change the paths in the files “js / logic.js” and “js / structure.js”. And here I ran into the first problem. The value of the HTTP parameter "Content-type" in Webix requests of the form "webix.ajax (). Post ()" by default: "application / x-www-form-urlencoded". This did not allow us to properly process our data for storage in the data / data.json file. Even the transfer of headers from the Express server using “app.set ()” did not work. Decided by passing the header directly to the request: webix.ajax().headers({ "Content-Type": "application/json" }).post("http://localhost:3000/data", {data: serializedData});
app.use(cors());
. <!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>
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')));
<link rel="stylesheet" href="skins/contrast.css" type="text/css">
<link rel="stylesheet" href="codebase/skins/contrast.css" type="text/css">
$ npm run nodemon
$ npm run server
Source: https://habr.com/ru/post/321134/
All Articles