Release 0.1b: Contributing a new feature... Why not?

Welcome back to my Release 0.1!

In a previous post, I discussed the first activity of the Release 0.1 which was a bugfix. Today, I am going to continue with the series by talking about a new feature suggestion that I made to another note-taking app of one of my colleagues.

Let's get started!

Issue #2: New feature proposal for a saved-status indicator for a note-taking app

Links: Repository | Issue | Pull Request

To begin with, I found this note-taking app of my colleague which was a simple white page with a text box area to type the note and a default initial message. I cloned the repository and ran the app to test it out. However, I found it impossible to save the note since there was no hint anywhere.

The initial view of the app
I was really curious so I opened the source code to investigate how this app works The app was written in plain HTML, CSS and JavaScript with a library called Filer.js to support with locally storing and retrieving the content of the note. What surprised me was that it uses another library called Hotkey.js and has this code snippet:
hotkeys('ctrl+s', function(event) {
    event.preventDefault();
    save();
});

function save() {
    fs.writeFile('/note', document.querySelector('#notepad').innerHTML, (err) => {
        if (err) throw err;
        console.log('The file has been saved');
    });
}
It did not take me long to realize, the app actually has a "Save Note" feature triggered by pressing the combination "Ctrl" and "S" keys like in Window Notepad.

However, I find this not so much intuitive for many people and even when assuming that the author put this instruction somewhere in the app, people might just forget to press the combination to save before they close the tab or their browser.

Therefore, I decided to, at least, put an indicator in the app to show whether the current content of the note has been saved. This indicator can act as a reminder for the user to save the note and also give a hint on how to do it.

The process of implementing the new feature

I started by modifying the markup, adding an extra <span> element right below the text box area.
<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>My Web Notepad</title>
    <link rel="stylesheet" href="https://unpkg.com/papercss@1.6.1/dist/paper.css">
</head>
<body>
    <main class="container">
        <div class="paper" id="notepad" contenteditable="true"></div>
        <span id="status" class="saved"><strong>Note Saved!</strong></span>
    </main>
    <!-- A few <scipt> tags go here -->
</body>
</html>
I gave the indicator the initial message of "Note Saved!" and the "saved" class for styling purpose later on. I also took the liberty to wrap both the text box area and the indicator into a <main> element with the class "container" for extensibility of the markup and to distinguish them from the <script> elements below.
Now, the app should look like this image:
A simple message "Note Saved!" below the note area
For now, the message is just a static message. What we want is that the message will change immediately when we type something into the note area so what we need is an event listener tracking the "imput"  event of the note area and changing the indicator's message. Therefore, in our <script> tag, we can do...
window.addEventListener('DOMContentLoaded', (event) => {
    let notepad = document.getElementById("notepad");
    let indicator = document.getElementById("status");

    let setIndicatorStatus = function(status) {
        if (status == "saved") {
            indicator.innerHTML = "<strong>Note Saved!</strong>";
            indicator.classList.replace("unsaved", "saved");
        }
        if (status == "unsaved") {
            indicator.innerHTML = "<strong>Note Unsaved</strong>. Press <i>Ctrl + S</i> to save.";
            indicator.classList.replace("saved", "unsaved");
        }
    };

    notepad.addEventListener("input", event => {
        setIndicatorStatus("unsaved");
    });
    
    fs.readFile('/note', 'utf8', function (err, data) {
        // ...
    });
});
Now, when we change the note's content, we should see the indicator's message like this:
The indicator's message changed when the note has been changed
Besides that, we also want the indicator to reverse "Note Saved!" when the combination Ctrl + S is pressed which means that the users saved the note. Recall that the app has a save() function implemented already, I am just going to modify it  and put it inside the event handler of the "DOMContentLoaded" event so it has access to the fully loaded DOM:
let save;
hotkeys('ctrl+s', function(event) {
    event.preventDefault();
    save();
});
window.addEventListener('DOMContentLoaded', (event) => {
    let notepad = document.getElementById("notepad");
    let indicator = document.getElementById("status");

    let setIndicatorStatus = function(status) {
        if (status == "saved") {
            indicator.innerHTML = "<strong>Note Saved!</strong>";
            indicator.classList.replace("unsaved", "saved");
        }
        if (status == "unsaved") {
            indicator.innerHTML = "<strong>Note Unsaved</strong>. Press <i>Ctrl + S</i> to save.";
            indicator.classList.replace("saved", "unsaved");
        }
    };
    
    save = function() {
        fs.writeFile('/note', notepad.innerHTML, (err) => {
            if (err) return;
            setIndicatorStatus("saved");
        });
    };

    notepad.addEventListener("input", event => {
            setIndicatorStatus("unsaved");
    });
    
    fs.readFile('/note', 'utf8', function (err, data) {
        // ...
    });
});
So now, everything is done. When we save the note by pressing the combination Ctrl + S, the indicator should change its message accordingly as in the image below:
The indicator's message returns to "Note Saved!"  when the combination is pressed
Finally, I will just give the indicator's message a color associated with the saved/unsaved status of the note so the app will be a little bit more user-friendly by adding a small CSS snippet:
<head>
    <meta charset="utf-8">
    <title>My Web Notepad</title>
    <link rel="stylesheet" href="https://unpkg.com/papercss@1.6.1/dist/paper.css">
    <style>
        #status {
            padding: 2em;
        }

        .unsaved {
            color: red;
        }
        
        .saved {
            color: green;
        }
    </style>
</head>
Now, we not only have a working indicator, but we also have a colorful indicator 😄

Are we done, yet?

Technically, yes! We achieved our new feature in the app. However, there is a small bug in our indicator. The user in the process of writing a note might modify it in different ways, they can add some content in but later on remove it since they do not need it anymore. In this case, we want our indicator to be "smart" and shows that the note has been saved.

How do we implement this?

There are many ways we can achieve this, my idea is that we will have a variable holding the "before change" content of the note to be compared with the current content of the note every time the note is changed. The result of the comparison will decide the status of the note, Besides that, the "before change" value will be set when the DOM is loaded and updated every time the note is saved (the Ctrl + S combination is pressed).
The code below highlights the changes I would like to make to the code above to achieve this small bugfix:

let save;
hotkeys('ctrl+s', function(event) {
    event.preventDefault();
    save();
});
window.addEventListener('DOMContentLoaded', (event) => {
    let notepad = document.getElementById("notepad");
    let indicator = document.getElementById("status");
    let oldvalue;

    let setIndicatorStatus = function(status) {
        if (status == "saved") {
            indicator.innerHTML = "<strong>Note Saved!</strong>";
            indicator.classList.replace("unsaved", "saved");
        }
        if (status == "unsaved") {
            indicator.innerHTML = "<strong>Note Unsaved</strong>. Press <i>Ctrl + S</i> to save.";
            indicator.classList.replace("saved", "unsaved");
        }
    };
    
    save = function() {
        fs.writeFile('/note', notepad.innerHTML, (err) => {
            if (err) return;
            setIndicatorStatus("saved");
            oldvalue = notepad.innerHTML;
        });
    };

    notepad.addEventListener("input", event => {
        if (oldvalue != notepad.innerHTML) {
            setIndicatorStatus("unsaved");
        } else {
            setIndicatorStatus("saved");
        }
    });
    
    fs.readFile('/note', 'utf8', function (err, data) {
        if (err) {
            let str = "Welcome to my notepad!";
            notepad.innerHTML = str;
            oldvalue = str;
        }
        else if (data) {
            notepad.innerHTML = data;
            oldvalue = data;
        }
    });
});

The result

Note Saved!
An interactive demo script demonstrating the new feature

My experience

Small projects such as this one give me chances to practice reading and understanding other developers' code which is a crucial skill to have as an amateur developer. Adding a feature to existing code is even more important since that is a very common activity in the industry to improve, update software. Through this specific project, adding feature even helps me maintain good coding practice because while updating the app, I have to keep in mind to follow the original author's coding style, to respect the original author's work.

In addition, Git/Github, once again, demonstrates itself as a powerful tool for developers to collaborate. From committing code, branching to filing issues and making pull requests, they are all great features that are, according to my professor, "difficult to learn but easy to use". They enabled me and my colleagues to fix each other's bugs and contribute new code smoothly. Git/Github will be a skill that I embrace in the future.

Finally (and surprisingly), by doing Release 0.1, I catch up pretty quick in writing blog. I learnt how to attract readers to my post by adding more interactive elements and writing actual code which people can pick up and use rather than plain static text content. I also know how to narrate readers through my post and navigate them to the direction I am intending to go. I believe, over time, I can have posts with better content and higher quality for my readers.

Thank you very much for your interest and follow me for more posts in the future!

Comments

Popular posts from this blog

Release 0.1a: My First Contribution

Release 0.2b: #js-vs-py - Fianlly learning Python!