Il template di questa settimana è un po’ meno curato dal punto di vista grafico. Non ho avuto il tempo, è stata una settimana molto intensa dal punto di vista lavorativo. Ma ci tenevo comunque a pubblicare qualcosa. Perché è un prototipo interessante, perché mostra alcune potenzialità di Construct 3 e perché è il mio primo test funzionante di qualcosa che voglio sviluppare nei prossimi mesi.
Ma di cosa si tratta? Di un semplice programma per accedere a database Microsoft Access, vedere le tabelle contenute nel database e analizzare la struttura delle stesse. Non fa nulla di più ma mi serviva per testare alcune cose assieme prima di cominciare a lavorare sul serio:
- la possibilità di leggere file su disco locale in maniera semplice e intuitiva
- la possibilità di leggere file MDB tramite JavaScript
- la possibilità di creare un unico file EXE per installare il programma
- la possibilità di aggiornare automaticamente un’applicazione a ogni nuova release pubblicata su GitHub
Ovviamente sapevo che é possibile farlo. Non sapevo come, e quali sarebbero stati i limiti che avrei incontrato. Ma prima vediamo il risultato:
Le impostazioni iniziali
Ho scelto di usare ElectronJS e TypeScript per semplificare la creazione di un’app offline. Ho scelto di appoggiarmi a node-adodb per gestire l’interrogazione dei file MDB. Infine ho deciso di usare Construct 3 per l’interfaccia grafica.
Stabilito questo ho creato la struttura base del progetto. Quindi, da riga di comando ho cominciato a inizializzare e scaricare i vari pacchetti:
npm init
npm i -D electron@latest
npm install typescript --save-dev
npm install electron-builder --save-dev
npm i electron-updater
npm i node-adodb
Riguardo a node-adodb
c’è una nota importante da riportare:
The library need system support
Microsoft.Jet.OLEDB.4.0
orMicrosoft.ACE.OLEDB.12.0
,Windows XP SP2
above supportMicrosoft.Jet.OLEDB.4.0
by default, Others need to install support!Recommended use
Microsoft.ACE.OLEDB.12.0
, download: Microsoft.ACE.OLEDB.12.0
La struttura delle cartelle
Dopo aver scaricato i pacchetti necessari ho stabilito come organizzare le varie cartelle del progetto:
- src: i file typescript del programma
- src_c3: i file di Construct 3
- dist: i file JavaScript e i file esportati di C3
- out: la cartella di destinazione dove salvare il file di installazione
package.json
Il passo successivo è stato configurare il package.json
in modo da rendere possibile compilare i file TypeScript, testare il progetto e pubblicarlo su GitHub.
{
"name": "access-c3",
"productName": "Access C3",
"version": "0.0.1",
"main": "dist/index.js",
}
Comincio con le informazioni base. Nome del repository e punto d’ingresso del programma. Poiché tutti i file TS compilati finiranno nella cartella dist
il punto di ingresso è nella cartella.
Il numero di versione e il productName
sono particolarmente importanti e non vanno sottovalutati. Il primo perché permetterà ad Electron di capire il numero di versione del programma installato ed eventualmente installare un aggiornamento del programma. Il nome del prodotto perché é il nome che avrà il programma quando sarà installato su un computer.
{
"scripts": {
"start": "npm run compile && electron .",
"compile": "tsc",
"build-installer": "electron-builder build --win --publish never",
"publish": "electron-builder build --win --publish always",
}
}
Il contenuto di scripts
serve per semplificare il lancio dell’applicazione (quando la devo testare) e la pubblicazione su GitHub del file finale.
Il comando npm run start
permette di compilare i file TypeScript e di lanciare il programma.
npm run build-installer
serve per creare il file di installazione (ma non di pubblicarlo automaticamente su GitHub)
npm run publish
crea il file di installazione del programma e lo carica direttamente sul repository di GitHub.
{
"build": {
"appId": "access-c3",
"directories": {
"output": "out"
},
"win": {
"target": [
"nsis"
],
"icon": "icon.ico",
"requestedExecutionLevel": "requireAdministrator"
},
"nsis": {
"installerIcon": "icon.ico",
"uninstallerIcon": "icon.ico",
"uninstallDisplayName": "Access C3",
"license": "license.txt",
"oneClick": false,
"allowToChangeInstallationDirectory": true
}
}
}
Su electron.build si possono trovare le spiegazioni dettagliate sulle varie opzioni di configurazione. Ho scelto di usare come target nsis
perché é l’unico che permette una installazione completa senza dover aver un computer connesso a internet.
{
"build": {
"publish": [
{
"provider": "github",
"owner": "el3um4s",
"repo": "access-c3"
}
]
}
}
Questo pezzetto del file di configurazione serve per gestire la pubblicazione su GitHub e configurare gli aggiornamenti automatici
{
"build": {
"extraResources": [
{
"from": "./node_modules/node-adodb/lib/adodb.js",
"to": "adodb.js"
}
]
}
}
Infine quest’ultima opzione serve per utilizzare node-adodb
da Electron.
tsconfig.json
Il secondo file importante per configurare il progetto è stato tsconfig.json
. Qui sono inserite le istruzioni per compilare i file TypeScript in file JavaScript:
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"exclude": [
"node_modules",
"public",
"out/**",
"typings/browser/**"
]
}
Rimando a aka.ms/tsconfig.json per i dettagli delle varie opzioni.
I file TypeScript
Ho usato 3 file TypeScript:
- index.ts
- connection.ts
- listTypeFieldEnum.ts
Sugli ultimi due, se a qualcuno interessa, parlerò in un altro post. Servono per gestire la connessione ai database e per spedire il risultato a Construct 3. Per il momento mi concentro su index.ts
.
Per prima cosa importo i moduli che serviranno nel progetto:
import { app, BrowserWindow, ipcMain } from 'electron';
import { autoUpdater } from "electron-updater"
import * as path from 'path';
import * as Connection from './connection';
import ADODB from 'node-adodb';
Poi imposto l’auto updater per eseguire un controllo ogni 4 ore:
(async () => {
const FOUR_HOURS = 1000 * 60 * 60 * 4;
setInterval(async () => {
await autoUpdater.checkForUpdates();
}, FOUR_HOURS);
await autoUpdater.checkForUpdates();
})();
Se viene trovato un aggiornamento Electron lo scarica e lo installa automaticamente. Dopo di ciò creo la finestra per il programma:
const createWindow = () => {
mainWindow = new BrowserWindow({
width: 854,
height: 480,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
enableRemoteModule: true
}
});
mainWindow.loadURL(path.join(__dirname, 'www', 'index.html'));
mainWindow.webContents.openDevTools();
};
app.on('ready', () => {
app.name = 'Access C3';
createWindow();
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
Bene, ora si tratta di gestire l’invio dei dati da e verso Construct 3. Per farlo è molto semplice usare:
ipcMain.on('list-tables', async (event, message) => {
const positionDB = message.path;
const connection = Connection.open(positionDB);
const schema = await Connection.getListTables(connection);
event.reply('list-tables', schema);
});
ipcMain.on('schema-table', async (event, message) => {
const positionDB = message.path;
const tableName = message.tableName;
const connection = Connection.open(positionDB);
const schema = await Connection.getSchemaTable(connection,tableName);
event.reply('schema-table', schema);
});
Invece per rendere evidente la ricerca e il download dei file di aggiornamento del programma conviene usare:
function sendStatusToWindow(text: string) {
console.log(text);
mainWindow.webContents.send('message', text);
}
autoUpdater.on('checking-for-update', () => {
sendStatusToWindow('Checking for update...');
})
autoUpdater.on('update-available', (info) => {
sendStatusToWindow('Update available.');
})
autoUpdater.on('update-not-available', (info) => {
sendStatusToWindow('Update not available.');
})
autoUpdater.on('error', (err) => {
sendStatusToWindow('Error in auto-updater. ' + err);
})
autoUpdater.on('download-progress', (progressObj) => {
let log_message = "Download speed: " + progressObj.bytesPerSecond;
log_message = log_message + ' - Downloaded ' + progressObj.percent + '%';
log_message = log_message + ' (' + progressObj.transferred + "/" + progressObj.total + ')';
sendStatusToWindow(log_message);
})
autoUpdater.on('update-downloaded', (info) => {
sendStatusToWindow('Update downloaded');
});
Construct 3
Dal punto di vista di Construct 3 invito a consultare il progetto (access-c3.c3p). Concettualmente, ho aggiunto un listener al plugin FileChooser
in modo da poter ottenere la posizione su disco fisso del database e usare ipcRenderer.send("list-tables", { name, path });
per ottenere la lista delle tabelle.
function buttonChooseDatabase_EventListener() {
const chooseDatabase = document.getElementById("choose-database");
chooseDatabase.addEventListener("change", handleFiles, false);
}
function handleFiles() {
const fileList = this.files;
if (fileList) {
const file = fileList[0];
console.log(file);
const name = file.name;
const path = file.path;
ElectronStorage.pathDatabase = path;
ipcRenderer.send("list-tables", { name, path });
}
}
Quando Electron risponde con la lista delle tabelle uso
ipcRenderer.on('list-tables', async (event, arg) => {
ElectronStorage.listTables = arg;
ElectronStorage.listTablesType = new Set();
g_runtime.callFunction("ListTables_Clear");
g_runtime.callFunction("ListTables_Filter_Clear");
ElectronStorage.listTables.forEach(table => {
g_runtime.callFunction("ListTables_Add", table.TABLE_NAME);
ElectronStorage.listTablesType.add(table.TABLE_TYPE);
});
g_runtime.callFunction("ListTables_Filter_Add", "ALL");
ElectronStorage.listTablesType.forEach(tableType => {
g_runtime.callFunction("ListTables_Filter_Add", tableType);
});
});
Questo mi permette di mostrare a schermo i nomi delle tabelle.
Per il momento è tutto. Se ci fossero domande o richieste particolari scriverò una seconda parte di questo articolo. Ma il mio scopo, testare la fattibilità di un programma del genere, è stato raggiunto. Penso di sperimentare ancora su questo progetto ma non so ancora se continuare a usare C3 come interfaccia grafica. Dovrò fare ancora alcuni test.
Ricordo comunque che il codice di questo progetto è disponibile su GitHub: