Creating a text editor in Electron: part 3 - Setting Preferences
Tags: electron, javascript, node
Published: 2018-09-12 18:12:46

---
title: Creating a text editor in Electron: part 3 - Setting Preferences
published: true
description:  
tags: electron, javascript, node
series: Creating a text editor in Electron
---

Welcome to the last part of the series where we explore the basics of Electron by building a text editor. If you like to get a full picture of what we're doing here, be sure to read [part one](https://dev.to/aurelkurtula/creating-a-text-editor-in-electron-reading-files-13b8), and [part two](https://dev.to/aurelkurtula/creating-a-text-editor-in-electron-part-2---writing-files-l80)

A convention that we see in almost all the apps we use is the ability to allow users to make their editor their own. That's what we will do in this section. We'll let the users set the theme of the application. By the end we'll have introduced a new window which will look like this: 

![](https://thepracticaldev.s3.amazonaws.com/i/mg6mi0d0wig6mbuf5s25.png)

## Custom header

Before we get into the meat of the tutorial lets, change the boring default header. 

(It's all about [making readers happy](https://dev.to/davidamunga/comment/5cko) 🤪). 

It's pretty simple. When we define the window (`new BrowserWindow`), we can give it options for the frame. If we set the `frame` to false, it would delete it- including the three buttons on the left-hand side. So we want to remove the default styling but not the buttons. At `./main.js` change the `BrowserWindow` definition to include the relevant option:

```javascript
window = new BrowserWindow({ 
    width:800, 
    height:600, 
    titleBarStyle: 'hidden' 
})
```

If we run the app we see the three buttons but no header, which means we would have to create our own. So in `./static/index.html` we would do something like this:

```
<header id="customtitle_wrap">
    <h1 id="customtitle">Texty</h1>
</header>
```

In [part two](https://dev.to/aurelkurtula/creating-a-text-editor-in-electron-part-2---writing-files-l80) we added the ability for an asterisk to appear in the title tag to indicate when a file needs saving. Now, we need to add that functionality to `#customtitle` rather than the title tag.

The CSS can now be whatever you'd like, however `-webkit-app-region: drag` should be applied to `#customtitle` so that it would be a handle from which to drag the window around the screen. Read the documentation for [frameless windows](https://github.com/electron/electron/blob/master/docs/api/frameless-window.md) to see all the options.

![](https://thepracticaldev.s3.amazonaws.com/i/qwxgkm99sm6zmwffc59f.png)

I grabbed a font from google fonts; however, in a real app, we would at least download the font so that users don't need to be connected to the internet. 

## Initializing the preferences window

Just as we've done on the [first tutorial](https://dev.to/aurelkurtula/creating-a-text-editor-in-electron-reading-files-13b8) we need to load an HTML page into a new window. Let's create the page at `./static/preferences.html`:

```html
<body>
    <p class="notification">Here you're able to personalise the interface by picking the colors you'd like to see. The changes will be saved automatically upon window being closed</p>
    <div class="container">
    <div id="content" >
        <form action="">
            <button id="defaultValues">Reset</button>
            <p><span>Background</span> <label for="background"></label> <span>
                <input type="text" name="background" value="#FFFFFF"></span> 
            </p>
            <p><span>Border Color</span> <label for="border-color"></label> <span>
                <input type="text" name="border-color" value="#50C68A"></span> 
            </p>
            <p><span>Text Color</span> <label for="text-color"></label> <span>
                <input type="text" name="text-color" value="#232323"></span> 
            </p>
            <p><span>Sidebar Background</span> <label for="sidebar-color"></label> <span>
                <input type="text" name="sidebar-color" value="#2F3235"></span> 
            </p>
            <p><span>Sidebar Text</span> <label for="sidebar-text"></label> <span>
                <input type="text" name="sidebar-text" value="#939395"></span> 
            </p>
        </form>
    </div>
    </div>
    <script src="scripts/preferences.js"></script>
</body>
```

This page has to launch when a menu button gets clicked. Let's add that button at `./components/Menu.js`

```javascript
{
    label: app.getName(),
    submenu: [
        {
            label: 'Preferences',
            accelerator: 'cmd+,', // shortcut
            click: _ => {
                const htmlPath = path.join('file://', __dirname, '../static/preferences.html')
                let prefWindow = new BrowserWindow({ width: 500, height: 300, resizable: false })
                prefWindow.loadURL(htmlPath)
                prefWindow.show()
                // on window closed
            },
        },
    ]
}
```

When `Preferences` is selected the `./static/preferences.html` page loads in a new browser window. This time we are making sure users are unable to resize it. 

With some CSS applied, we get this:

![](https://thepracticaldev.s3.amazonaws.com/i/qowqq4vmxy5w9oiw25v4.png)

As specified in the HTML above, the default colours are hardcoded in the form. With Javascript, we want to apply those colour values as the background colour for the labels, and when users enter new colour values have them be reflected in the labels. We could have fun with colour pickers, but we'll keep it basic and assume users want to input their preferred colours. In which case we need to listen to input changes. 

This functionality needs to go in `./static/scripts/preferences.js`. 

Let's remember the HTML:

```html
<p>
    <span>Sidebar Text</span> 
    <label for="sidebar-text"></label> <span>
    <input type="text" name="sidebar-text" value="#939395"></span> 
</p>
```            

Hence the javascript can be as simple as looping through the inputs and changing the labels: 

```javascript
var inputs = document.getElementsByTagName('input')
for(var i = 0 ; i < inputs.length; i++){
    document.querySelector(`label[for="${inputs[i].name}"]`).style.backgroundColor = inputs[i].value
    inputs[i].onkeyup = e => {
        document.querySelector(`label[for="${e.target.name}"]`).style.backgroundColor = e.target.value
    }
}
```

The code loops through each input element, apply their values as label background colours, then on input changes re-applies the colours. 

![](https://thepracticaldev.s3.amazonaws.com/i/5nf5rn33neezads3ace2.gif)

### Saving the colour preferences

The point of this window is that these colours persist when the application closes, so they have to be stored somewhere. Electron gives us a path to store user data. The [documentation](https://electronjs.org/docs/api/app) states that we access this through `electron.app.getPath('userData')`

> The directory for storing your app's configuration files, which by default it is the appData directory appended with your app's name.

Within that folder, we want to store our colours as JSON. We do this using the same messaging from Render process to the Main process pattern as we've done in [part two](https://dev.to/aurelkurtula/creating-a-text-editor-in-electron-part-2---writing-files-l80).

First, let's collect all the colours then send them to the Main process.

```javascript
let preferences = {};
for(var i = 0 ; i < inputs.length; i++){
    ...
    preferences[inputs[i].name] = inputs[i].value
    inputs[i].onkeyup = e => {
        preferences[e.target.name] = e.target.value
        ...
        ipcRenderer.send(PREFERENCE_SAVE_DATA_NEEDED, preferences)
    }
}
```

The `preferences` object is populated with all the default colours. Then whenever one of the inputs changes, the corresponding object key is changed. Lastly, we send a `PREFERENCE_SAVE_DATA_NEEDED` message to the Main process with the `preferences` object as the message body.

At the top of `./components/Menu.js` we can listen for the message and collect its data

```javascript
let inputs;
ipcMain.on(PREFERENCE_SAVE_DATA_NEEDED, (event, preferences) => {
    inputs = preferences
})
```

Finally, for the menu, a pattern which I've seen in almost all the mac apps is that preferences are saved without needing a "save" button. We can do the same thing here by acting upon window getting closed.

In the Menu page, we can write the logic on window `close`.

```javascript
{
    label: 'Preferences',
    accelerator: 'cmd+,', // shortcut
    click: _ => {
        ....
        prefWindow.on('close', function () {
            prefWindow = null 
            userDataPath = app.getPath('userData');
            filePath = path.join(userDataPath, 'preferences.json')
            inputs && fs.writeFileSync(filePath, JSON.stringify(inputs));
            window.webContents.send(PREFERENCE_SAVED, inputs); 
        })
        
    },
}
```                

The `userDataPath` is located at `/Users/YourUserName/Library/Application Support/Electron/` and in there you'll find our `preferences.json` which holds the colours. 

When that's done, the `PREFERENCE_SAVED` message is sent to the Render process of our original `window`. 

Now we need to read the colours from the `preferences.json` file and apply them in the UI. 

First, let's do it in the `./static/scripts/preferences.js`

```javascript
const fs = require('fs')
let userDataPath = remote.app.getPath('userData');
let filePath = path.join(userDataPath, 'preferences.json')
let usersStyles =  JSON.parse( fs.readFileSync(filePath) )

for(let style in usersStyles) {
    document.querySelector(`input[name="${style}"]`).value = usersStyles[style]
    document.querySelector(`label[for="${style}"]`).style.backgroundColor = usersStyles[style]
}
```

The process is reversed there. We read the saved data from `preferences.json`, loop through the colours and apply them as the input values and label background colours.


#### Reseting colors. 

The reason why we'd want to hardcode the colours in the HTML form is so that we'd access them at any time with `defaultValue` in javascript. We'll do so upon clicking the reset button: 

```html
<button id="defaultValues">Reset</button>
```

On click, loop through the input fields and apply the default values accordingly.

```javascript
document.getElementById('defaultValues').addEventListener('click', function(e) { // reset
    e.preventDefault();
    preferences = {};
    for(var i = 0 ; i < inputs.length; i++){
        preferences[inputs[i].name] = inputs[i].defaultValue
        document.querySelector(`label[for="${inputs[i].name}"]`).style.backgroundColor = inputs[i].defaultValue
        inputs[i].value = inputs[i].defaultValue
    }
    ipcRenderer.send(PREFERENCE_SAVE_DATA_NEEDED, preferences)
} )
```

The above code does the following 

1. Resets the `preferences` object.
2. Re-populates the `preferences` object with input `defaultValue` .
3. Changes the background colour of labels with input `defaultValue`.
4. Changes the input value with input `defaultValue`.
5. Sends a message to the Main process.

## Applying the saved colour to the main window

Upon closing the preferences window, a message gets transmitted.

```javascript
window.webContents.send(PREFERENCE_SAVED, inputs);
```

We can listen to it in the main window and use the content sent with the message.

Before doing so let's talk CSS.

The most important bit of CSS are the variables: 

```css
:root {
    --background: #FFFFFF;
    --border-color: #50C68A;
    --text-color: #232323;
    --sidebar-color: #2F3235;
    --sidebar-text: #939395;
}
```

Whenever we change those variables with javascript, the appearance of every element where we applied those variables to would change. 

We can do this at `./static/scripts/index.js`

```javascript
    let userDataPath = remote.app.getPath('userData');
    let filePath = path.join(userDataPath, 'preferences.json')
    
    let usersStyles  = JSON.parse( fs.readFileSync(filePath) )

    for(let style in usersStyles) {
        document.documentElement.style.setProperty(`--${style}`, usersStyles[style]);
    }
    ipcRenderer.on(PREFERENCE_SAVED, function (event, inputs) {
        for(let style in inputs) {
            document.documentElement.style.setProperty(`--${style}`, inputs[style]);
        }
    });
```    
There you have it. Now every element that uses those variables will be changed automatically. 

And the end result looks like this

![](https://thepracticaldev.s3.amazonaws.com/i/rrd1k2589ruszh5t7vxf.gif)


**You can clone the [repository at GitHub](https://github.com/aurelkurtula/Texty/tree/part3)**

## Conclusion 

That's it for this series. As I tried to make it clear there are a lot of things missing. The code can definitely be refactored, there can be storage improvements, and error handling is none existent. 

While I was working on this demo I thought about my current writing workflow which consists of [node generated markdown pages hosted as GitLab wiki](https://dev.to/aurelkurtula/using-git-wiki-instead-of-evernote-to-organize-personal-content-e9d) and I feel adding an Electron interface might make the process of note-taking slightly smoother. That might be my next personal project.

Hope you've been equally inspired 💡

{% link https://dev.to/aurelkurtula/creating-a-text-editor-in-electron-reading-files-13b8 %}

{% link https://dev.to/aurelkurtula/creating-a-text-editor-in-electron-part-2---writing-files-l80 %}