Install and manage node.js via NVM

NVM is known as Node Version Manager and provides an option for easy installation of Node.js.
You can install a specific Node.js version or multiple Node.js versions on the same system using nvm and use required version for application.

A bash script is available to install nvm on your system.

curl https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash

Reload system environment using this command. It will set the required environment variables to use nvm on the system.

source ~/.bashrc      ## for CentOS/RHEL systems 

Use

nvm ls-remote 

to list available node.js versions.

Now install the node.js version you need to use for running node.js application.

nvm install v8.10.0

nvm lets you install multiple node.js versions. Simply repat the above nvm install with the needed node.js version.

As you have installed multiple node.js versions, You can select the specific version of node.js as default version used by system and load in the environment.

nvm use v8.10.0

If you have multiple node.js applications on your system and want to run each with a specific version of node.js. NVM provides you an option to use node.js version for running any application.

nvm run v8.10.0 app.js
nvm list

will provide a list of installed versions of node.js on your system. To remove any version installed on your system use

nvm remove v7.7.2

Plain simple guide to setup Domino V10 on Red Hat Enterprise Linux Server 7.4

Red Hat Enterprise Linux Server (RHEL) is one of the supported platforms for Domino V10. In this post I will show how to setup a Domino V10 server on RHEL 7.4 in a just a few steps.

Did you know, by joining the free Red Hat Developers program, you can get a no-cost Red Hat Enterprise Linux subscription for developers?

This subscription includes: Red Hat Enterprise Linux Server (all currently supported releases), additional development tools, and numerous add-ons such as resilient storage, scalable file systems, and high-performance networking. The no-cost subscription also includes access to the Red Hat Customer Portal for software updates and thousands of knowledgebase articles.

After you have successfully registered as a Red Hat Enterprise Linux® Developer, you can download the iso image to setup a new RHEL 7.4 installation.
I will not go into the installation details here. I just recommend to select “minimal install”. Remember, we are installing a server. So why would we need all the overhead of a GUI.

When the installation has finished successfully you’ll have a running RHEL server 7.4. Make sure that the server is connected to the internet because we need to install a couple of packages prior to installing Domono V10.

To get updates for your RHEL 7.4 server, you must register the machine with Red Hat first using the credentials that have been used when registering for the no-cost Red Hat Enterprise Linux® Developer subscription.

subscription-manager  register --username my.mailaddress@mydomain.tld --password PaszVV0rd --auto-attach --force

Registering to: subscription.rhsm.redhat.com:443/subscription
The system has been registered with ID: 7e798124-d48a-4b55-826b-b9c77a398648 

Installed Product Current Status:
Product Name: Red Hat Enterprise Linux Server
Status:       Subscribed

The Domino installer has some dependencies that are not installed during the initial setup of our RHEL 7.4 server.

yum install perl
yum install bc-1.06.95-13.el7.x86_64

To access your server via the TCP/IP, you must open some ports first. Install the firewalld package

yum install firewalld

and then open the needed ports

firewall-cmd --zone=public --add-port=22/tcp --permanent  
firewall-cmd --zone=public --add-port=8585/tcp --permanent
firewall-cmd --zone=public --add-port=1352/tcp --permanent

After that reload your configuration.

firewall-cmd --reload

Next we can install Domino V10. As user root upload DOMINO_SERVER_V10.0_64_BIT_LINUX_.tar to i.e. /tmp and unpack the archive with

cd /tmp
tar xvf DOMINO_SERVER_V10.0_64_BIT_LINUX_.tar

cd linux64/domino
./install

The installation wizard will start

          Initializing Wizard........
          Launching InstallShield Wizard........


-------------------------------------------------------------------------------
Welcome to the InstallShield Wizard for IBM Domino

The InstallShield Wizard will install IBM Domino on your computer.
To continue, choose Next.


IBM Domino

IBM

https://www.ibm.com


Press 1 for Next, 3 to Cancel or 5 to Redisplay [1] 

Follow the instructions on the screen. Depending on your hardware the installation process will take about 5-10 minutes.

After you install the program files for an IBM® Lotus® Domino® server on a system, you can use either a Microsoft® Windows® client system or another Domino server to run the server setup program remotely. Running the server setup program from a Windows client is easier if the client has Domino Administrator installed — to run the program from a client without Domino Administrator, you need the Java™ runtime environment plus some files from the program directory of an installed Domino server.

Follow the instructions in this technote to configure your Domino V10 server.

Finally, we want to start the Domino V10 server when the OS starts. this can easily be done using the start script by Daniel Nashed. You can get a free copy of the script here.

There will be a newer version of the script soon. Watch Daniel’s blog for updates.

Installation is straight forward; Just follow the instructions in the “Quick Configuration” section of the rc_domino_readme.txt file that comes with the start script.

[root@serv02 ~]# systemctl status domino.service
● domino.service - IBM Domino Server (notes)
   Loaded: loaded (/etc/systemd/system/domino.service; enabled; vendor preset: disabled)
   Active: active (running) since Tue 2018-11-27 15:48:55 CET; 14h ago
  Process: 1120 ExecStart=/opt/ibm/domino/rc_domino_script start (code=exited, status=0/SUCCESS)
 Main PID: 1349 (rc_domino_scrip)
    Tasks: 157 (limit: 8000)
   CGroup: /system.slice/domino.service
           ├─1349 /bin/sh /opt/ibm/domino/rc_domino_script start
           ├─1445 /opt/ibm/domino/notes/latest/linux/server
           ├─1556 /opt/ibm/domino/notes/latest/linux/logasio NOTESLOGGER reserved
           ├─1641 /opt/ibm/domino/notes/latest/linux/event
           ├─2523 /opt/ibm/domino/notes/latest/linux/update
           ├─2524 /opt/ibm/domino/notes/latest/linux/replica
           ├─2525 /opt/ibm/domino/notes/latest/linux/router
           ├─2526 /opt/ibm/domino/notes/latest/linux/amgr -s
           ├─2527 /opt/ibm/domino/notes/latest/linux/adminp
           ├─2528 /opt/ibm/domino/notes/latest/linux/smtp
           ├─2529 /opt/ibm/domino/notes/latest/linux/daosmgr
           ├─2532 /opt/ibm/domino/notes/latest/linux/dbmt -compactThreads 1 -updallThreads 1 -range 4:00AM 6:00AM -compactNdays 5 -force 1 -nounread
           ├─2598 /opt/ibm/domino/notes/latest/linux/amgr -e 1
           ├─2925 /opt/ibm/domino/notes/latest/linux/cldbdir
           ├─3072 /opt/ibm/domino/notes/latest/linux/clrepl
           ├─3265 /opt/ibm/domino/notes/latest/linux/autorepair
           └─4268 /opt/ibm/domino/notes/latest/linux/amgr -e 2

Nov 27 15:48:54 serv02.fritz.box systemd[1]: Starting IBM Domino Server (notes)...
Nov 27 15:48:55 serv02.fritz.box rc_domino_script[1120]: Archived log file to '/local/notesdata/notes_181127_154855.log'
Nov 27 15:48:55 serv02.fritz.box rc_domino_script[1120]: Starting Domino for xLinux (notes)
Nov 27 15:48:55 serv02.fritz.box rc_domino_script[1120]: Warning: Cannot write to [FTBasePath] '/local/ft' (/local/ft)
Nov 27 15:48:55 serv02.fritz.box rc_domino_script[1120]: done PID is 1349
Nov 27 15:48:55 serv02.fritz.box systemd[1]: Started IBM Domino Server (notes).
[root@serv02 ~]# 

If everything is set correct, you will now have a Domino V10 server running on RHEL 7.4


Support

Ist schon lustig, wenn man mit dem Support in einer remote session sitzt, und der Supporter ganz genau wissen will, welche Client Version man verwendet, obwohl es sich um ein Server Problem handelt.

Aber auch die Server Version ist interessant. Und selbstverständlich bekommt man nur Support, wenn man alles auf supporteten Betriebssystemen installiert hat.

Der Supporter braucht das nicht.

Er verwendet Windows 10 als Betriebssystem für den Domino 10. Und fragt per Ticketsystem zurück, warum er die übermittelten .nsf in seinem Client nicht öffnen kann.  Nun ja, ODS53 geht nun mal nur mit V10 …


[V10] – nserver -c command not working properly when used with te amgr run

Andy Brunner informed me a couple of days ago that using the “te amgr run” in a server program document command does no longer work after upgrading the server to Domino V10.

You will get an error message on the console similar to this one

Tell AMGr Run ‘LE4D.nsf’ ‘letsencrypt’
15.10.2018 07:24:11 AMgr: Start executing agent ‘ ‘ in ‘LE4D.nsf’
15.10.2018 07:24:11 AMgr: Error attempting to load agent ‘ ‘ in ‘LE4D.nsf’: Entry not found in index

I have created a case with IBM support. The case number is TS001489373 and the issue is tracked unter SPR# GFALB5NKTV.

The case has already been escalated to development and they are investigating the issue and I’m awaiting their feedback.

In the meantime, you can use the following workaround ( Thanks to Andy Brunner )

Create a text file on the server. The file can contain one or more lines where the syntax is the same as when you start an agent from the Domino console.

Tell AMgr Run “LE4D.nsf” ‘letsencrypt’

Next change your program document to use the text file as input


Accessing the IDVAULT with LotusScript

NotesIdVault and NotesUserId are new classes as of IBM Notes 9.0.1 FP9. These classes provide a representation of the secure storage facility for UserIDs that may be configured for Domino by policy.

The classes are also available in Java and JavaScript.

This sound promising. As a developer you are now able to access the IDVAULT programmatically, get and put UserIds, resetPassword, mark an ID as active or inactive and force a resync if an ID becomes corrupt for some reason.

Well, this is what I thought when I first read about the new classes.

Let us take a closer look at some methods.

When you look at the list of methods in the NotesIDVault class, you can easily see that a couple of methods are not available.

  • ExtractIdFile
  • MarkIdActive
  • MarkIdInactive

I have tested other methods, and all of them do what they are supposed to do. Except GetUserId.

GetUserId returns an NotesUserID. This might not be too bad, because this is exactly what we want to get when calling this method. But, if you expect to be able to download the id file to the local file system, you’ll be disappointed.

The documentation says

This object is primarily used to obtain the names of the private encryption keys which are available for use within the UserID object.

Frankly said, completely useless.

You could get the attached user.id with traditional Lotsuscript ( locate the document, detach the embedded object ), but I would expect this as a method in either the NotesIDVault or NotesUserId class.  Once again, half backed stuff. I hope that the guys and gals at HCL are more open for enhancement requests than IBM was.

UPDATE: There IS actually a method in the NotesIDVault class to detach the user.id from the vault document to the local file system!

At the bottom of this article you find an updated version of my IDVAULT class the extends the NotesIdVault class and lets you extract an id file.

A couple of years ago, I wrote my own IDVault class in Lotusscript. It uses LS2CAPI functions that let you do more than the provided LS classes.

The class provides methods to

  • IdFileGet()
  • IdFilePut()
  • IdFileExtract()
  • ResetPassword()
  • MarkActive()
  • MarkInactive()

IdFileGet() lets you download the user.id file from the vault document to the local file system. Much better than the method from the LS class, isn’t it.

IdFileExtract() lets you get the user.id from the vault document to the local file system WITHOUT knowing the password. It uses the ResetPassword method to apply a new password to the user.id and some “magic” to llet the original vault document remain unchanged.

The magic behind this is that you must make a copy of the vault document prior to resetting the password. The copy MUST be stored in another  database. After you have downloaded the ID with IDFileGet(), you must replace the vault document with the copy.

Here is a sample agent ( the agent signer must be a password reset authority in the IDVault )

%REM
	Agent test
	Created Sep 18, 2015 by Ulrich Krause/singultus
%END REM

Option Declare
Use "IDVault"

Sub Initialize
	Dim idv_db As NotesIdVaultDb
	Dim bck_db As NotesIdVaultDb
	Dim vaultServer As String
	Dim vaultDb As String
	Dim vaultDb_backup As String
	
	vaultServer = "develop1/gotham"
	vaultDb = "IBM_ID_VAULT\batcave.nsf"
	vaultDb_backup =  "IBM_ID_VAULT\batcave_bck.nsf"
	
	Dim idv_doc As NotesIdVaultDocument

	Set idv_db = New NotesIdVaultDb(vaultServer, vaultDb)
	
	If ( idv_db.IsConnected() ) then
		Set idv_doc = New NotesIdVaultDocument(idv_db.db,nothing)
		Set bck_db = New NotesIdVaultDb(vaultServer, vaultDb_backup)
		
		If ( bck_db.IsConnected() ) Then
			Set idv_doc = New NotesIdVaultDocument(idv_db.db, bck_db.db)
		Else
			Set idv_doc = New NotesIdVaultDocument(idv_db.db, Nothing)
		End If
		
		idv_doc.ServerName = vaultServer
		idv_doc.UserName = "cn=Harvey Dent/o=gotham"
		idv_doc.Password = "hdent001"
		idv_doc.IdFilePathName = "C:\Notes\Data\ids\people\backup\hdent_with_new_pwd.id"
		'Call idv_doc.ResetPassword()
		'Call idv_doc.IdFileGet()
		'Call idv_doc.IdFilePut()
		Call idv_doc.IdFileExtract()
		'Call idv_doc.markInactive()
		'Call idv_doc.markActive()
		MsgBox idv_doc.LastErrorString

	End if
	
End Sub

Here is the updated version. This version is also platform independend


URLs in Notes client do not open

A couple of weeks ago, I noticed that hyperlinks in a Notes mail do no longer open. I did not pay much attention to it, because I am reading my mails mostly in the browser or on a smart device.

Today, I did some Google research. I came across an old technote that best describes the behaviour.

LO56433: CANNOT CLICK URLS IN NOTES 8.5.2 WITH OS BROWSER

What I found is that HKEY_CLASSES_ROOT\https\shell\open\command\ pointed to a non existing browser and HKCU\Software\Classes\http\shell\open\command\ was completely missing in the system registry.

Setting the entries to the DEFAULT iexplore.exe has fixed the issue.


Electron – reload all BrowserWindows on code change

electron-reload is the simplest way to load contents of all active BrowserWindows within electron when the source files are changed.

To add electron-reload to your existing project, type

 npm i electron-reload --save-dev 

Initialize this module with desired glob or file path to watch and let it refresh electron browser windows as targets are changed

const electron = require('electron');
const path = require('path');
const url = require('url');

const { app, BrowserWindow } = electron;

require('electron-reload')(__dirname);

let win;

app.on('ready', createWindow);

Here is a small video showing electron-reload in action.

You can download the source code for this article here.


midpoints LE4D 2.0 – some hints

On March, 28th, we released LE4D v2.0. If you are running LE4D v1.x, you must update to v2.0. Certificate renewal will no longer work with v1.x because of some changes on the Let’s Encrypt API endpoint.

Here are some additional hints.

Settings documents are disabled after design change

In v2.0, we added a new feature to toggle the status of setings documents. All new settings are disabled by default. And also, after the design replace, you have to enable them prior to run the agent.

LE4DDisabled

Error: No trusted certificates found

You might see the following error message on the Domino console:

29.03.2018 08:21:39   Agent Manager: Agent  error: Caused by:
29.03.2018 08:21:39   Agent Manager: Agent  error: com.ibm.jsse2.util.h: No trusted certificate found
29.03.2018 08:21:39   Agent Manager: Agent  error:         at com.ibm.jsse2.util.g.a(g.java:21)

This happens most likely after you have applied a FP or HF. In all cases we have seen, the cacerts is replaced with the default cacerts during FP/ HF install.

To fix this problem, you have to import the needed certificates again.

The certificates can be found here https://letsencrypt.org/certificates/

An “HowTo” about importing the certs can be found here http://abdata.ch/add-a-root-certificate-to-ibm-domino-jvm-keystore/

Error: Order’s status (“invalid”) was not pending

You might see the following error message on the Domino console:

28/03/2018 22:51:58   Agent Manager: Agent  error:         at lotus.domino.NotesThread.run(Unknown Source)
28/03/2018 22:51:58   Agent Manager: Agent printing: [ERROR] – Order’s status (“invalid”) was not pending
28/03/2018 22:51:58   Agent Manager: Agent printing: LE4D  – finished!

Due to the change in the underlying ACME protocol, Let’s Encrypt needs to re-validate the HTTP challenge on certificate renewal. Do do this, the challenge token must be accessible on the Domino server on port 80.

If you only have port 443 open, then the challenge will fail and you will see the error message.

Just for clarification. Port 80 is only needed for the first time challenge validation after the upgrade to LE4D v2.0. It is also needed, when you change the configuration and add a new host to the existing list of hostnames.

After the challenge has been validated, you can close port 80 again. It is not needed for certificate renewal.

 


[VMWARE ESXi] – [Errno 28] No space left on device #ibmchampion

I tried to update my ESXi 6.5 server yesterday and ran into this error “[Errno 28] No space left on device”.

Before I tell you, how I solved the issue, I would like to show, how I do my updates.

You can download update files from VMWare, but there is an easier way to keep your ESXi server up to date.

On an ESXi host, you need to activate remote command execution explicitly. This protects your server and prevents that you can log in to the host by using a remote shell. You can enable remote command execution from the direct console or from the vSphere Web Client.

Now you can open a shell using your preferred tool.

The “VMware Front Experience ” blog has a list of recent patches and updates available .

Click on the “image profile” link for the patch, you want apply.

A popup will open.

Execute the commands from the shell. In general, this is a no no-brainer. You apply the patches, and after that, you have to restart the ESXi server. Done!

But today, for some reason, I got this error message “[Errno 28] No space left on device”.

There are several “solutions” that all tell you to delete obsolete log files and the like. But none of this solutions really solve the problem. If you have not changed the log settings, then ESXi rotates logs.

Therefore, it is unlikely that you use all of the available disk space for logging.

Another solution suggested to change the file system. This can be dangerous. If something goes wrong, you will end up in a useless piece of hardware.

But I finally found the root cause. The system swap file has become very large, leaving no space to unpack the patch files.

What can you do? You can move the swap file to another datastore.

By default, the “Datastore” is set to none. This means that the system partition is being used. Change the setting and run the update command again. You can now update your server without any problems.

 


Plain simple introduction to mybatis #ibmchampion

MyBatis is a first class persistence framework with support for custom SQL, stored procedures and advanced mappings. MyBatis eliminates almost all of the JDBC code and manual setting of parameters and retrieval of results.

MyBatis can use simple XML or Annotations for configuration and map primitives, Map interfaces and Java POJOs (Plain Old Java Objects) to database records.

MyBatis makes it easier to build better database-oriented applications more quickly and with less code.

SQL server

I am using Microsoft SQL Server 2017 in my example, but you are free to use any other SQL server.

I have Microsoft SQL Server 2017 installed on RHEL 7. The installation and configuration was a matter of just a couple of minutes. Here is what I did.
Configure the repository

sudo curl -o /etc/yum.repos.d/mssql-server.repo https://packages.microsoft.com/config/rhel/7/mssql-server-2017.repo

Install the server

sudo yum install -y mssql-server

Configure the server

sudo /opt/mssql/bin/mssql-conf setup

If you want to use a different port

sudo /opt/mssql/bin/mssql-conf set network.tcpport 1433

Enable and start the service

systemctl enable mssql-server
systemctl status mssql-server

Configure your firewall and reload the settings

sudo firewall-cmd --zone=public --add-port=1433/tcp --permanent
sudo firewall-cmd --reload

To access the server, you can use Microsoft SQl Management Studio, which has about 900 MB, or a more lightweight solution like SQuirrel

Database and Table

For my example, I created a database eknori and a table users. The table is dead simple with only an id, firstname and lastname for a user.

CREATE TABLE [dbo].[users](
	[id] [varchar](50) NOT NULL,
	[firstname] [varchar](max) NULL,
	[lastname] [varchar](max) NOT NULL,
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

myBatis

With SQl server running, we can now start to create our project to work with the database.

You can download the source code here.

Since we are communicating with the database, we have to configure the details of the database. config.xml is the file used for the XML-based configuration. By using this file, you can configure various elements.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
 
<configuration>
    <typeAliases>
        <typeAlias alias="User" type="de.eknori.sql.model.User"/>        
    </typeAliases>
 
    <environments default="development">
        <environment id="development">
          <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"/>
                <property name="url" value="jdbc:sqlserver://YourServer:1433;databaseName=eknori"/>
                <property name="username" value="sql-user"/>
                <property name="password" value="sql-pwd"/>
            </dataSource>
       </environment>
    </environments>
    
    <mappers>
       <mapper resource="de/eknori/sql/mybatis/mapper/User.xml"/>
    </mappers>
</configuration>

Within the environments element, we configure the environment of the database that we use in our application.

In MyBatis, you can connect to multiple databases by configuring multiple environment elements. To configure the environment, we are provided with two sub tags namely transactionManager and dataSource.

  • JDBC, the application is responsible for the transaction management operations, such as, commit, roll-back, etc…
  • MANAGED, the application server is responsible to manage the connection life cycle. It is generally used with the Web Applications

The dataSource tag is used to configure the connection properties of the database, such as driver-name, url, user-name, and password of the database that we want to connect.

Instead of specifying the absolute class name everywhere, we can use typeAliases, a shorter name for a Java type.
The mapper element is used to configure the location of mapper xml files, which contain the mapped SQL statements. Our file is User.xml.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 
<mapper namespace="User">

    <resultMap id="result" type="User">
        <result property="id" column="id"/>
        <result property="firstname" column="firstname"/>   
        <result property="lastname" column="lastname"/>  
    </resultMap>
 
    <select id="selectAll" resultMap="result">
        SELECT * FROM users;
    </select>
    
 	<select id="selectById" parameterType="int" resultMap="result">
        SELECT * FROM users WHERE id = #{id}
    </select>
    
 	<insert id="insert" parameterType="User">
        INSERT INTO users (id, firstname, lastname) VALUES (#{id},#{firstname}, #{lastname});
    </insert>
    
    <update id="update" parameterType="User">
        UPDATE users
        SET firstname = #{firstname}
        SET lastname = #{lastname}
        WHERE id = #{id} 
  	</update>
 
	<delete id="delete" parameterType="int">
        DELETE from users WHERE id = #{id}
    </delete>
</mapper>

This configuration abstracts almost all of the JDBC code, and reduces the burden of setting of parameters manually and retrieving the results.

We only need some simple methods and POJO to send and receive data to and from the database.

Here is our User object.


package de.eknori.sql.model;

public class User {

	private int		id;
	private String	firstname;
	private String	lastname;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getFirstName() {
		return firstname;
	}

	public void setFirstName(String name) {
		firstname = name;
	}

	public String getLastname() {
		return lastname;
	}

	public void setLastname(String lastname) {
		this.lastname = lastname;
	}

	@Override
	public String toString() {
		return "id: " + id + " FirstName: " + firstname + " LastName: " + lastname;
	}
}

userDAO contains the methods that use the mapped SQL statements from User.xml


package de.eknori.sql.dao;

import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;

import de.eknori.sql.model.User;

public class UserDAO {

	private SqlSessionFactory sqlSessionFactory = null;

	public UserDAO(SqlSessionFactory sqlSessionFactory) {
		this.sqlSessionFactory = sqlSessionFactory;
	}

	public List<User> selectAll() {
		List<User> list = null;
		SqlSession session = sqlSessionFactory.openSession();

		try {
			list = session.selectList("User.selectAll");
		} finally {
			session.close();
		}

		return list;

	}

	public User selectById(int id) {
		User user = null;
		SqlSession session = sqlSessionFactory.openSession();
		try {
			user = session.selectOne("User.selectById", id);

		} finally {
			session.close();
		}
		return user;
	}

	public int insert(User user) {
		int id = -1;
		SqlSession session = sqlSessionFactory.openSession();

		try {
			id = session.insert("User.insert", user);
		} finally {
			session.commit();
			session.close();
		}
		return id;
	}

	public void update(User user) {
		@SuppressWarnings("unused")
		int id = -1;
		SqlSession session = sqlSessionFactory.openSession();

		try {
			id = session.update("User.update", user);

		} finally {
			session.commit();
			session.close();
		}
	}

	public void delete(int id) {

		SqlSession session = sqlSessionFactory.openSession();

		try {
			session.delete("User.delete", id);
		} finally {
			session.commit();
			session.close();
		}
	}
}

Finally we have ConnectionFactory that reads config.xml and establishes a connection to the database.


package de.eknori.sql.mybatis;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Reader;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class ConnectionFactory {

	private static SqlSessionFactory sqlSessionFactory;

	static {
		try {

			String resource = "de/eknori/sql/mybatis/config.xml";
			Reader reader = Resources.getResourceAsReader(resource);

			if (sqlSessionFactory == null) {
				sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
			}

		} catch (FileNotFoundException fileNotFoundException) {
			fileNotFoundException.printStackTrace();
		} catch (IOException iOException) {
			iOException.printStackTrace();
		}
	}

	public static SqlSessionFactory getSqlSessionFactory() {
		return sqlSessionFactory;
	}
}

And, action!

We are now ready to read and write data from and to the database


package de.eknori.sql;

import java.util.List;

import de.eknori.sql.dao.UserDAO;
import de.eknori.sql.model.User;
import de.eknori.sql.mybatis.ConnectionFactory;

public class Main {

	private static User	user	= null;
	private static int	id		= 999;

	public static void main(String args[]) {

		UserDAO userDAO = new UserDAO(ConnectionFactory.getSqlSessionFactory());

		List<User> users = userDAO.selectAll();
		users.forEach(element -> System.out.println(element));

		userDAO.delete(id);

		user = new User();
		user.setId(id);
		user.setFirstName("Ulrich");
		user.setLastname("Krause");
		userDAO.insert(user);

		user = userDAO.selectById(id);

		System.out.println(user);

	}
}

That is easy, isn’t it?

myBatis has more options to configure your project like Annotations and Dynamic SQL, but I found the configuration via .xml mapper files the easiest one to start with.

If you want to use other SQL servers instead, you only have to configure config.xml accordingly and download and import the correct driver.


[Notes 9.0.1 FP10] – “Script contains errors. Would you like to save it anyway.” #ibmchampion

When editing code in the LS editor, code will become corrupt.

  • Steps to reproduce:
    Create a new application “scripteditor.nsf” in the local data directory.
  • Open the application “View -> Design
  • Navigate to “Code -> Script Libraries” and create a new LotusScript library “test
  • Insert the following code into the Declaration section and save the library. Code is just a sample; you can use any other code as well.
Private Const ERR_OPEN_IDV_DB = 3000
Private Const ERR_OPEN_BCK_DB = 3001
Private Const ERR_MISSING_SERVER_NAME = 3002
Private Const ERR_MISSING_USER_NAME = 3003
Private Const ERR_MISSING_FILE_NAME = 3004
Private Const ERR_MISSING_PWD = 3005
Private Const ERR_DOC_MARKED_INACTIVE = 3006
Private Const ERR_DOC_MARKED_ACTIVE = 3007

Private Const ERR_OPEN_IDV_DB_MSG = "Unable to open IDVault database "
Private Const ERR_OPEN_BCK_DB_MSG = "Unable to open backup database"
Private Const ERR_MISSING_SERVER_NAME_MSG = "ServerName must not be empty"
Private Const ERR_MISSING_USER_NAME_MSG = "UserName must not be empty"
Private Const ERR_MISSING_FILE_NAME_MSG = "IdFilePathName must not be empty"
Private Const ERR_MISSING_PWD_MSG = "Password must not be empty"
Private Const ERR_DOC_MARKED_INACTIVE_MSG = "Document is already marked inactive"
Private Const ERR_DOC_MARKED_ACTIVE_MSG = "Document is already marked active"
  • Click on the “root entry” of the library = test

  • Place the cursor at the end of a line of code and hit the return key

  • Save the library; click the “save” icon or “File -> Save

You will get an error message

Click a couple of time in the code. After a while you will see that codelines start to duplicate and also codelines are truncated.

I was able to recreate the issue with applications that have been created years ago as well as new applications that were created using 901FP10. The error is reproducible by other customers, too.

The issue is NOT reproducible with 901FP9

I have created a PMR# 92011,031,724 with IBM today.

UPDATE: IBM has confirmed the bug. Tracked under SPR# KHLEAWNPZ6 (APAR #LO93728)


Crash: Java_com_ibm_oti_vm_VM_getClassNameImpl+0x63 (java_lang_class.cpp:281, 0x00007FFFFE6C87A3 [jclse7b_29+0x87a3])

After upgrading to Domino 901FP9, the latest jRebel version crashed the server. But not all server with this combination of software releases. With FP10 it got worse; now the crash was reproducible on all servers.

I created a PMR with IBM and the issue is tracked under SPR# OSAMAVRQKN

The above crash is easily recreatable locally without Domino or jRebel involved with JAVA 8 release.

On Windows:

  • set JREDIR= …
  •  %JREDIR%\bin\java -Dcom.ibm.oti.vm.bootstrap.library.path=%JREDIR%\bin\default;%JREDIR%\bin -version

On Linux:

  • export JREDIR= …
  • $JREDIR/bin/java -Dcom.ibm.oti.vm.bootstrap.library.path=$JREDIR/lib/amd64/default:$JREDIR/lib/amd64 -version

I did further testing with various java releases.

The first non-working level reproducing this crash for each java release is given below:

  • JAVA 6/26 SR5
  • JAVA 7 SR4
  • JAVA 7.1 GA
  • JAVA 8 GA

Above test results indicate that crash doesn’t happen at JAVA 6/26 SR4 or below. Crash doesn’t occur at JAVA 7 SR3 or below. JAVA 7.1 and JAVA 8 releases reproduce this crash from General Availability onwards.

Complete JAVA 6 release doesn’t reproduce this crash and runs fine including the latest build.

Today I got a response from the J9VM support team.

In old versions of JDK, by default we are using nocompressedrefs (meaning -Xnocompressedrefs). So jre/lib/amd64/default is the one where we find all the JVM native libraries.

In newer versions of JDK, by  default we are using compressedrefs (meaning -Xcompressedrefs). So jre/lib/amd64/compressedrefs is the one where we find all the JVM native libraries.

Therefore it would cause native library mis-match if you use -Dcom.ibm.oti.vm.bootstrap.library.path=jre/lib/amd64/default for -Xcompressedrefs.

The following commands will fail (becaue it would have native library mis-match):

  • <Path>/jre/bin/java -Dcom.ibm.oti.vm.bootstrap.library.path=<Path>/jre/lib/amd64/default   -version
  • <Path>/jre/bin/java -Xcompressedrefs -Dcom.ibm.oti.vm.bootstrap.library.path=<Path>/jre/lib/amd64/default -version
  • <Path>/jre/bin/java -Xnocompressedrefs  -Dcom.ibm.oti.vm.bootstrap.library.path=<Path>/jre/lib/amd64/compressedrefs -version

The following commands will be OK (becaue it would not have native  library mis-match):

  • <Path>/jre/bin/java -Dcom.ibm.oti.vm.bootstrap.library.path=<Path>/jre/lib/amd64/compressedrefs -version
  • <Path>/jre/bin/java -Xcompressedrefs -Dcom.ibm.oti.vm.bootstrap.library.path=<Path>/jre/lib/amd64/compressedrefs -version
  • <Path>/jre/bin/java -Xnocompressedrefs  -Dcom.ibm.oti.vm.bootstrap.library.path=<Path>/jre/lib/amd64/default -version

 


Electron – Cross Platform Desktop Apps Made Easy (Part 6) #ibmchampion

Following part 1 – 5 of the Electron tutorial you’re probably ready with the design, preparation and debugging of your app.

So you are ready to create your first release. You have no clue, how to do that? No worries!

In this article you are going to learn how to create a release of your Electron app for different platforms (Windows, Mac, Linux) and in all architectures (x32, x64) using the electron packager module.

Lets prepare our projects folder first. For this article, I copied the part5 folder and renamed it to part6. Also change the ‘name‘ parameter in package.json to part6

 

{
  "name": "part6",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "start": "electron ."
  },
  "author": "Ulrich Krause",
  "license": "MIT",
  "devDependencies": {
    "electron": "^1.8.2"
  }
} 

Electron Packager is a command line tool and Node.js library that bundles Electron-based application source code with a renamed Electron executable and supporting files into folders ready for distribution. Note that packaged Electron applications can be relatively large (40-60 MB).

Electron Packager is known to run on the following host platforms:

  • Windows (32/64 bit)
  • OS X
  • Linux (x86/x86_64)

It generates executables/bundles for the following target platforms:

  • Windows (also known as win32, for both 32/64 bit)
  • OS X (also known as darwin) / Mac App Store (also known as mas)
    Note for OS X / MAS target bundles: the .app bundle can only be signed when building on a host OS X platform.
  • Linux (for x86, x86_64, and armv7l architectures)

In order to build and package your app, you need to install electron-packager first. You can install it globally or as a dev dependency. We will install electron-packager globally with

npm install electron-packager -g

On OS X, you’ll need root access!

To build an application for a platform you’ll need to execute the following command in terminal / command prompt.

electron-packager <sourcedir> <appname> --platform=<platform> --arch=<arch> [optional flags...]

To build a package from the current directory and for the current platform execute.

electron-packager .

electron-packager will do the following:

  • Use the current directory for the sourcedir
  • Infer the appname from the productName in package.json
  • Infer the appVersion from the version in package.json
  • Infer the platform and arch from the host, in this example, darwin platform and x64 arch.
  • Download the Windows x64 build of Electron (and cache the downloads in ~/.electron)
  • Build the Windows part6.app
  • Place part6.app in part6/part6-win32-x64/ (since an out directory was not specified, it used the current working directory)

To build packages for all hosts and platforms, you can execute

electron-packager . --all

Please keep in mind that’s recommendable to build every platform on it’s respective platform i.e build the Windows version of your app in a Desktop with Windows as operative system.
Although for some platforms is possible to build for other platforms i.e you can build the Linux and Windows versions in a Windows computer, you’ll be unable to create a Mac application in a Windows platform, therefore you need to build it in a Mac environment.

Creating distributables like installers

Now lets create an installer for the application. I am doing this on a Mac, but it also works on any other supported platform.
On my Mac, I have electron-packager installed as a dev dependency ( I do not have root access to the machine 🙁 )

npm install electron-packager --save-dev

Also, we need to install electron-builder. Execute

npm install electron-builder --save-dev

In package.json, electron-packager and electron-builder will be added during installation. ( lines 39, 40 )

Create a new script in the scripts section of package.json. ( line 7 ) and also add lines 12 – 36. package.json should now look like this

{
  "name": "part6",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "dist": "build",
    "start": "electron ."
  },
  "author": "Ulrich Krause",
  "license": "MIT",
  "build": {
    "dmg": {
       "contents": [
        {
          "x": 110,
          "y": 150
        },
        {
          "x": 240,
          "y": 150,
          "type": "link",
          "path": "/Applications"
        }
      ]
    },
    "linux": {
      "target": [
        "AppImage",
        "deb"
      ]
    },
    "win": {
      "icon": "build/icon.ico"
    }
  },
  "devDependencies": {
    "electron": "^1.8.2",
    "electron-builder": "^20.0.6",
    "electron-packager": "^11.0.1"
  }
}

If you do not use the source for this article make sure that you at least copy the build folder to your project folder. It contains the icons that are used during the build process. You will get an error about missing icons if the icons are not present.

Next, we can start the build with

npm run dist

You should see similar output in your terminal

midpoints-macbook-ulrich:part6 ulrich$ npm run dist

> part6@1.0.0 dist /Volumes/DATA/Temp/part6
> build

electron-builder version=20.0.6
loaded configuration file=package.json (“build” field)
description is missed in the package.json appPackageFile=/Volumes/DATA/Temp/part6/package.json
writing effective config file=dist/electron-builder.yaml
no native production dependencies
packaging platform=darwin arch=x64 electron=1.8.2 appOutDir=dist/mac
skipped macOS application code signing reason=cannot find valid “Developer ID Application” identity …
building target=macOS zip arch=x64 file=dist/part6-1.0.0-mac.zip
building target=DMG arch=x64 file=dist/part6-1.0.0.dmg
building block map blockMapFile=dist/part6-1.0.0.dmg.blockmap
midpoints-macbook-ulrich:part6 ulrich$

You can ignore the warning about code signing for the moment.

electron builder now has created a DMG file. You can now install part6-1.0.0.dmg on your Mac.


(screenshot shows “part5”, I know 🙂 )

If you want to use a different icon for your application, replace build/icon.icns with your own. The filename MUST be icon.icns!

We only have scratched the surface; electron-builder has more options. For a complete list, read

Also, I found electron-forge. Electron Forge isa complete tool for building modern Electron applications. It unifies the existing (and well maintained) build tools for Electron development into a simple, easy to use package so that anyone can jump right in to Electron development.

That’s it for today.


Electron – Cross Platform Desktop Apps Made Easy (Part 5) #ibmchampion

Today, I want to show, how we can use Electron’s Notification Api for Windows, Linux and macOS. All three operating systems provide means for applications to send notifications to the user.

Electron conveniently allows developers to send notifications with the HTML5 Notification API, using the currently running operating system’s native notification APIs to display it.

Lets write some code. From the past 4 parts of this tutorial you already know, that we start with a new, empty project. So, create a new folder in your projects folder, run npm init and npm install electron --save-dev to create package.json and add Electron as a dependency. Also add 2 new files; app.js and main.html.

Here is, how your project should look like.

{
  "name": "part5",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "start": "electron ."
  },
  "author": "Ulrich Krause",
  "license": "MIT",
  "devDependencies": {
    "electron": "^1.8.2"
  }
}
const {
    app,
    BrowserWindow
} = require('electron')

// Keep a global reference of the window object, if you 
// don't, the window will be closed automatically when 
// the JavaScript object is garbage collected.
let win

function createWindow() {
    win = new BrowserWindow({
        width: 800,
        height: 600
    })
    win.loadURL('file://' + __dirname + '/main.html')
}

app.on('ready', createWindow)
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>Desktop Notification</title>
</head>

<body>
    <h1>Desktop Notification</h1>
</body>

</html>

Avoid platform-dependent code

Before we start with our notification, here are some things, you need to know, when creating Electron applications for different platforms. There are some platform specific elements that you already might know from other programming languages.
Temp dir, path delimiter, just to name a few.

According to Electron’s ‘Coding Style

Avoid platform-dependent code:

  1. Use path.join() to concatenate filenames.
  2. Use os.tmpdir() rather than /tmp when you need to reference the temporary directory.

This being said, we will now make some modifications to app.js. We add some core Node.js modules ( lines 2, 3 ) to handle path and URL. Then we use methods from those modules to build the URL for main.html in line 23

// add core Node.js modules
const url = require('url')
const path = require('path')

const {
    app,
    BrowserWindow
} = require('electron')

// Keep a global reference of the window object, if you 
// don't, the window will be closed automatically when 
// the JavaScript object is garbage collected.
let win

function createWindow() {
    win = new BrowserWindow({
        width: 640,
        height: 480
    })
    
    // use url and path to make loading of main.html
    // platform independend
    win.loadURL(url.format({
        pathname: path.join(__dirname, 'main.html'),
        protocol: 'file:',
        slashes: true
    }))
}

app.on('ready', createWindow)

Next, we will add code to handle window and application closing, because macOS behaves a little bit different than Windows or Linux. Read the code comments for explaination.

// add core Node.js modules
const url = require('url')
const path = require('path')

const {
    app,
    BrowserWindow
} = require('electron')

// Keep a global reference of the window object, if you 
// don't, the window will be closed automatically when 
// the JavaScript object is garbage collected.
let win

function createWindow() {
    win = new BrowserWindow({
        width: 640,
        height: 480
    })

    // use url and path to make loading of main.html
    // platform independend
    win.loadURL(url.format({
        pathname: path.join(__dirname, 'main.html'),
        protocol: 'file:',
        slashes: true
    }))

    // Emitted when the window is closed.
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    win.on('closed', function () {
        win = null
    })
}

app.on('ready', createWindow)

// Quit when all windows are closed.
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
app.on('window-all-closed', function () {
    if (process.platform !== 'darwin') {
        app.quit()
    }
})

// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
app.on('activate', function () {
    if (mainWindow === null) {
        createWindow()
    }
})

With these modifications, you now have a boilerplate that you can use when creating new Electron applications. You can also use electron-quick-start from GitHub which is similar.

Notifications

HINT: if you are working on Windows, the following sample will not work with all versions. It should work on Win 7 & 8, but might fail on Windows 10. Especially, if you have ‘Windows 10 fall creators update’ installed. This is a known issue. Notifications on Windows only work, if the application is packaged and installed on the target platform. Packaging and Installation is planned to be a topic for an upcoming article. If you are on Linux or macOS, you can keep on reading.

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>Desktop Notification</title>
</head>

<body>
    <h1>Desktop Notification</h1>
    <button type="button" name="button" onclick="doNotify()">Notify me</button>
    <script src="main.js"></script>
</body>

</html>

We add a button to our main.html ( line 11 ) that will call doNotify() when clicked and also add a script tag ( line 12 ). We then create a new main.js file in our project folder. main.js will contain the doNotify() function.

function doNotify() {
    const notification = {
        title: 'Basic Notification',
        body: 'This is an Electron notification'
    }
    const myNotification = new window.Notification(notification.title, notification)
}

I have created this example on a Windows 10 machine. As it is not showing the notification there, I moved my project folder to my Mac, and I get

And when I click the ‘Notify me‘ button, the notification appears.

This is just a simple example. For more information see https://electronjs.org/docs/api/notification.

You can download the source code for this part of the tutorial here.


Electron – Cross Platform Desktop Apps Made Easy (Part 4) #ibmchampion

Today, I want to show how you can use a system file dialog in your Electron application to select a file in the filesystem and display its content.

As always, we will create a new application. Create a new folder in your projects directory, change into the folder and from a terminal window initialize the application with npm init. Then execute npm install electron --save-dev to install electron and add it as a dependency to package.json.

Don’t forget to add a start script to package.json. This will let you start your application from the command line by simply running the npm start command.

Your package.json should look similar like this.

{
  "name": "part4",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "start": "electron ."
  },
  "author": "Ulrich Krause",
  "license": "MIT",
  "devDependencies": {
    "electron": "^1.8.2"
  }
}

Next create a new file, app.js. That is the main file of our application.

On application start, we want to open a system file dialog when the main.html has been loaded. We can then browse and select a file in the filesystem. When the OK button is clicked, the application will load the file and display its content.

const {app, BrowserWindow, ipcMain} = require('electron') 
const url = require('url') 
const path = require('path') 

let win  

function createWindow() { 
   win = new BrowserWindow({width: 800, height: 600}) 
   win.loadURL('file://' + __dirname + '/main.html') 
}  

ipcMain.on('openFile', (event, path) => { 
   const {dialog} = require('electron') 
   const fs = require('fs') 
   dialog.showOpenDialog(function (fileNames) { 

      if(fileNames === undefined) { 
         console.log("No file selected");    
      } else { 
         readFile(fileNames[0]); 
      } 
   });
   
   function readFile(filepath) { 
      fs.readFile(filepath, 'utf-8', (err, data) => { 
         
         if(err){ 
            alert("An error ocurred reading the file :" + err.message) 
            return 
         } 
         
         // handle the file content 
         event.sender.send('fileData', data) 
      }) 
   } 
})  
app.on('ready', createWindow)

Have you noticed the let win in line 9? We have not used it before. But it is an important detail in an Electron app. Without this line your win object ( the main window of your application ) would be destroyed as soon as the garbage collection recyles the object, and your application will die. If you use let win, you are save.

When the main process loads main.html, the ipcRenderer on main.htmll sends the ‘openFile‘ message to the main process ( line 11 in main.html ).
In line 12 of app.js, this message is catched and the file dialog opens. You can then browse and select a file. Once you have confirmed your selection, the content of the file is being read into data and then send to the renderer in line 33 along with the ‘fileData‘ message.

<!DOCTYPE html> 
<html> 
   <head> 
      <meta charset = "UTF-8"> 
      <title>File read using system dialogs</title> 
   </head> 
   
   <body> 
      <script type = "text/javascript"> 
         const {ipcRenderer} = require('electron') 
         ipcRenderer.send('openFile', () => { 
         }) 
         
         ipcRenderer.on('fileData', (event, data) => { 
            document.write(data) 
         }) 
      </script> 
   </body> 
</html>

When the renderer receives the ‘fileData‘ message in line 14, it will write the content of data to the document.

This is only a simple example, but I think, that you got the idea of how to use system dialogs in your Electron application. Try to create a button, that opens the dialog on click. It is not that difficult 🙂

you can download the source code for this part of the tutorial here.


Electron – Cross Platform Desktop Apps Made Easy (Q & A) #ibmchampion

I got some very good feedback on the first 3 articles. So, thank you very much for kind words, critics and suggestions! Much appreciated.

Also, I got a couple of questions. Instead of answering the questions offline, I thought it is a good idea to add a Q&A article to the tutorial. I will add more content as questions are coming in.

I am NOT an expert in Javascript, Node.js or Electron. I will answer the questions as best I can. Feel free to add comments.

Q: I have never worked with Node.js and npm. What the heck is it?

Node.js is an open source, cross-platform runtime environment for developing server-side and networking applications. Node.js applications are written in JavaScript, and can be run within the Node.js runtime on OS X, Microsoft Windows, and Linux.

Node.js also provides a rich library of various JavaScript modules which simplifies the development of web applications using Node.js to a great extent.

Node.js = Runtime Environment + JavaScript Library

NPM is a package manager for Node.js packages, or modules if you like. It can be compared to Linux, where you use apt-get, yum and the like to add or update functions to the core Linux system. In Java, you would add .jar files from the Maven repository to your project to make specific classes and method available without reinventing the wheel. www.npmjs.com hosts thousands of free packages to download and use.

A package in Node.js contains all the files you need for a module. Modules are JavaScript libraries you can include in your project.

The NPM program is installed on your computer when you install Node.js. NPM creates a folder named “node_modules“, where the package will be placed. All packages you install in the future will be placed in this folder.

NPM = online repositories for node.js packages/modules which are searchable on search.nodejs.org

NPM = command line utility to install Node.js packages, do version management and dependency management of Node.js packages.

Q: I am using Visual Studio Code. Do I really need an additional terminal window to launch applications?

No, you don’t need an additional terminal window. Visual Studio Code comes with an integrated terminal.
To activate click on “View -> Integrated Terminal”  or use Strg + Backtick ( Strg + ö when you have a German keyboard layout ) to toggle the integrated terminal on/off.

You can also open an additional instance of the terminal by clicking on the plus sign.

For more details on how to configure the integrated terminal, I recommend reading this article.

Q: Why no semicolons?

I thought it was still best to include them to avoid potential problems.

That’s a common misconception, here is a response to it: http://blog.izs.me/post/2353458699/an-open-letter-to-javascript-leaders-regarding

Is there a coding standard for Node.js which explains how semicolons should be used?

Oh yeah, there are a lot of coding standards. And that’s the problem. 🙂
I think I’ll just explain why I moved away from using semicolons. It’s easier to see potential errors this way.

I mean, if you use semicolons, one semicolon missing could go unnoticed. Because your eye is trained to treat them as end of line noise. Here is an example of an error that you could have:

function test() {
  var foo = '123';
  var bar = '456';
  var example = a_long_line_without_ending_semicolon
                                              //   ^^^ 
                                              // this would usually go unnoticed

  (function () {
    console.log(example); 
  })(example);
}

But if you don’t use them, the semicolon at the end of the line never matters.

Semicolons in JavaScript are optional

Here is an article that covers the topic in deep.

Q: Why you sometimes use curly braces with const and sometimes not?

To instantiate an Electron object, you would normally write

const app = require('electron').app
const BrowserWindow = require('electron').BrowserWindow
const ipcMain = require('electron').ipcMain

That is a lot of redundancy in code. A better way is to assign the entire Electron framework to an object und use this object to extract the Electron object that we want.

const electron = require('electron')
const app = electron.app
const BrowserWindow = electron.BrowserWindow
const ipcMain = electron.ipcMain

Slightly better, isn’t it?

Chrome 49 added destructuring assignment to make assigning variables and function parameters much easier and we can just write

const electron = require ('electron');
const {app, BrowserWindow, ipcMain} = electron;

or

const {app, BrowserWindow, ipcMain} = require ('electron');

as the shortest possible. You can use the latter, if you have a final list of objects. The first one is handy, if you want to get access to other Electron objects in your code. You simply reference to electron then.

Q: The menu is also visible in the Preferences page; how can I hide it?

In main.js, menu is build and added to the application in lines 18 and 19.

const {remote, ipcRenderer} = require('electron')
const {Menu} = remote.require('electron')

const template = [
    {
       label: 'Electron',
       submenu: [
        {
            label: 'Preferences',
            click: function() {
                ipcRenderer.send('show-prefs')
            }
        }
       ]
    }
]

var menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)

The static method Menu.setApplicationMenu(menu) sets menu as the application menu on macOS. On Windows and Linux, the menu will be set as each window’s top menu.

This explains, why the menu is also visible in the Preferences page. How can it be removed?

You can use autoHideMenuBar:true. This will auto hide the menu bar. The downside is that you can still toggle the visibility when the Alt key is pressed.

const electron = require ('electron');
const {app, BrowserWindow, ipcMain} = electron;
app.on('ready', function() {
  var mainWindow = new BrowserWindow({
      width:800,
      height:600
  });
mainWindow.loadURL('file://' + __dirname + '/main.html');
mainWindow.openDevTools();

var prefsWindow = new BrowserWindow({
  width:400,
  height:400,
  show:false,
  autoHideMenuBar:true
});

prefsWindow.loadURL('file://' + __dirname + '/prefs.html');

ipcMain.on('show-prefs', function() {
  prefsWindow.show()
});

ipcMain.on('hide-prefs', function() {
  prefsWindow.hide()
});
})

 


Electron – Cross Platform Desktop Apps Made Easy (Part 3) #ibmchampion

Today, we are going to create a multi window application using Electron. I assume that you already have Node.js installed.

Let us start and create a new folder in our projects directory. ( C:\projects\electron\multi-window ). Then open the folder in the command window.

Next use npm init to create the package.json file and install Electron and save it to our dev dependency with npm install electron --save-dev

Change the package.json so we can start our application using npm start later on.

Your package.json should now look like this.

{
  "name": "multi-window",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "dependencies": {
  "electron": "^1.8.2"
},
  "devDependencies": {},
  "scripts": {
  "start": "electron ."
},
  "author": "",
  "license": "ISC"
}

We can now start creating our first window. Create a new file app.js in the multi-window folder and open it in your preferred text editor.

The sample code below shows the minimal code that is needed to create the main window, give it a defined height and width and load the main.html file from the current directory using the file protocol.

const electron = require ('electron');
const {app, BrowserWindow} = electron;

app.on('ready', function() {
var mainWindow = new BrowserWindow({
   width:800,
   height:600
  })
  mainWindow.loadURL('file://' + __dirname + '/main.html')
})

And here is our simple main.html file

<html>
    <head>
        <title>Multi Window Application
        </title>
    </head>
    <body>       
       <h1>Main</h1>
    </body>
</html>

At this point, we can start our application with npm start. You should get

A useful thing to do when you’re working on an app is to open up the dev tool. We can do this by adding the following line to our app.js code.

mainWindow.openDevTools()

When you now start your application, the dev tools also open at startup.

You can switch the tools off / on from the menu bar  “View -> Toggle Developer Tools” or using a keyboard shortcut command. If you deploy your application in production, comment out the line of code.

Create a custom menu bar

Lets create our own menu bar now. To do that, we add a new script tag to our main.html file.

<html>
    <head>
        <title>Multi Window Application
        </title>
    </head>
    <body>
        <h1>Main</h1>
        <script>require('./main.js')</script>
    </body>
</html>

Now that we are requiring the main.js in our main.html file, lets go ahead and create the main.js file. The file is going to handle all the javasript for our main window.

The first thing we want to do is to create a menu object. We can do that by typing

const {Menu} = require('electron')

which would perfectly fine, if we were in the app.js. app.js is the main process, and since we have loaded the main.js in the browser, we are in the renderer process.

So we can’t just simply require(‘menu’) here, we have to require the menu from the main process. To do that, we create a remote

const {remote} = require('electron')

and using this remote, we are able to call the require function, which will require the menu from the main process

const {Menu} = remote.require('electron')

Just be aware of this whenever you see remote. there are a couple of ways to create a menue. We will use a helper function that is on the Menu class

var menu = Menu.buildFromTemplate()

and you can just pass in a structured javascript object with the menu structure that you want. The code will create a main menu entry and a submenu; we will use the onClick event handler later.

const template = [
  {
   label: 'Electron',
   submenu: [
    {
     label: 'Preferences',
     click: function() {}
    }
   ]
  }
]

Then pass template as an argument to the function and attach the menu to our application.

var menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)

Lets go ahead and make that click do something. We could require a remote browser window in the onClick event itself, but it is not that efficient. A better way to do it is to manage all your windows in the main process.

Let us create a new window in app.js (lines 12 to 17).
We add a new prefsWindow the same way, we have added our mainWindow. The important thing here is show.false. When the app is ready, the prefsWindow is not yet shown.

const electron = require ('electron');
const {app, BrowserWindow} = electron;

app.on('ready', function() {
var mainWindow = new BrowserWindow({
      width:800,
      height:600
  })
  mainWindow.loadURL('file://' + __dirname + '/main.html')
  mainWindow.openDevTools()

  var prefsWindow = new BrowserWindow({
      width:400,
      height:400,
      show:false
  })
  prefsWindow.loadURL('file://' + __dirname + '/prefs.html')
})

Now we have that prefsWindow that is created but still hidden. And so we need a way to call and show that window when the Preferences menu item is clicked.

InterProcess Communication

To do that we use a thing called IPC ( InterProcess Communication ) which is very similar to remote. It allows us to send call back and forth between the main process and the renderer process.

Our main.js will look like this now

const {remote, ipcRenderer} = require('electron')
const {Menu} = remote.require('electron')

const template = [
    {
       label: 'Electron',
       submenu: [
        {
            label: 'Preferences',
            click: function() {
                ipcRenderer.send('show-prefs')
            }
        }
       ]
    }
]

var menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)

We have added a new varaiable ipcRenderer and use the ipRenderer to send a message when the menu item is clicked.

This will send a message up to our app, which we can catch in app.js

const electron = require ('electron');
const {app, BrowserWindow, ipcMain} = electron;

app.on('ready', function() {
  var mainWindow = new BrowserWindow({
      width:800,
      height:600
  })
mainWindow.loadURL('file://' + __dirname + '/main.html')
mainWindow.openDevTools()

var prefsWindow = new BrowserWindow({
  width:400,
  height:400,
  show:false
})
prefsWindow.loadURL('file://' + __dirname + '/prefs.html')

ipcMain.on('show-prefs', function() {
  prefsWindow.show()
})
})

When you now start your application and click the “Preferences” menu item, a new window will open.

If you close the prefsWindow clicking the X, you will get an error when you try to open it again using the menu. The X will destroy the prefsWindow object and so it is no longer available.

Next we will send messages back from the prefsWindow to the mainWindow. In our prefs.html we create a new script tag ( lines 7 to 15 ) and by now, we add the script directly instead of loading another .js file.

<html>
  <head>
     <title>Multi Window Application</title>
  </head>
  <body>
  <h1>Preferences</h1>
     <script>
        const {ipcRenderer} = require('electron')
        var button = document.createElement('button')
        button.textContent = 'Hide'
        button.addEventListener('click', function() {
            ipcRenderer.send('hide-prefs') 
        })
        document.body.appendChild(button)
     </script>
  </body>
</html>

add the following (highlighted) snippet to app.js. The code will listen for the hide-prefs message from the prefsWindows and closes the windows.

ipcMain.on('show-prefs', function() {
  prefsWindow.show()
})

ipcMain.on('hide-prefs', function() {
  prefsWindow.hide()
})
})

Now you can close the prefsWindow with a click on the button and reopen it from the menu.

You can download the full code for this part of the tutorial from here.