Simple Javascript Notice Board (Free Download)

Welcome to a tutorial and sharing on how to create a Javascript notice board. Yep, there are already a couple of examples on the Internet. So I figured to add a little more extra – This one uses pure Javascript, has no reliance on third-party libraries, and is also a progressive web app. Read on!

ⓘ I have included a zip file with all the source code at the start of this tutorial, so you don’t have to copy-paste everything… Or if you just want to dive straight in.

 

 

TABLE OF CONTENTS

Download & Demo JS Board Useful Bits & Links
The End

 

DOWNLOAD & DEMO

Firstly, here is the download link to the example code as promised.

 

QUICK NOTES

  • Download and unzip into your HTTP folder.
  • Technically, it should work with both file:// and http://.
  • But for it to act as an “installable app” and “progressive web app”, http://localhost and https://your-site.com is required.
If you spot a bug, feel free to comment below. I try to answer short questions too, but it is one person versus the entire world… If you need answers urgently, please check out my list of websites to get help with programming.

 

EXAMPLE CODE DOWNLOAD

Click here to download all the example source code, I have released it under the MIT license, so feel free to build on top of it or use it in your own project.

 

JAVASCRIPT NOTICE BOARD DEMO


 

Enter text and “add” to create a new note.
Drag-and-drop a note to rearrange.
Double click on a note to edit.
Hover over a note and click on “X” to delete it.

 

 

JAVASCRIPT NOTICE BOARD

All right, let us now get into more details of how the Javascript notice board works.

 

PART 1) THE HTML

notice-board.html
<!-- (A) ADD/UPDATE NOTE -->
<form id="noteform">
  <input type="text" id="notetxt" required disabled/>
  <input type="submit" id="notego" value="Add" disabled/>
</form>
 
<!-- (B) NOTICE BOARD -->
<div id="board"></ul>

The HTML should be pretty self-explanatory. There are only 2 sections –

  1. A simple form to add/update notes.
  2. The notice board itself. The notes will be generated with Javascript.

 

PART 2) THE JAVASCRIPT

2A) INITIALIZE & QUICK OVERVIEW

notice-board.js
var board = {
  // (A) PROPERTIES
  notes : [],   // current list of notes
  hwrap : null, // html notice board wrapper
  hform: null,  // html add/update note form
  hadd : null,  // html add/update note text field
  hgo : null,   // html add/update note button
  hsel : null,  // current note being dragged or edited

  // (B) INITIALIZE NOTICE BOARD
  init : () => {
    // (B1) GET HTML ELEMENTS
    board.hwrap = document.getElementById("board");
    board.hform = document.getElementById("noteform");
    board.hadd = document.getElementById("notetxt");
    board.hgo = document.getElementById("notego");

    // (B2) LOAD & DRAW HTML NOTES
    let data = localStorage.getItem("notes");
    if (data !== null) { for (let n of JSON.parse(data)) {
      board.draw(n);
    }}

    // (B3) ENABLE ADD NEW NOTE
    board.hform.onsubmit = () => { return board.add(); };
    board.hadd.disabled = false;
    board.hgo.disabled = false;
  },

  ...
}
window.addEventListener("DOMContentLoaded", board.init);
  • (A) Captain Obvious at your service, var board contains all the mechanics of the notice board.
  • The idea is to save the data in the local storage. So that it will not disappear when the user closes the browser.
  • (B) On page load, board.init() will run.
    • (B1) Simply fetches all the related HTML elements.
    • (B2) Load the saved data from the local storage. The data is nothing but a flat JSON encoded string of ["NOTE", "NOTE", ...]
    • (B3) Enable the add/update note HTML form.

 

 

2B) DRAW HTML NOTE

notice-board.js
// (C) HELPER - CREATE HTML NOTE
draw : (note, first) => {
  // (C1) CREATE HTML NOTE
  let div = document.createElement("div");
  div.className = "note";
  div.draggable = true;
  div.innerHTML = `<div class="del" onclick="board.del(this.parentElement)">X</div> <div class="txt">${note}</div>`;
 
  // (C2) ON DRAG START - ADD DROPPABLE HINTS
  div.ondragstart = (e) => {
    board.hsel = e.target;
    for (let n of board.notes) {
      n.classList.add("drag");
      if (n != board.hsel) { n.classList.add("hint"); }
    }
  };
 
  // (C3) ON DRAG ENTER - HIGHLIGHT DROPZONE
  div.ondragenter = (e) => {
    if (div != board.hsel) { div.classList.add("active"); }
  };
 
  // (C4) DRAG LEAVE - REMOVE HIGHLIGHT DROPZONE
  div.ondragleave = (e) => {
    div.classList.remove("active");
  };
 
  // (C5) DRAG END - REMOVE ALL HIGHLIGHTS
  div.ondragend = (e) => { for (let n of board.notes) {
    n.classList.remove("drag");
    n.classList.remove("hint");
    n.classList.remove("active");
  }};
 
  // (C6) DRAG OVER - PREVENT DEFAULT "DROP", SO WE CAN DO OUR OWN
  div.ondragover = (e) => { e.preventDefault(); };
 
  // (C7) ON DROP - REORDER NOTES & SAVE
  div.ondrop = (e) => {
    // (C7-1) PREVENT DEFAULT BROWSER DROP ACTION
    e.preventDefault();
 
    if (e.target != board.hsel) {
      // (C7-2) GET CURRENT & DROPPED POSITIONS
      let idrag = 0, // index of currently dragged
      idrop = 0; // index of dropped location
      for (let i=0; i<board.notes.length; i++) {
        if (board.hsel == board.notes[i]) { idrag = i; }
        if (e.target == board.notes[i]) { idrop = i; }
      }
 
      // (C7-3) REORDER HTML NOTES
      if (idrag > idrop) {
        board.hwrap.insertBefore(board.hsel, e.target);
      } else {
        board.hwrap.insertBefore(board.hsel, e.target.nextSibling);
      }
 
      // (C7-4) REORDER & SAVE NOTES ARRAY
      board.save();
    }
  };
 
  // (C8) DOUBLE CLICK TO EDIT NOTE
  div.ondblclick = () => {
    // (C8-1) SELECTED NOTE
    board.hsel = div.querySelector(".txt");
 
    // (C8-2) LOCK - NO DRAG NO DELETE WHILE EDITING
    for (let n of board.notes) { n.classList.add("lock"); }
 
    // (C8-3) UPDATE NOTE FORM
    board.hadd.value = board.hsel.innerHTML;
    board.hgo.value = "Update";
    board.hform.onsubmit = () => { return board.update(); };
    // board.hadd.focus();
    board.hadd.select();
  };
 
  // (C9) DONE - PUSH INTO ARRAY & ATTACH TO CONTAINER
  if (first) {
    board.notes.unshift(div);
    board.hwrap.insertBefore(div, board.hwrap.firstChild);
  } else {
    board.notes.push(div);
    board.hwrap.appendChild(div);
  }
},

Right, not going to explain line-by-line. But this function is as it is, a helper function used to generate <div class="note"> during the init phase, and also when the user adds a new note.

  • (C1) Creates the <div class="note">.
  • (C2 to C7) The drag-and-drop mechanism.
  • (C8) Double click to edit note.
  • (C9) Attach the note to <div id="board">.

 

 

2C) ADD UPDATE DELETE NOTES

notice-board.js
// (D) ADD NEW NOTE
add : () => {
  board.draw(board.hadd.value, true);
  board.hadd.value = "";
  board.save();
  return false;
},
 
// (E) DELETE NOTE
del : (note) => { if (confirm("Delete note?")) {
  note.remove();
  board.save();
}},
 
 // (F) UPDATE NOTE
update : () => {
  // (F1) UPDATE NOTE
  board.hsel.innerHTML = board.hadd.value;
  board.hadd.value = "";
  board.hgo.value = "Add";
 
  // (F2) "RESTORE" ADD NOTE FORM
  board.hform.onsubmit = () => { return board.add(); };
 
  // (F3) SAVE
  board.save();
 
  // (F4) UNLOCK - ALLOW DRAG & DELETE
  for (let n of board.notes) { n.classList.remove("lock"); }
  return false;
},
 
 // (G) UPDATE & SAVE NOTES
save : () => {
  // (G1) REORDER NOTES ARRAY
  let data = [];
  board.notes = [];
  for (let n of board.hwrap.getElementsByClassName("note")) {
    board.notes.push(n);
    data.push(n.querySelector(".txt").innerHTML);
  }
 
  // (G2) SAVE DATA
  localStorage.setItem("notes", JSON.stringify(data));
}

These should be pretty self-explanatory once you catch the gist.

  • (D) Add a new note – Simply use board.draw() to create a new <div class="note">, then update the local storage.
  • (E) Delete note – Remove the selected <div class="note">, update the local storage.
  • (F) Update note – Update the text of the selected <div class="note">, update the local storage.
  • (G) To save the notes – Get all <div class="note">, extract the text, and save it into the local storage.

 

 

3) PROGRESSIVE WEB APP

3A) HTML META

notice-board.html
<head>
  <!-- MANIFEST & WORKER -->
  <link rel="manifest" href="manifest.json">
  <script>
  if ("serviceWorker" in navigator) {
    navigator.serviceWorker.register("worker.js");
  }
  </script>
</head>

With the above HTML and Javascript, we already have a working notice board. This step is optional, to create an installable and offline-capable notice board. Some people think that creating a PWA is difficult, but it is really not that bad.

 

3B) SERVICE WORKER

worker.js
// (A) FILES TO CACHE
const cName = "js-notice",
cFiles = [
  // (A1) STATIC FILES
  "notice-board.html",
  "notice-board.css",
  "notice-board.js",

  // (A2) IMAGES
  "images/cork.jpg",
  "images/favicon.png",
  "images/icon-512.png"
];

// (B) CREATE/INSTALL CACHE
self.addEventListener("install", (evt) => {
  evt.waitUntil(
    caches.open(cName)
    .then((cache) => { return cache.addAll(cFiles); })
    .catch((err) => { console.error(err) })
  );
});

// (C) LOAD FROM CACHE FIRST, FALLBACK TO NETWORK IF NOT FOUND
self.addEventListener("fetch", (evt) => {
  evt.respondWith(
    caches.match(evt.request)
    .then((res) => { return res || fetch(evt.request); })
  );
});

For those who have not heard of “service worker”, it is basically a Javascript that we “install” to run in the background.

  • (A & B) Save the notice board HTML, CSS, and Javascript into a storage cache. 
  • (C) Load the files from the cache if it exists, and fall back to the network if not found.

In other words, effectively turning this notice board into an offline app.

 

 

3C) MANIFEST

manifest.json
{
  "short_name": "JS Board",
  "name": "JS Notice Board",
  "icons": [{
    "src": "images/favicon.png",
    "sizes": "64x64",
    "type": "image/png"
  }, {
    "src": "images/icon-512.png",
    "sizes": "512x512",
  "type": "image/png"
  }],
  "start_url": "notice-board.html",
  "scope": "/",
  "background_color": "white",
  "theme_color": "white",
  "display": "standalone"
}

Lastly, this manifest file is self-explanatory… The app name, icons, preferences, etc…

 

USEFUL BITS & LINKS

That’s all for the tutorial, and here is a small section on some extras and links that may be useful to you.

 

COMPATIBILITY CHECKS

This example will work on some of the older browsers. But for the “installable” to work, a modern “Grade A” browser is required.

 

LINKS & REFERENCES

 

THE END

Thank you for reading, and we have come to the end. I hope that it has helped you to better understand, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!

Leave a Comment

Your email address will not be published.