Jana Moudrá edited this page Feb 2, 2015 · 14 revisions

Notes Application Code Lab

The goal of this code lab is to create note-taking application in Dart. We create simple application which can add notes, delete selected note or all notes in the list and we also try local storage to save the notes into the browser.

You will learn:

  • how to write applications in Dart
  • how to work with DOM manipulation in Dart
  • how to work with JSON
  • how to save data to local storage
  • how to compile the application into the JavaScript

Download the prepared project from Github and open it in Dart editor. We prepared everything important for you so you don't have to solve any CSS styling and HTML structure, and you can directly explore how to write Dart application.

Project is divided into steps according to this Code Lab. Try to run the application from step_06/ and see how it works.

Notes application

Step 1 - Empty project

Start in step_01/. This is a basic project with HTML template and styles and only printing of “Hello World!” in Dart code.

We update this step with functionality and at the end we will have nice notes application.

Step 2 - Adding note

In this step we add the functionality of adding new note. At the beginning we add input form and we watch changes in its value. Then we take the new value and create the note from it as new LI element appended to the UL list.

Body part of the HTML (file dart_codelab.html) looks like:

<div id="note_app_container" class="panel panel-primary">
      <div class="panel-heading">
        <h1>Notes app</h1>
        <!-- Here will be delete all button -->
      </div>
      <div class="panel-body">        
        <!-- Here will be input form -->
        <!-- This will be list with notes -->
      </div>      
</div>

Add this input into div with class panel-body instead of comment <!-- Here will be input form -->:

<input type="text" id="note_add_input" class="form-control" placeholder="Enter note"/>

To watch changes on the input in Dart you have to retrieve the input in the code and set listener to it. We also need import of the library dart:html because we work with DOM. Add this to the top of your .dart file:

import 'dart:html';

Retrieve your input from HTML and set on change listener to it. Save it into the variable outside of the method because we also work with the input later. Your code looks like like:

import 'dart:html';

InputElement noteInput;

void main() {
  noteInput = querySelector("#note_add_input");
  noteInput.onChange.listen(createNote); 
}

Now we need to implement createNote() method which is called when input changes. In this method we want to get the value from input and if it is not empty (we don't want to have empty notes), we create new note. We also create new method addNote() with parameter of the note text used for creating of new note:

void createNote(Event event) {
  String noteText = noteInput.value;
  if (noteText.isNotEmpty) {
    addNote(noteText);
  }
}

Before we implement the addNote() method it is needed to add UL element where all notes as LI elements are created. Add this instead of comment <!-- This will be list with notes -->:

<ul id="notes_wrapper"></ul>

Implement the addNote() method. Here you create new LI element with text of the note and append it to the new UL element with id notes_wrapper. Before we can start, we have to also retrieve the notes_wrapper in the main() function and save it to variable for later use. Add this to main():

notesWrapper = querySelector("#notes_wrapper");

where notesWrapper is variable defined outside of the function:

UListElement notesWrapper;

Now when we have our UL element retrieved, we can get back to addNote() method and implement adding of LIs to it.

void addNote(String noteText) {  
  LIElement note = new LIElement();
  note.text = noteText; 
  notesWrapper.append(note);
}

When we run the app, we see that the app has small problem. When we create the new note we don't reset the form so there is still old value written. We fix this resetting the value of input. And also we extract the creating of the new LI element into new method called addNoteElement() to make it more clean. The addNote() looks like:

void addNote(String noteText) {  
  addNoteElement(noteText);
  noteInput.value = "";
}

And addNoteElement() looks like:

void addNoteElement(String noteText) {
  LIElement note = new LIElement();
  note.text = noteText; 
  notesWrapper.append(note);
}

Run the app and see if it works.

Step 3 - Delete all button

We add the delete all button which deletes all notes from the list.

Add the delete all button into HTML instead of the comment <!-- Here will be delete all button -->:

<button id="notes_delete_all" class="btn btn-danger">Delete all</button>

Retrieve the button from the main() function and save it to the variable. Also set on click listener on it. Add this to the main() function:

notesDeleteAll = querySelector("#notes_delete_all");
notesDeleteAll.onClick.listen(deleteAll);

The variable is:

ButtonElement notesDeleteAll;

In deleteAll() method we want to implement deleting of all notes (described as LIs) from the list (UL). The deleteAll() looks like:

void deleteAll(Event event) {
  notesWrapper.children.clear();
}

The list takes all its children and deletes them.

Run the app and test the delete all button.

Step 4 - Delete selected note

Now we add delete button for every note which deletes the selected note.

When is the right time to add delete button for note? When we are creating it. Look in the method addNoteElement() and add there call to a new method addDeleteNoteButton(note) which is called right before appending of note to the list of notes. addNoteElement() looks like:

void addNoteElement(String noteText) {
  LIElement note = new LIElement();
  note.text = noteText; 
  addDeleteNoteButton(note);
  notesWrapper.append(note);
}

Implement the addDeleteNoteButton() method. In this method create new button, set text to “Delete”, set on click listener and append it to the note. We are also adding some classes because of styling:

void addDeleteNoteButton(LIElement note) {
  ButtonElement deleteButton = new ButtonElement();
  deleteButton
    ..text = "Delete"
    ..classes.add("btn btn-danger btn-sm")
    ..onClick.listen(deleteNote);
  note.append(deleteButton);
}

Implement the method deleteNote() which is called when the delete button is clicked. We need to delete the note LI element when the on click on button in it is called. How to get to it? Because the LI element is direct parent of the button, we can get it through calling .parent. deleteButton() method looks like this:

void deleteNote(Event event) {
  LIElement actualNote = ((event.target as ButtonElement).parent as LIElement);  
  actualNote.remove();  
}

Run the app and test the delete button when hovering over notes.

Step 5 - Array with all notes

In the next step we save the notes to the local storage. But we need to serialize our notes to the JSON format before we can save it. This is the reason why we are holding array of actual notes to have the possibility to serialize this array easily into and from JSON.

Start with creating of the class for our note. Notes are represented as instances of class Note in the array. It has one attribute - text and constructor:

class Note {
  String text;
  
  Note(this.text);
}

Create array of notes:

List notes;

Inicialize the array in main() function:

notes = new List();

Now we have the array for notes but it is always empty. So we need to update it after every operation like add and delete. Add the note into the array in the addNote() method. The method looks like this:

void addNote(String noteText) { 
  notes.add(new Note(noteText));
  addNoteElement(noteText);
  noteInput.value = "";
}

Remove all notes from array in deleteAll() method. The method looks like this:

void deleteAll(Event event) {
  notes.clear();
  notesWrapper.children.clear();
}

Remove the actual note from array when deleting it in the deleteNote(). This is little tricky because we need to find out the index of the note in the list because it corresponds with the index in the array of notes. Anyway we can find the index of note in the list. Pay attention to get the index before the note is deleted from the list. The deleteNote() method looks like this:

void deleteNote(Event event) {
  LIElement actualNote = ((event.target as ButtonElement).parent as 
    LIElement);  
  int actualIndex = notesWrapper.children.indexOf(actualNote);
  notes.removeAt(actualIndex);  
  actualNote.remove();  
}

Step 6 - Save to local storage

In this step we save our notes into local storage so the notes survive page refresh. We use the local storage for this where we save the notes as JSON String.

Our JSON looks like this:

{
  "notes" : [
    {
      "text" : "Update codelab"
    },
    {
      "text" : "Do shopping"
    },
    {
      "text" : "Remember to call friend"
    }
  ]
}

Because we work with JSON, we need to add import for dart:convert library. Add this:

import 'dart:convert';

We write two static methods for serialization into JSON and deserialization from JSON into our Note class. The method toJson() gets the array in parameter and returns JSON String. Implementation looks like this:

static String toJson(List notes) {
  Map notesJsonMap = new Map();
  List notesJsonList = new List();
    
  for (Note note in notes) {
    Map noteJsonMap = new Map();
    noteJsonMap["text"] = note.text;
    notesJsonList.add(noteJsonMap);
  }
    
  notesJsonMap["notes"] = notesJsonList;
  return JSON.encode(notesJsonMap);
}

The method fromJson() gets the JSON String in parameter and converts it into the array of notes:

static List fromJson(String jsonString) {
  List notes = new List();    
  Map notesJsonMap = JSON.decode(jsonString);
  List notesJsonList = notesJsonMap["notes"];
    
  for (Map noteJsonMap in notesJsonList) {
    Note note = new Note(noteJsonMap["text"]);
    notes.add(note);
  }
  return notes;
}

Now we have methods for converting our array to and from JSON and it is a good time to start using local storage. We write two methods: for saving into local storage and for retrieving from local storage. First we create constant which is used as a key for saving and retrieving from local storage:

final String NOTES_LIST = "notesList";

Method for saving into local storage looks like:

void saveToLocalStorage() {
  window.localStorage[NOTES_LIST] = Note.toJson(notes);
}

When retrieving from local storage we need to check if there is something saved. If there is something saved, we generate notes from it and show them. Showing of all notes from list is implemented in method addNotes(). If there is nothing in local storage, we initialize empty array. Method for retrieving from local storage looks like:

void loadFromLoacalStorage() {
  if (window.localStorage[NOTES_LIST] != null) {
    notes = Note.fromJson(window.localStorage[NOTES_LIST]);
    addNotes();
  } else {
    notes = new List();
  }
}

Now we need to implement addNotes() method. This method cycles through all notes from the array and creates them as LI elements.

void addNotes() {
  for (Note note in notes) {
    addNoteElement(note.text);
  }
}

We have implemented saving into local storage and we need to use this. After each operation like add and delete we call saveToLocalStorage().

void addNote(String noteText) {  
  …
  saveToLocalStorage();
}

void deleteNote(Event event) {
  … 
  saveToLocalStorage();
}

void deleteAll(Event event) {
  … 
  saveToLocalStorage();
}

We have also implemented retrieving from local storage and we need to also use this. We retrieve from local storage in main() function. Replace notes = new List(); with calling of method loadFromLoacalStorage(). Method main() looks like:

void main() {
  noteInput = querySelector("#note_add_input");
  noteInput.onChange.listen(createNote); 
  
  notesWrapper = querySelector("#notes_wrapper");
  
  notesDeleteAll = querySelector("#notes_delete_all");
  notesDeleteAll.onClick.listen(deleteAll); 
  
  loadFromLoacalStorage();
}

Run the app, add some notes and try to refresh browser.

Step 7 - Compile to JavaScript

Run pub build from command line to generate JavaScript files and open the application from generated files.

See how it works.

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.