Docs
contents
concepts
cookbook
Getting Started
Development
- Basic TIBET+Electron Development
- Linting Your TIBET+Electron Application
- Testing Your TIBET+Electron Application
Main and Renderer
- Adding Logic To The Main Process
- Adding Logic To The Renderer Process
- Communicating Between Main And Renderer
- Logging Activity In The Main Process
- Logging Activity In The Renderer Process
Framing
- Automatic Application Versioning and Updates
- Automatic Profile Load and Save
- Adding A Native Menu To Your Electron App
Dialogs & Notifications
- Showing a Native Dialog
- Showing a Native Error Dialog
- Showing a Native Notification
- Showing the User a Dialog Before Quitting
File Operations
code
concepts
It was never about the web…
A long-standing challenge in the IT industry is developing cross-platform applications; applications that run well on Windows®, Mac OS®, and Linux® platforms.
The web, while not strictly an application platform, made it possible to leverage web browsers and web development skills as one way to address the cross-platform development problem. It's not a great solution, but frankly, there haven't been many solid alternatives. Until now.
Electron, a pairing of Chromium and NodeJS, provides a compelling platform for cross-platform application development, one that major companies are already leveraging.
Slack, Microsoft Teams, Visual Studio Code, Postman, Discord, Zoom, Signal, Wordpress, Skype, WhatsApp and dozens of other applications have taken advantage of Electron to create powerful desktop versions, versions that far surpass their browser-based variants, when such variants even exist.
Now it's your turn.
The TIBET solution
Designed from day one to run offline, TIBET is unique among web platforms both in scope and in our focus on desktop application development.
TIBET's client stack includes support for file operations, state and profile management, data localization, advanced keyboard shortcuts, drag-and-drop, context menus, and numerous other features common in desktop applications but rarely supported by web frameworks.
TIBET's server components and development tools help you manage mocking of local and remote service endpoints, unit and integration testing, build/release/deploy cycles, and more.
TIBET's low-code approach not only speeds development, it also features built-in debugger integration. The result is a development process that let's you get more done with less effort.

cookbook
Getting Started
Getting started is easy…
Install TIBET
For Windows, Mac OS, or Linux:
$ npm install -g tibet
$ tibet init
For Docker-based development see the full install guide.
Creating A TIBET+Electron Project
The tibet clone
command is used to create all new TIBET projects.
For an Electron project add --dna electron
to your clone command so your new project is based on our pre-built Electron 'dna':
$ tibet clone {{appname}} --dna electron
$ cd {{appname}}
$ tibet init
Running Your Electron App
The tibet start
command can be used to start any TIBET project, regardless of
the project dna.
Navigate to your project directory and enter tibet start
:
$ cd {{appname}}
$ tibet start
...
NOTE: tibet start
actually delegates to tibet electron
however we recommend you use tibet start
for consistency across your TIBET-based projects.
NOTE: TIBET+Electron projects include package.json
entries to ensure tibet start
is set as the start script. This ensures your packaged applications operate correctly out-of-the-box.
Development
Basic TIBET+Electron Development
TIBET development for Electron-based projects is consistent with TIBET development on other supported platforms. This guide covers a few things unique to Electron (such as main vs. renderer processes) however the rest of your development process is the same.
To get a general feel for TIBET we recommend you spend a few minutes with the TIBET Quickstart and TIBET Essentials guides.
Each of our guides work with TIBET+Electron projects. Simply add --dna electron
to the tibet clone
command you use to create your hello
project.
All of TIBET's command line tools (init
, start
, lint
, test
, build
, release
, deploy
, version
, etc.) work with your Electron project.
Linting Your TIBET+Electron Application
TIBET's built-in JavaScript, CSS, and XML/XHTML linting tools work as expected with Electron projects, ensuring all your "source" is lint-free.
Within your project directory tree enter tibet lint
:
$ tibet lint
checked 0 of 22 total files
(8 filtered, 14 unchanged)
0 errors, 0 warnings. Clean!
Lint checks keep track of file changes so that only recently changed files are tested. You can also focus on just your JavaScript, CSS, or markup if you like.
See the tibet lint manpage for more details.
Testing Your TIBET+Electron Application
Your TIBET+Electron project is testable using the built-in tibet test
command:
$ tibet test
# Loading TIBET platform at 2019-08-02T01:53:29.910Z
# TIBET reflection suite loaded and active in 6469ms
# TIBET starting test run
# 2 suite(s) found.
1..3
#
# tibet test APP --suite='APP'
ok - Has a namespace.
ok - Has an application type.
# pass: 2 total, 2 pass, 0 fail, 0 error, 0 skip, 0 todo, 0 only.
#
# tibet test APP.hello.app.Type --suite='APP.hello:app'
ok - Is a templated tag.
# pass: 1 total, 1 pass, 0 fail, 0 error, 0 skip, 0 todo, 0 only.
#
# PASS: 3 total, 3 pass, 0 fail, 0 error, 0 skip, 0 todo, 0 only.
TIBET's test harness launches your application in a headless fashion, loads any tests it finds, and executes them. This approach can be used effectively to test your rendering process. It can also be used to integration test your main process logic.
NOTE: Unit testing the main process is also possible, however it requires a little more effort. Stay tuned for more on this topic.
See the tibet test manpage and our TIBET Testing documentation for details on how to fully test your TIBET applications.
Main & Renderer
Adding Logic To The Main Process
TIBET+Electron projects use an electron.js
file which loads your TIBET application along with optional plugins.
You can add logic to the Electron main process via this plugin mechanism - a dynamically configurable set of modules that TIBET loads into Electron when the application starts.
An excellent example can be found in the preload.js
module supplied with every
TIBET Electron project in the plugins
directory.
Let's modify a copy of that module to see how the process works.
cd
into theplugins
directory and copypreload.js
totester.js
.Edit
tester.js
and empty the body of the main function from theRequires
block to the end of the function.Insert the following code:
logger.warn('This is only a warning');
Edit the
tibet.json
file and addtester
to the Array of entries underelectron.plugins.core
.Start your application:
tibet start
Describing the details of functionality available to you in the 'main process' (aka the "server") is best left to the Electron Documentation.
Adding Logic To The Renderer Process
To add logic to the renderer process follow the same process you'd use to add functionality to any TIBET client process. In other words, follow the same approach outlined in the TIBET Quickstart and TIBET Essentials guides.
Communicating Between Main and Renderer
TIBET makes it easy to communicate between Electron's two primary processes by leveraging the same communication model it uses for everything else - signaling.
See TIBET Signaling for details on TIBET signaling.
Signaling From Renderer To Main
To communicate with the main process from a TIBET application running in an Electron renderer process use the following outline.
In the renderer process you'll want to use signalMain
on the
TP.electron.ElectronMain
type:
TP.electron.ElectronMain.signalMain('TP.sig.SampleSignal', {"foo":"bar"});
In the Main process you set up a handler for that signal:
ipcMain.handle('TP.sig.SampleSignal',
function(event, payload) {
// event contains native Electron Event object.
// payload contains a POJO: {"foo":"bar"}
});
You can return a Promise from the main process using the following pattern.
In the renderer process:
TP.electron.ElectronMain.signalMain('TP.sig.SampleSignal2', {"foo":"bar"}).then(
function() {...});
In the main process:
ipcMain.handle('TP.sig.SampleSignal',
function(event, payload) {
return await dialog.showMessageBox(...);
});
Signaling From Main To Renderer
Communicating from the Electron main process to a TIBET application running in a renderer process also uses TIBET's signaling mechanism.
In the renderer process subscribe to the signal with TP.electron.ElectronMain
.
This is typically done in the init
instance method TIBET uses to initialize a
new instance:
APP.MyApp.MyType.Inst.defineMethod('init',
function() {
// The TP.sig is optional here.
this.observe(TP.electron.ElectronMain, 'TestSignal');
});
Also in the renderer process, define a handler for the target signal:
APP.MyApp.MyType.Inst.defineHandler('TestSignal',
function(aSignal) {
// The payload contains {"foo":"bar"} that we sent from the main process.
APP.info('Got to the TestSignal handler: ' + aSignal.getPayload());
return this;
});
Last, add code to the main process to send the signal:
mainWindow.webContents.send('TP.sig.TestSignal', {"foo":"bar"});
That's all there is to it.
Logging Activity In The Main Process
When you define a module to be loaded into the main process TIBET will provide
tha function with an options
parameter. A 'logger' object is provided in the
options
which allows you access to a common logging module from TIBET.
module.exports = function(options) {
var logger,
meta;
logger = options.logger;
meta = {
type: 'plugin',
name: 'preload'
};
logger.system('preloading utilities', meta);
...
};
TIBET's default logger object has the following methods for level-based logging:
logger.trace();
logger.debug();
logger.info();
logger.warn();
logger.error();
logger.fatal();
logger.system();
Logging Activity In The Renderer Process
To log data in the renderer process leverage TIBET's lgging functionality available as documented in TIBET Logging.
Framing
Automatic Application Versioning and Updates
For deploying automatically versioned Electron TIBET applications, see Deploying via electron-builder
Automatic Profile Load and Save
TIBET in Electron will automatically save any data that is registered under the
top-level profile
key in TIBET's configuration system. Putting the following
code in the Application object's AppDidStart
signal handler method would cause
this value to be saved:
APP.MyApp.Application.Inst.defineHandler('AppDidStart',
function(aSignal) {
...
TP.sys.setcfg('profile.favoritecolor', 'red');
});
This data is written to the tibet.json
file under the profile
key when a
'proper shutdown' (i.e. via the quit
menu item) is initiated in the TIBET
Electron app.
For now, the only data that is tracked by TIBET itself in the profile system are the user's windows size and position.
Adding A Native Menu To Your Electron App
NOTE: This feature is currently being reworked to be more powerful and is missing from the current TIBET release. This section is representative of the functionality that will be available in a future release.
To function the tag must be rendered in a TIBET application. Usually you'll use
your application's app
tag to render top-level shared components such as
dialogs. You'll find a project's app tag in
{{project}}/src/tags/APP.{{appname}}.app/
. For this example we'll add the
dialog to the APP.{{appname}}.app.xhtml
file.
Rendering The Tag
<electron:menu label="MyMenu">
<electron:menuitem label="DoThing1" on:click="DoFirstThing"/>
<electron:menu label="MySubmenu">
<electron:menuitem label="DoSubmenu1" on:click="DoSubFirstThingFirst"/>
<electron:menuitem label="DoSubmenu2" on:click="DoSubFirstThingSecond"/>
</electron:menu>
<electron:menuitem label="DoThing2" on:click="DoSecondThing"/>
</electron:menu>
Handling the menu triggers
To handle the menu triggers for the menu items, simply implement standard TIBET
handlers on the Application object. You'll find a project's Application subtype
in {{project}}/src/APP.{{appname}}.Application.js
.
APP.MyApp.Application.Inst.defineHandler('DoFirstThing',
function(aSignal) {
// Handle the 'DoFirstThing' menu item here.
return this;
});
Dialogs And Notifications
Showing a Native Dialog
Use an electron:dialog
tag with no type
.
<electron:dialog ... />
The electron:dialog
tag gives you markup-driven access to Electron's native
dialogs without the need for coding. For regular dialog boxes, we do not specify
a type
.
Rendering The Tag
To function the tag must be rendered in a TIBET application. Usually you'll use
your application's app
tag to render top-level shared components such as
dialogs. You'll find a project's app tag in
{{project}}/src/tags/APP.{{appname}}.app/
. For this example we'll add the
dialog to the APP.{{appname}}.app.xhtml
file.
<electron:dialog title="Warn User">
<label>Please don't do that</label>
<button>OK, I won't</button>
<button>No, I will anyway!</button>
</electron:dialog>
Activating The Dialog
To trigger the dialog to open you'll need to "activate" it. You can do this in a
number of ways. The simplest is to target it with a signal. In the example below
we'll add an id
to the dialog so we can target it and we'll include a button
to fire the activation signal we want:
<electron:dialog id="MyWarnDialog" title="Warn User">
<label>Please don't do that</label>
<button>OK, I won't</button>
<button>No, I will anyway!</button>
</electron:dialog>
<button on:click="{signal: UIActivate, origin: 'MyWarnDialog'}">Warn User</button>
Getting the results
To get the results of the dialog, simply use TIBET Data Binding to bind the results to a URN data container:
<electron:dialog id="MyWarnDialog" title="Warn User" bind:out="urn:tibet:warnresponse">
<label>Please don't do that</label>
<button>OK, I won't</button>
<button>No, I will anyway!</button>
</electron:dialog>
<button on:click="{signal: UIActivate, origin: 'MyWarnDialog'}">Warn User</button>
Viewing the results
Then, to see the result that was clicked (the index of the button that was
clicked), bind the URN to a <textarea/>
:
<textarea bind:in="urn:tibet:warnresponse"/>
To view and manipulate the results programmatically, peek inside the URN container:
response = TP.uc('urn:tibet:warnresponse').getContent();
Taking action based on response
To take an action based on the button that the user clicked, simply grab the result and switch on its value:
// Create a Number here via the TP.nc() call
response = TP.nc(TP.uc('urn:tibet:warnresponse').getContent());
switch(response) {
case 0:
// The user clicked 'OK, I won't'
break;
case 1:
// The user clicked 'No, I will anyway!'
break;
}
Showing a Native Error Dialog
Use an electron:dialog
tag with a type
of error
.
<electron:dialog type="error" ... />
The electron:dialog
tag gives you markup-driven access to Electron's native
error dialogs without the need for coding.
Rendering The Tag
To function the tag must be rendered in a TIBET application. Usually you'll use
your application's app
tag to render top-level shared components such as
dialogs. You'll find a project's app tag in
{{project}}/src/tags/APP.{{appname}}.app/
. For this example we'll add the
dialog to the APP.{{appname}}.app.xhtml
file.
<electron:dialog type="error" title="Error Condition">
<label>There was an error condition!</label>
</electron:dialog>
Activating The Dialog
To trigger the dialog to open you'll need to "activate" it. You can do this in a
number of ways. The simplest is to target it with a signal. In the example below
we'll add an id
to the dialog so we can target it and we'll include a button
to fire the activation signal we want:
<electron:dialog type="error" id="MyErrorDialog" title="Error Condition">
<label>There was an error condition!</label>
</electron:dialog>
<button on:click="{signal: UIActivate, origin: 'MyErrorDialog'}">Pop an error
dialog</button>
An error dialog box has no return value, so there are no further actions we can take with it.
Showing a Native Notification
Use an electron:notification
tag.
<electron:notification ... />
The electron:notification
tag gives you markup-driven access to Electron's
native notification system without the need for coding.
Rendering The Tag
To function the tag must be rendered in a TIBET application. Usually you'll use
your application's app
tag to render top-level shared components such as
notifications. You'll find a project's app tag in
{{project}}/src/tags/APP.{{appname}}.app/
. For this example we'll add the
dialog to the APP.{{appname}}.app.xhtml
file.
<electron:notification title="Hi there">
<body>Just wanted to say "Hi there"!</body>
</electron:notification>
Activating The Notification
To trigger the notification to open you'll need to "activate" it. You can do this
in a number of ways. The simplest is to target it with a signal. In the example
below we'll add an id
to the notification so we can target it and we'll include
a button to fire the activation signal we want:
<electron:notification id="MyNotification" title="Hi there">
<body>Just wanted to say "Hi there"!</body>
</electron:notification>
<button on:click="{signal: UIActivate, origin: 'MyNotification'}">Send the user
a notification.</button>
Notifications have no return value, so there are no further actions we can take with them.
Showing the User a Dialog Before Quitting
TODO
File Operations
Opening A File By Using A Native Open Dialog
Use an electron:dialog
tag with a type
of open
.
<electron:dialog type="open" ... />
The electron:dialog
tag gives you markup-driven access to Electron's native
open dialogs without the need for coding.
Rendering The Tag
To function the tag must be rendered in a TIBET application. Usually you'll use
your application's app
tag to render top-level shared components such as
dialogs. You'll find a project's app tag in
{{project}}/src/tags/APP.{{appname}}.app/
. For this example we'll add the
dialog to the APP.{{appname}}.app.xhtml
file.
<electron:dialog type="open"/>
Activating The Dialog
To trigger the dialog to open you'll need to "activate" it. You can do this in a
number of ways. The simplest is to target it with a signal. In the example below
we'll add an id
to the dialog so we can target it and we'll include a button
to fire the activation signal we want:
<electron:dialog type="open" id="MyOpenDialog"/>
<button on:click="{signal: UIActivate, origin: 'MyOpenDialog'}">Open File</button>
Getting the results
To get the results of the open dialog, simply use TIBET Data Binding to bind the results to a URN data container:
<electron:dialog type="open" id="MyOpenDialog" bind:out="urn:tibet:openfilenames"/>
<button on:click="{signal: UIActivate, origin: 'MyOpenDialog'}">Open File</button>
Viewing the results
Then, to see the filename(s) that were selected, bind the URN to a <textarea/>
:
<textarea bind:in="urn:tibet:openfilenames"/>
To view and manipulate the results programmatically, peek inside the URN container:
filenames = TP.uc('urn:tibet:openfilenames').getContent();
filenames.forEach(
function(filename) {
APP.info(filename);
});
Viewing the file content
To view the file content, simply create a URL from the filenames and get their content:
filenames = TP.uc('urn:tibet:openfilenames').getContent();
filenames.forEach(
function(fn) {
// Print the contents to the application log.
APP.info(TP.uc(fn).getContent());
});
Saving A File By Using A Native Save Dialog
Use an electron:dialog
tag with a type
of save
.
<electron:dialog type="save" ... />
The electron:dialog
tag gives you markup-driven access to Electron's native
save dialogs without the need for coding.
Rendering The Tag
To function the tag must be rendered in a TIBET application. Usually you'll use
your application's app
tag to render top-level shared components such as
dialogs. You'll find a project's app tag in
{{project}}/src/tags/APP.{{appname}}.app/
. For this example we'll add the
dialog to the APP.{{appname}}.app.xhtml
file.
<electron:dialog type="save"/>
Activating The Dialog
To trigger the dialog to open you'll need to "activate" it. You can do this in a
number of ways. The simplest is to target it with a signal. In the example below
we'll add an id
to the dialog so we can target it and we'll include a button
to fire the activation signal we want:
<electron:dialog type="save" id="MySaveDialog"/>
<button on:click="{signal: UIActivate, origin: 'MySaveDialog'}">Save File</button>
Getting the results
To get the results of the save dialog, simply use TIBET Data Binding to bind the results to a URN data container:
<electron:dialog type="save" id="MySaveDialog" bind:out="urn:tibet:savefilename"/>
<button on:click="{signal: UIActivate, origin: 'MySaveDialog'}">Save File</button>
Viewing the results
Then, to see the filename that was entered, bind the URN to a <textarea/>
:
<textarea bind:in="urn:tibet:savefilename"/>
To view and manipulate the results programmatically, peek inside the URN container:
filename = TP.uc('urn:tibet:savefilename').getContent();
Saving the file content
To save the file content, simply create a URL from the filename, set its content
and call save
:
filename = TP.uc('urn:tibet:savefilename').getContent();
request = TP.request(TP.hc('refresh', true)));
TP.uc(filename).setContent('This is some content to save').save(request);
code
The TIBET library dna
includes a template specifically designed for creating
Electron-based applications.
The TIBET Lama is currently available in a Technology Preview form and is fully compatible with running in an Electron environment.