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:
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' list3: ...4: // clicking the 'New Note' button adds a new note to the list5: $("#btnNew").click(function() {6: addNewNote();7: });8: ...9: });10:11: // adds a new note to the 'notes' list12: function addNewNote(class, title, content) {13: // if class is not specified, use a random colour class14: if (!class) {15: class = "colour" + Math.ceil(Math.random() * 3);16: }17:18: // add a new note to the end of the list19: 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 button26: 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 appropriate32: addNoteEvent(newNote);33:34: // if a title is provided then set the title of the new note35: if (title) {36: // get the title textarea element and set its value37: newNote.find("textarea.note-title").val(title);38: }39:40: // if a content is provided then set the content of the new note41: if (content) {42: // get the content textarea element and set its value43: 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' list6:7: // load notes from local storage if one's available8: var storedNotes = localStorage.getItem("notes");9: if (storedNotes)10: {11: // passes the stored json back into an array of note objects12: 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 storage24: $("#btnSave").click(function() {25: var notesArray = new Array();26:27: // for each of the notes add a bespoke note object to the array28: 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 areas30: 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 it38: var jsonStr = JSON.stringify(notesArray);39:40: // and save the json string into local storage41: localStorage.setItem("notes", jsonStr);42:43: // info the user it's done44: alert("Notes saved");45: });46:47: // add a note to the list if there aren't any48: if (count == 0)49: {50: $("#btnNew").click();51: }52: });
Demo
You can try out the full demo at:
or using the short link:
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