Creating a sticky note app with HTML5, CSS3 and Javascript

I saw this tutorial the other day, it’s cool but I fancied taking it a step further and make it useful as an app rather than just a fancy way to show some structured data on the screen. Here’s a list of the features which I wanted to add:

  • being able to edit the title and content of the notes
  • being able to save all your notes
  • being able to add new notes
  • being able to delete notes you no longer want
  • randomize the colour of the notes more

HTML and CSS changes

I initially considered using the new contenteditable attribute in HTML5 to make the title and content editable, but settled on using textarea instead as it offers better support for multiple line text and you can easily add placeholder text as well as set a limit on the length of the title.

I added two images as buttons to add a new note and save the state of all the notes and put a a border around the notes to make them stand out a little more.

Here’s the updated HTML:

   1: <body>

   2:     <div id="controls">

   3:         <img id="btnNew" src="images/newnote.png"/>

   4:         <img id="btnSave" src="images/save.png"/>

   5:     </div>

   6:

   7:     <ul id="notes">

   8:     </ul>

   9: </body>

And the CSS to go along with it:

   1: * {

   2:     maring: 0;

   3:     padding: 0;

   4: }

   5:

   6: .hide {

   7:     display: none;

   8: }

   9:

  10: body {

  11:     font-size: 100%;

  12:     margin: 15px;

  13:     background: #666;

  14:     color: #fff;

  15: }

  16:

  17: #controls img {

  18:     width: 28px;

  19:     height: 30px;

  20:     background: rgba(0, 0, 0, 0);

  21: }

  22:

  23: #controls img:hover, #controls img:focus {

  24:     -webkit-transform: scale(1.2);

  25:       -moz-transform: scale(1.2);

  26:       -o-transform: scale(1.2);

  27:       position:relative;

  28: }

  29:

  30: ul, li {

  31:     list-style: none;

  32: }

  33:

  34: ul {

  35:     overflow: hidden;

  36:     padding: 15px;

  37: }

  38:

  39: ul li {

  40:     margin: 15px;

  41:     float: left;

  42:     position: relative;

  43: }

  44:

  45: ul li div {

  46:     text-decoration: none;

  47:     color: #000;

  48:     background: #ffc;

  49:     display: block;

  50:     height: 150px;

  51:     width: 150px;

  52:     padding: 10px;

  53:     -moz-box-shadow: 5px 5px 7px rgba(33, 33, 33, 1);

  54:     -webkit-box-shadow: 5px 5px 7px  rgba(33, 33, 33, .7);

  55:     box-shadow: 5px 5px 7px rgba(33, 33, 33, .7);

  56:     -moz-transition: -moz-transform .15s linear;

  57:     -o-transition: -o-transform .15s linear;

  58:     -webkit-transition: -webkit-transform .15s linear;

  59:     border-style: solid;

  60: }

  61:

  62: ul li div img {

  63:     padding: 1px 3px;

  64:     margin: 10px;

  65:     position: absolute;

  66:     top: 0;

  67:     right: 0;

  68: }

  69:

  70: ul li textarea {

  71:     font-family: 'Chewy', arial, sans-serif;

  72:     background: rgba(0, 0, 0, 0); /* transparent background */

  73:     resize: none;

  74:     padding: 3px;

  75:     border-style: none;

  76: }

  77:

  78: .note-title {

  79:     font-size: 140%;

  80:     font-weight: bold;

  81:     height: 30px;

  82:     width: 70%;

  83: }

  84:

  85: .note-content {

  86:     font-size:120%;

  87:     height: 100px;

  88:     width: 95%;

  89: }

  90:

  91: ul li:nth-child(even) div {

  92:   -o-transform:rotate(4deg);

  93:   -webkit-transform:rotate(4deg);

  94:   -moz-transform:rotate(4deg);

  95:   position:relative;

  96:   top:5px;

  97: }

  98:

  99: ul li:nth-child(3n) div {

 100:   -o-transform:rotate(-3deg);

 101:   -webkit-transform:rotate(-3deg);

 102:   -moz-transform:rotate(-3deg);

 103:   position:relative;

 104:   top:-5px;

 105: }

 106:

 107: ul li:nth-child(5n) div {

 108:   -o-transform:rotate(5deg);

 109:   -webkit-transform:rotate(5deg);

 110:   -moz-transform:rotate(5deg);

 111:   position:relative;

 112:   top:-10px;

 113: }

 114:

 115: ul li div:hover, ul li div:focus {

 116:   -moz-box-shadow:10px 10px 7px rgba(0,0,0,.7);

 117:   -webkit-box-shadow: 10px 10px 7px rgba(0,0,0,.7);

 118:   box-shadow:10px 10px 7px rgba(0,0,0,.7);

 119:   -webkit-transform: scale(1.25);

 120:   -moz-transform: scale(1.25);

 121:   -o-transform: scale(1.25);

 122:   position:relative;

 123:   z-index:5;

 124: }

 125:

 126: /* define 3 different colours for the notes */

 127: ul li div.colour1 {

 128:     background: #ffc;

 129: }

 130: ul li div.colour2 {

 131:     background: #cfc;

 132: }

 133: ul li div.colour3 {

 134:     background: #ccf;

 135: }

There’s a couple of things to note here:

  • the textarea element has a transparent background
  • the textarea element is not resizable
  • the textarea element has a placeholder text

Here’s what the page looks like now:

image

Adding interactions with Javascript

Adding a new note

In the CSS above, you can see that I had defined three CSS class:

   1: /* define 3 different colours for the notes */

   2: ul li div.colour1 {

   3:     background: #ffc;

   4: }

   5: ul li div.colour2 {

   6:     background: #cfc;

   7: }

   8: ul li div.colour3 {

   9:     background: #ccf;

  10: }

when we create a new note, all we need to do to randomize its colour is to add one of these classes to the element randomly.

Here’s the event handler for the ‘new’ image button:

   1: $(document).ready(function() {

   2:     notes = $("#notes"); // get references to the 'notes' list

   3:     ...

   4:     // clicking the 'New Note' button adds a new note to the list

   5:     $("#btnNew").click(function() {

   6:         addNewNote();

   7:     });

   8:     ...

   9: });

  10:

  11: //  adds a new note to the 'notes' list

  12: function addNewNote(class, title, content) {

  13:     // if class is not specified, use a random colour class

  14:     if (!class) {

  15:         class = "colour" + Math.ceil(Math.random() * 3);

  16:     }

  17:

  18:     // add a new note to the end of the list

  19:     notes.append("<li><div class='" + class + "'>" +

  20:                  "<textarea class='note-title' placeholder='Untitled' maxlength='10'/>" +

  21:                  "<textarea class='note-content' placeholder='Your content here'/>" +

  22:                  "<img class='hide' src='images/close.png'/>" +

  23:                  "</div></li>");

  24:

  25:     // get the new note that's just been added and attach the click event handler to its close button

  26:     var newNote = notes.find("li:last");

  27:     newNote.find("img").click(function() {

  28:         newNote.remove();

  29:     });

  30:

  31:     // hook up event handlers to show/hide close button as appropriate

  32:     addNoteEvent(newNote);

  33:

  34:     // if a title is provided then set the title of the new note

  35:     if (title) {

  36:         // get the title textarea element and set its value

  37:         newNote.find("textarea.note-title").val(title);

  38:     }

  39:

  40:     // if a content is provided then set the content of the new note

  41:     if (content) {

  42:         // get the content textarea element and set its value

  43:         newNote.find("textarea.note-content").val(content);

  44:     }

  45: }

  46:

  47: function addNoteEvent(noteElement) {

  48:     noteElement.focus(function () {

  49:         $(this).find(".img").removeClass("hide");

  50:     }).hover(function() {

  51:         $(this).find("img").removeClass("hide");

  52:     }, function () {

  53:         $(this).find("img").addClass("hide");

  54:     });

  55: }

as you can see, this event handler calls the addNewNote function which is responsible for adding a new note to the notes unordered list and sets its class, title or content depending on the arguments. In this particular usage it’ll pick one of the three colour classes randomly and add the class to the new note.

Once a note has been added, I then get a reference to it and create an event handler for its close button so that when its clicked it’ll remove this note from the DOM.

The addNoteEvent function is then charged with adding the focus and hover in/out event handlers to the note to show and hide the close button so that it’s only shown when the note is focused/hovered on.

Saving the notes

I have previously cover the use of HTML5’s local and session storage in this post already, what I’m doing here is very similar but a little more involved.

When the ‘save’ button is clicked, I need to inspect each of the notes and remember the class, title text (remember, you can’t get the text that’s been entered into the textarea by getting the HTML of the element) and content text in a bespoke object, the resulting array of objects is then JSON serialized using Douglas Crockford‘s Json.js library. It this is JSON string that’s saved into the local storage.

On the other end, when the page has been loaded and the DOM event fired (i.e. when JQuery.ready fires) I will need to check if the JSON string is in the local storage, if it is, then deserialize the string back to an object array and add the notes back in in the same order:

   1: var notes;

   2: var count = 0;

   3:

   4: $(document).ready(function() {

   5:     notes = $("#notes"); // get references to the 'notes' list

   6:

   7:     // load notes from local storage if one's available

   8:     var storedNotes = localStorage.getItem("notes");

   9:     if (storedNotes)

  10:     {

  11:         // passes the stored json back into an array of note objects

  12:         var notesArray = JSON.parse(storedNotes);

  13:         count = notesArray.length;

  14:

  15:         for (var i = 0; i < count; i++) {

  16:             var storedNote = notesArray[i];

  17:             addNewNote(storedNote.Class, storedNote.Title, storedNote.Content);

  18:         }

  19:     }

  20:

  21:     ...

  22:

  23:     // clicking the 'Save' button saves the state of the notes board to the local storage                

  24:     $("#btnSave").click(function() {

  25:         var notesArray = new Array();

  26:

  27:         // for each of the notes add a bespoke note object to the array

  28:         notes.find("li > div").each(function (i, e) {

  29:             // save the class attribute of the div, as well as the text for the title and content text areas

  30:             var colourClass = $(e).attr("class");

  31:             var title = $(e).find("textarea.note-title");

  32:             var content = $(e).find("textarea.note-content");

  33:

  34:             notesArray.push({ Index: i, Title: title.val(), Content: content.val(), Class: colourClass });

  35:         });

  36:

  37:         // json encode it

  38:         var jsonStr = JSON.stringify(notesArray);

  39:

  40:         // and save the json string into local storage

  41:         localStorage.setItem("notes", jsonStr);

  42:

  43:         // info the user it's done

  44:         alert("Notes saved");

  45:     });

  46:

  47:     // add a note to the list if there aren't any

  48:     if (count == 0)

  49:     {

  50:         $("#btnNew").click();

  51:     }

  52: });

Demo

You can try out the full demo at:

http://stickynote.theburningmonk.com

or using the short link:

http://LNK.by/fhbba

Enjoy!

UPDATE 27/02/2011:

Thanks to Joey Beechey for pointing out to me in the comment that the demo doesn’t work in Safari and after a bit of digging I found that the problem is with the addNewNote function which has a parameter named ‘class‘ which is a reserved word. I found another reference of this problem here too, which mentions the same problem but with the variable name ‘abstract‘ instead!

I’ve also put the whole thing into a zip file which you can download from here, so feel free to play around with it yourself!

References:

Douglas Crockford’s JSON-js project on github

John Resig’s blog post which includes some handy examples on how to use the JSON-js library

Original tutorial which inspired this blog post

Having fun with HTML5 – Local Storage and Session Storage

HTML5 includes two new ways to store data on the client – local storage and session storage. Local storage has no time limit on how long the data should be kept around, session storage on the other hand (as the name suggests) stores data for only one session.

Traditionally you can store data on the client using cookies, but they are not suited for storing large amounts of data because everything gets sent back to the server on every request and hence slows down the roundtrip and making the user experience less enjoyable.

Using the new storage methods, data is only passed on when asked for and it’s therefore possible to store large amounts of data without slowing down the site. The data is also sandboxed and a website can only access data stored by itself, you can use javascript to set and get data from the relevant storage.

Browser Support Detection

A browser that supports HTML5’s local storage will have a localStorage property on the global window object, you can write your own function to check for the existence of and validity (not null) of the localStorage property, or use a third-party library like Modernizr.

Modernizr is an open source, light weight javascript library that detects support for many HTML5 and CSS3 features, when it runs it creates a global object called Modernizr. To check for local storage support, all you need to do is to check the boolean flag for local storage:

if (Modernizr.localstorage) {
    // browser supports local storage
}
else {
    // browser doesn't support local storage
}

or similarly for session storage:

if (Modernizr.sessionstorage) {
    // browser supports local storage
}
else {
    // browser doesn't support local storage
}

API

The full API specification for the localStorage and sessionStorage objects can be found here. At the time of writing, this is how the common Storage interface look:

image

Demo

I’ve put together a quick demo here to illustrate how to detect and use the local and session storage to get, set, remove and clear stored items (well, basically, covers each of the available methods on the Storage interface above).

The page itself is simple and crude, just a couple of <div> and most importantly two tables which I use to show all the key value pairs stored in the local and session storage:

image

Page Load

When the page finishes loading, I call the Modernizr object to find out if the current browser supports local and/or session storage, if not, hide the relevant <div> elements so the tables are not even shown on the page:

$(document).ready(function () {
    // hide the storage divs if browser doesn't support local storage or session storage

    if (Modernizr.localstorage && Modernizr.sessionstorage) {
        $("#detectionSpan").html("Your browser supports both local and session storage");
    } else {
        if (!Modernizr.localstorage) {
            $("#detectionSpan").html("Your browser doesn't support local storage");
            $("#localStorageDiv").hide();
        } else {
            $("#detectionSpan").html("Your browser doesn't support session storage");
            $("#sessionStorageDiv").hide();
        }
    }
    showKeys();
});
Populate Tables

The showKeys() function populates the localTable and sessionTable tables with the keys in the corresponding storage if and only if the storage type is supported by the browser:

// show the keys currently held in the local and session storage
function showKeys() {
    if (Modernizr.localstorage) {
        showStorageKeys("local", "#localTable");
    }
    if (Modernizr.sessionstorage) {
        showStorageKeys("session", "#sessionTable");
    }
}

// show the keys currently held in the specified type of storage in the specified table
function showStorageKeys(type, table) {
    // get the specified type of storage, i.e. local or session
    var storage = window[type + 'Storage'];

    // remove the rows in the specified table before we start
    $(table + " > tbody > tr").remove();

    // loop through the existing keys in the storage and add them to the TBODY element as rows
    for (var i = 0; i < storage.length; i++) {
        var key = storage.key(i);
        var value = storage.getItem(key);
        $(table + " > tbody:last")
            .append("<tr><td>" + key + "</td>" +
                    "<td>" + value + "</td>" +
                    "<td><input type='submit' value='Remove' onclick='removeItem(\"" + type + "\", \"" + key + "\")'/></td></tr>");
    }
}
Introducing the new placeholder attribute

You might have noticed that the two text boxes had placeholder text similar to that familiar search box in Firefox:

text boxes with place holder text Firefox search box

This is done using HTML5’s placeholder attribute for the <input> tag:

<input id="keyText" placeholder="Enter key"/>
<input id="valueText" placeholder="Enter value"/>

Nice and easy, eh? ;-)

Setting an item

To add a new key value pair or update the value associated with an existing key, you just have to call the setItem method on the intended storage object:

// adds a new key to both local and session storage
function setKey() {
    var key = $("#keyText").val();
    var value = $("#valueText").val();

    if (Modernizr.localstorage) {
        localStorage.setItem(key, value);
    }
    if (Modernizr.sessionstorage) {
        sessionStorage.setItem(key, value);
    }
    showKeys();
}
Removing an item

Earlier in the showStorageKeys(type, table) function, I added a row to the relevant table for each key value pair in the storage including a button with a handler for the onclick event. The handlers are created with the correct storage type (“local” or “session”) and key for the current row baked in already so that they will call the removeItem(type, key) function with the correct parameters:

// removes an item with the specified key from the specified type of storage
function removeItem(type, key) {
    // get the specified type of storage, i.e. local or session
    var storage = window[type + 'Storage'];
    storage.removeItem(key);
    showKeys();
}
Clearing all items

Finally, the ‘”Clear” buttons underneath the tables call the clearLocalKeys() and clearSessionKeys() function to remove all the key value pairs in the corresponding storage:

function clearLocalKeys() {
    clearKeys("local");
}

function clearSessionKeys() {
    clearKeys("session");
}

// clear all the held keys in the specified type of storage
function clearKeys(type) {
    // get the specified type of storage, i.e. local or session
    var storage = window[type + 'Storage'];

    // clear the keys
    storage.clear();

    showKeys();
}

So that covers all the interesting bits about the demo and all and all pretty straight forward and easy to implement, admittedly I’m a javascript novice so if you feel any of this could be done better please feel free to point out to me!