A NodeJs module for Electron (Atom Shell, previously) that will help you create, control, manage and connect your application windows very easily.
Most of the applications created using Electron are one-window applications. Why only one? Maybe because we as web developers are used to building only inside the browser. But if you are to build a multi-window Electron application then you may want to have a look at this package module.
- Features
- Installation
- How it works
- Class: WindowManager
- Class: Window
- Final notes and upcoming updates
Creating a "window" in Electron is done using Electron's native "BrowserWindow" module, which is easy to use and very straight forward, until the point when you need to access/control a window from another one, or share values/events between these windows, or use a unified layout/design. That's when this modules come into picture. If you need it I guarantee you will love it :)
- You can easily open, close, move, resize, clone ... etc windows. Of course all of that IS DOABLE through Electron's BrowserWindow, but our module here makes it much easier. It's a kind of wrapper for Electron's module, plus some extra functionalities and tools.
- You can access any window from within any other window. That's because all the work is done on the "Main" process, and because each window is given a unique name, so that you could "access" it from anywhere!
- You can simply share data between the created windows, and even watch for changes in this data. Cool, right ?!
- You can, very easily, connect all of the created windows through events; you can trigger an event in window 1 and listen for it in window 2 and 3 and vice versa. This will help you make your application parts more connected and live. More details
- For an offline/local multi-window application scenario YOU WILL NEED a way to make these windows share one layout/design, or else you will find yourself building a website in the 90s! This module will help you with this problem. Here is how
Simply type the following command in the terminal, inside the application folder
npm install --save electron-window-manager
Then, inside the application main.js (or whatever you've chosen for your application), require the module, like this:
var windowManager = require('electron-window-manager');
Now, this module can be used in both the "Main" and the "Renderer" processes of Electron. In the Main process you can use it to create the application's first/main window, and later, in the Renderer process you can create the other windows of the application, or any other way you like.
On the Main process you can use it like this:
const electron = require('electron');
const app = electron.app;
const windowManager = require('electron-window-manager');
// When the application is ready
app.on('ready', function(){
windowManager.init( ... );
// Open a window
windowManager.open('home', 'Welcome ...', '/home.html');
});
And in the Renderer process (inside the created window), you can use it like this:
<script>
var remote = require('remote');
var windowManager = remote.require('electron-window-manager');
// Create a new window
var win2 = windowManager.createNew('win2', 'Windows #2');
win2.setURL('/win2.html');
win2.onReady( ... );
win2.open();
</script>
Please go ahead and check out the module code and see for yourself how it all works, there's no magic involved, but it's consistent and easy to read. Just have a look at the code and you are good to go.
- This module is basically a wrapper for Electron's
BrowserWindow
module, so it's definitely a good start to check out Electron's documentation for BrowserWindow. - At its core, this module works as a holder for the
BrowserWindow
instances created, each instance is stored by a unique name. So you could later on call any window by name. - Each
BrowserWindow
instance is created inside the "Window" class, which in a way extends and adds more functionalities to it. - When creating a new window you are required to provide a basic SETUP for it, stuff like the width, height, title, url ... etc. The setup options
BrowserWindow
offers are plenty, and this module adds couple more. If some/many of the windows share the same setup, you can simply create a "Setup Template" and pass this template (by name) to the window in the making to apply the setup. - After a window is created you can change its content, resize it, move it, full screen it, ... pretty much anything you may need, even you can execute JS code on it, from another window ... cool ha!
- A development-mode is on by default, until you finish developing the application and set it off. The development mode will make debugging a bit easier; you will be able to reload ANY window just by pressing
Ctrl + R
, also you will be able to toggle the development tools Chrome offers just by pressingCtrl + F12
, for any window and without any pre-configuration. The developer tools can be turned on by default for a window just by passingshowDevTools: true
in the window setup, or even by calling the methodshowDevTools()
on the window object.
The windowManager
class is the interface of the module, through which you can access the created windows, and of course create new ones. Also through it you can access everything the module uses internally, like utilities and other modules used in the process.
This property is where the created Window
instances are stored, each by its name
The basic configuration of the module
This method initiates the module so that you could use it. It takes one optional argument, which is the configuration. You only need to initiate the module once, and that's on the "Main" process.
- config
- appBase (string): The path to the application base, whether it's local or remote. By default the local path to the application directory is used.
- devMode (boolean): Sets whether the development mode is on or off, it's on by default.
- layouts (object): As mentioned, this module will help you use layout(s) for the content of your windows. This option is where you can pass a list of the layout file(s), each under a specific name. Here's an example:
This of course will be effective only when the app is local, or at least the target file is stored locally. More details on the "Layouts" feature
windowManager.init({ 'layouts': { 'default': '/layouts/default.html', // The "/" at the start will be replaced with the 'appBase' value 'secondary': '/layouts/secondary.html' }, 'defaultLayout': 'secondary' });
- defaultLayout (string): The name of the default layout.
- defaultSetupTemplate (string): The name of the setup template that you want as the default for all of the application windows. "Setup Templates" is a way of presetting shared setup properties for the application windows. More details
- defaultWindowTitle The default title for the windows; will be used if a window title wasn't specified, and if the setup template didn't provide a title either! Here you can simply use the name of the application.
- windowsTitlePrefix (string): Each window can have its own title, this value will allow you to set a prefix for this title, for all the windows, ... maybe the application name!
- onLoadFailure (function): Here you can set what happens whenever the target URL of a window isn't accessible, meaning that the loading has failed, for whatever reason. By default a small message will be be displayed instead of the page (it basically loads the loadFailure.html file from the module directory). Here's an example:
windowManager.init({ 'onLoadFailure': function(window){ window.loadURL('/404.html'); // -- or --- window.write('<h3> Cannot load the requested page! </h3>'); } });
This method sets the default setup for the application windows, it basically creates a new setup template with the name "default", and marks it as the default. Example:
windowManager.setDefaultSetup({'width': 600, 'height': 450, 'position': 'right'});
When creating a new window this setup will be used automatically, you can override this by passing FALSE as the setup template name.
This method, as the name suggests, creates a new window, it will create and return a new instance of the class Window. All of the listed arguments up there are optional by the way, even the url; you can omit it now and set it later.
- name (string): The name of the new window, if omitted a serialized name will be used instead, like "window_1" ~> "window_2" ~> ...
- title (string): The window title. If omitted the setup template 'title' property will be used, and if this property wasn't provided the
config.defaultWindowTitle
will be used instead. If all of that isn't set the document title will be used. - url (string): The targeted URL for the window, it could be a local file (file:// ... .html), or a URL (http:// ...). If the passed value here starts with "/" the value will be preceded by the "appBase" (which is the path to the application base, set in the config), also you can use
{appBase}
inside the passed value and it will be replaced with the application base path. - setupTemplate (string) The name of setup template you want applied to this new window. "Setup Templates" is a way of sharing preset setup properties with more than one window.
- setup (object|string) [optional] The new window setup. The full options list is available here.This module offers couple more options to use in the mix, but we will get to that later. BTW, as a shortcut you can pass the new window dimensions like this "300x200", where 300 is the width and 200 is the height!
- showDevTools (boolean) Whether to show the developer tools offered by Chrome or not, for debugging. False by default.
Here's an example:
var homeWindow = windowManager.createNew('home', 'Welcome ...', '/pages/home.html', false, {
'width': 600,
'height': 450,
'position': 'topLeft',
'layout': 'simple',
'showDevTools': true,
'resizable': true
});
homeWindow.registerShortcut('CmdOrCtrl+N', function(window){
window.minimize();
windowManager.open( ... );
}).open();
As mentioned, beside the the setup options BrowserWindow
offers we offer couple more:
- layout (string) The name of the layout you want the window's content to be displayed inside.
- position (string|array) This setup option sets the position of the window on the screen, you can pass the x & y coordinates as an array (ex: [300, 200]), or simply pass the position name, the available positions are: top, right, bottom, left, topRight, topLeft, bottomRight, bottomLeft. The default position by the way is "center".
var win = windowManager.createWindow(false, false, false, false, {'position': 'bottomRight'});
- onLoadFailure (function) This callback will be triggered whenever the URL loading fails.
var win = windowManager.open(false, false, false, false, { 'onLoadFailure': function(window){ window.close(); } })
If you don't set this a default callback will handle it, by showing a global "Not available" message page, this global callback can be set in windowManager.init()
- showDevTools (boolean) Whether to show the dev tools or not.
This is the same as the createNew
method, except that it opens the window directly. Returns a Window Object on successfully opening a window.
Using this method you can create more than one window instance, with the setup information retrieved from a JSON file. You will use it like this:
// windows.json
{
"home": { "title": "Home", "url": "http:// ...", "setup": { ... } },
"about": { "title": "About", ... }
}
// Import the window list
windowManager.importList('windows.json');
// Open a window, by name
windowManager.get('home').open();
Creates a clone of the specified window and returns the Window
class instance
Returns the window
instance of the specified window
var win = windowManager.get('home');
win.resize(300, 200).restore();
Returns the window
instance of the specified window by BrowserWindow instance's id attribute
var win = windowManager.getById(1);
win.close();
Returns the Window
instance of the currently-under-focus window
windowManager.getCurrent().close();
Closes a window, by its name
Closes the current window
Destroys the window instance, by its name
Closes all the windows created by this module
Closes all the window, except for one, the one you pass its name
Maximizes a window. If name is specified it will target the named window. If left out it will target the currently focused window. If the window is already maximized it will restore.
// Maximize focused window. Works well for window button functionality.
windowManager.maximize();
// Target window named 'help'
windowManager.maximize('help');
Minimizes a window. If name is specified it will target the named window. If left out it will target the currently focused window.
// Minimize focused window. Works well for window button functionality.
windowManager.minimize();
// Target window named 'help'
windowManager.minimize('help');
Restores a minimized window, by name
This method simply takes two values, the first is the one that goes when the development mode is on and the other is when it's off, and according to whether it's on or off, the corresponding value will be returned
var api = windowManager.devModeChoice({'key': ... }, {'key': ...});
When creating a new window you will need to provide a basic setup; things like the width, height and the window URL (the targeted content). The available setup options are plenty, and in most cases you will find yourself repeating it with each window you create. The "Setup Templates" feature will help you make presets of the setup properties you use and name them, and later when creating a new window you will just pass the template name and the associated setup will be applied.
windowManager.templates.set('small', {
'width': 600,
'height': 350,
'resizable': true,
'layout': 'classy',
'showDevTools': true,
'title': 'App name, for small windows!', // Yeah, even the window title!
'onLoadFailure': function(){ ... },
'menu': { // Sets the window menu. Set to null for a window without a menu!
label: 'File',
submenu: [
{
label: 'New',
accelerator: 'CmdOrCtrl+N',
role: 'new',
click: function(){ ... }
},
{
label: 'Open',
accelerator: 'CmdOrCtrl+O',
role: 'open',
click: function(){ ... }
}
]
}
});
windowManager.open(false, false, 'welcom.html', 'small');
windowManager.open(false, false, 'byebye.html', 'small');
You can set a default template for all the windows to inherit
windowManager.init({
'defaultSetupTemplate': 'small'
});
windowManager.templates.set('small', { ... });
You can override this option for a specific window by passing FALSE as the setupTemplate name.
windowManager.open('home', 'Welcome', '/pages/welcome.html', FALSE);
Pass null to explicitly define the template in the next parameter!
windowManager.open('home', 'Welcome', '/pages/welcome.html', null, { ... });
Use this method to create a new template, you provide a name and the preferred setup, and later you can use that name when you are creating a new window, to apply the specified setup.
windowManager.templates.set('big', {
'width': 1000,
'height': 600
});
Fetches a setup template by name
Use this method to modify the setup of a specific template, to override one or more of its properties
windowManager.templates.modify('big', {'height': 650});
Returns the value of a specific property of the given template
windowManager.templates.getProperty('big', 'width');
A nice feature this module offer is "Layouts". Through this feature you can set a layout/theme/design for your application. If you are working on a multi-window app this feature will be very handy. This feature doesn't offer much right now, but I will put more focus on in the next releases. Here's how it works: you create a layout file, with all the assets and code you want, when you create a new window the content (HTML) of this window will be embeded/included within the layout code. Thus you won't duplicate your code with each window; you will just include the window content withing a ready layout.
// layout.html
<!doctype html>
<html lang="en">
<head>
....
</head>
<body>
{{content}} <!--This will be replaced with the window content-->
<script src="{{appBase}}scripts/bootstrap.js"></script>
<!--{{appBase}} will be replaced with the application base path-->
</body>
</html>
// welcome.html (the window content)
<h3>Welcome ...</h3>
Of course you can create more than one layout, and when creating a new window you chose which layout you want the window content included in. And of course you can set a default layout for all of the application windows.
var win = windowManager.createNew('home', 'Welcome ... ', '/pages/welcome.html', false, {'layout': 'simple'});
// or
win.useLayout('simple');
To set a default layout for the whole application you simple pass its name in the initiation config
windowManager.init({
'defaultLayout': 'simple'
})
To override this option for a specific window you will need to pass FALSE for the property "layout" when creating the window
Adds a new layout, you need to provide the path to the layout file, and a name that represents this layout, so that you could use it when creating a new window.
windowManager.layouts.add('default', '/layouts/default.html'); // The "/" at the beginning = {appBase}
Fetches a layout by name
This class offers a simple way of sharing data between windows, you can simply set a value on window #1 and get it on window #2
Is where the data itself is stored
Stores a value by a key name
windowManager.sharedData.set('user', {'name': ' ... ', 'email': ' ... '});
Fetches a value, by key name
windowManager.sharedData.fetch('user');
You can use this method to watch for changes in the saved data
- prop Is the key you want to watch
- callback Is the callback that will be triggered whenever the value of this property gets changed
windowManager.sharedData.watch('user', function(prop, action, newValue, oldValue){
console.log('The property: ', prop, ' was:', action, ' to: ', newValue, ' from: ', oldValue);
});
This feature is available using WatchJS, please visit the module docs for further details. Also, you can access WatchJS itself in case you needed the whole API like this:
windowManager.sharedData.watcher; // The WatchJS module
var watcher = windowManager.sharedData.watcher;
watcher.unwatch( ... );
Stops watching for changes in the saved data.
This is a simple feature to help you make your app more alive and connected windows-wise. You will be able to emit and listen to events inside the created windows.
Here's an example:
// On window "home"
let handler = windowManager.bridge.on('new_chat_message', function(event){
...
});
// On window "chats"
windowManager.bridge.emit('new_chat_message', {'message': ' ... '});
// On window "home"
windowManager.bridge.removeListener('new_chat_message', handler);
This method emits an event to whatever page listening for it
- event The name of the event that will be emitted
- data [optional] Any extra data you need to broadcast along with the event
- target [optional] The name of the targeted. In case you are targeting a specific window.
This method adds a listener for a specific event
, and whenever this event is triggered the callback
will be called, and return the handler added into the event
listeners array.
- event The name of the event the window will be watching for
- callback The callback to call when the event is emitted. This call back will be passed 1 parameters with the following properties:
- event The event name
- target The name of the targeted window, if specified
- emittedBy The name of the window that emitted the event
This feature is basically a wrapper for NodeJs native EventEmitter class, which is used heavily almost every where inside Electron itself. Check it the docs for extra knowledge about the subject. You also can access the EventEmitter used by this module simply by calling windowManager.eventEmitter
windowManager.eventEmitter.addListener( ... );
This method is the alias of windowManager.brider.on
This method remove the listener returned by windowManager.bridge.on
or windowManager.bridge.addListener
- event The event name
- handler the handler returned by
windowManager.bridge.on
orwindowManager.bridge.addListener
This feature is basically a wrapper for NodeJs native EventEmitter class, Check it the docs for extra knowledge about the subject.
windowManager.eventEmitter.addListener( ... );
This object holds a couple of utility method, for module internal use, and for you if you need it. I will likely add more methods to this class later.
Returns the path to the application directory.
It readies the given URL for use with in the module, basically it replaces the /
prefix or the {appBase}
string with the path to the application directory.
This method takes a position name and returns the corresponding x & y coordinates, the accepted values are: "top", "bottom", "right", "left", "topRight", "topLeft", "bottomRight", "bottomLeft" and "center".
- setup (object) The window setup object, inside which the "position", "width" and "height" properties must be present.
You probably wont be needing this method, but it's here just in case.
The
Window
class is basically the whole thing, windowManager is merely an access point for its instances. Whenever you usewindowManager.createNew( ... )
orwindowManager.open( ... )
you are creating a new instance ofWindow
.
var window = new Window( name, title, url, setupTemplate, setup, showDevTools );
But don't try to use the above code, Window
isn't available in your app scope, use windowManager.createNew/open
instead, and as you can see the arguments are the same in the 3 cases, check out windowManager.createNew for more info on the arguments.
Stores the window name.
Stores the window setup object.
Stores the BrowserWindow
instance created, such that all electron-native methods can be used.
See electron Window object docs
win.object.setFullScreen(false)
windowManager.get('win2').object.center()
Updates the window setup. You can either provide a property-value pair or pass an object to override the current setup.
win.set('width', 300);
win.set({'width': 300, 'height': 250});
Creates the browserwindow instance.
var win1 = windowManager.createNew(false, false, 'win1.html');
win1.create();
win1.object.on(...);
win1.open();
// or
var win2 = windowManager.createNew();
win2.create('win2.html');
win2.open();
Opens/shows the created window.
var win1 = windowManager.createNew(false, false, 'win1.html');
win1.open();
// or
var win2 = windowManager.createNew();
win2.open('win2.html');
Makes the window under focus.
Sets the layout to use in the window, by name.
var window = window.createNew( ... );
window.useLayout('classy');
window.open();
Sets the target URL for the window. To open a URL after the window is open use loadURL()
.
Sets the setup template to use, by name.
var window = window.createNew( ... );
window.applySetupTemplate('big');
window.open();
Sets the content of the opened window from the url. Same as with WebContents you can use both local and remote targets.
var win = windowManager.createNew();
win.loadURL('file://' + __dirname + 'index.html');
// or
win.loadURL('http://google.com');
The same way you would open a url using any browser. Now, to make things easier you can set the base path to the application in the config (while initiating the module) and use this path in any URL-value you path to the module, {appBase}
, or by simply starting the value with /
.
win.loadURL('/pages/index.html');
// or
win.loadURL('{appBase}pages/index.html');
It simply sets the HTML code of the window, instead of loading a url.
win.html('<h3> Electron is AWESOME </h3>');
It simply takes the page down! It will trigger the onLoadFailure
callback, which by default will force display a "Not available" message page. This method is called whenever the target url of the window isn't available, instead of displaying a blank page.
Returns BrowserWindow
's webContents object for the window.
win.content().on('did-fail-load', function(){ ... });
win.content().downloadURL( ... )
win.content().reload()
win.content().print()
Reloads the URL of the window, if TRUE is passed the page will be reloaded with the cache ignored.
win.reload(); // With cache
win.reload(true); // Without cache
Returns the URL open inside the window.
Registers a callback that triggers when the page is ready. If you pass TRUE for the withTheDomReady
argument the callback will trigger only when the DOM is ready, and not before.
win.onReady(true, function(window){
window.resize(600);
});
Executes JavaScript code on the window content.
win.execute(' alert(" Hi! ") ');
Goes back to the previous page, Electron is a browser after all!
Closes the window.
Maximizes the window. Restores if already maximized.
Minimizes the window.
Restores the window back in focus.
Takes the window to fullscreen.
Toggles the developer tools. By default, and when the devMode
is on you can open the developer tools by pressing Ctrl + F12
on any window.
- detached (boolean) [optioanl] Whether to open the devTools in a separate window or not
Registers a keyboard shortcut on the window
win.registerShortcut('CmdOrCtrl+N', function(){
windowManager.open( ... );
});
This feature is available thanks to the electron-localshortcut module. Here's more details on the shortcuts codes. The module itself can be access through windowManager.shortcuts
in case you wanted to use more of it, to globally-register a new shortcut or something.
windowManager.shortcuts.unregisterAll();
Moves the window to a specific x (and/or) y coordinates. You can also provide a position "name" and it will be resolved to the correct position according to the screen size and the window dimensions. The available position names are top, right, bottom, left + topRight, topLeft, bottomRight, bottomLeft
win.move(300, 200);
win.move('topLeft');
Resizes the window to a specific width and/or height
win.resize(1000); // Only set the width
win.resize(800, 400);
- The development of this module is continuous via community contributions.
- You may find this module a bit redundant, but if you use Electron for complex multi-window projects you will probably need it.
- PLEASE if you have any comments or suggestions of any kind open a new issue!.
- The next releases will ...
- put more focus on the Layouts feature, probably using Handlebars to offer templating capabilities.
- offer more debugging tools and features.
- improve the documentation and the code examples.
And yeah, THANKS GITHUB FOR ELECTRON, IT'S A DREAM COME TRUE.