{"id":1932,"date":"2018-02-26T05:28:06","date_gmt":"2018-02-26T05:28:06","guid":{"rendered":"http:\/\/websterart.com\/wordpress\/?p=1932"},"modified":"2018-02-27T17:39:01","modified_gmt":"2018-02-27T17:39:01","slug":"to-do-app-in-html5","status":"publish","type":"post","link":"https:\/\/websterart.com\/wordpress\/2018\/02\/to-do-app-in-html5\/","title":{"rendered":"to do app in html5"},"content":{"rendered":"<p>I spent a full day trying to debug an <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Learn\/JavaScript\/Client-side_web_APIs\/Client-side_storage\" target=\"_blank\" rel=\"noopener\">indexedDB JavaScript\u00a0lesson<\/a>. It would work on my iPhone, and on <a href=\"https:\/\/codepen.io\/markhwebster\/pen\/QQBXLd\" target=\"_blank\" rel=\"noopener\">codepen<\/a>, but nowhere else. It wouldn&#8217;t \u00a0work at all on my MacBook, except for codepen.com. I knew my code was clean because typing errors are always the first thing I check. And the way it was behaving, I could tell it was some kind of security blockage either on the hosting servers or in the browsers.<\/p>\n<p>While searching for an answer I came across another <a href=\"http:\/\/blog.teamtreehouse.com\/create-your-own-to-do-app-with-html5-and-indexeddb\" target=\"_blank\" rel=\"noopener\">free online tutorial<\/a>, and this one worked. I don&#8217;t know why it worked because it was structured much the same, though with subtle differences. Anyway I was able to combine stuff I&#8217;d learned in both tutorials to make a pretty sweet little \u00a0pure \u00a0HTML5 and vanilla JavaScript \u00a0note taking app.<\/p>\n<p>Once I had it running I realized it was missing the drag and drop functionality I&#8217;ve come to expect in apps like <a href=\"https:\/\/www.wunderlist.com\" target=\"_blank\" rel=\"noopener\">wunderlist<\/a>. After a quick google search, I found a tutorial that built a <a href=\"https:\/\/codepen.io\/markhwebster\/pen\/bLxmyd\">drag and drop list<\/a>. Sadly, that one has a problem on mobile devices. You can drag the list items on a computer, but not on a phone. Phones have a different set of responses and the \u00a0eventListeners are set up completely different from computers.<\/p>\n<p>But more searching led me to <a href=\"https:\/\/mobiforge.com\/design-development\/touch-friendly-drag-and-drop\" target=\"_blank\" rel=\"noopener\">this cool tutorial page with links<\/a> out to the slip.js library.<\/p>\n<p>And finally I had a very nice little &#8220;todo&#8221; note taker app. It&#8217;s still not hooked up to a database&#8230;that may be coming. But it&#8217;s actually quite useable. There are 400 lines of code, not counting the slip javascript library. I prettied it up with some style sheets, and tried to make it mobile friendly.<\/p>\n<p>It&#8217;s tricky to embed it in WordPress. There are conflicts with classes and ID&#8217;s. So instead,\u00a0<a href=\"https:\/\/websterart.com\/html\/study-js\/treehouse\/treeslip.html\" target=\"_blank\" rel=\"noopener\">here is a direct link on my server.<\/a>. It remembers your list items as long as you don&#8217;t clear your browsers cache. It does not sync with your computer though&#8230; You need wunderlist for that.<\/p>\n<p>Just for practice, I&#8217;ve also posted it up on codepen so that <a href=\"https:\/\/codepen.io\/markhwebster\/pen\/EQOBJL\">you can tinker with it, if you should so desire.<\/a><\/p>\n<p>There are two js files that run it. This is the one that hooks it up to the indexedDB database.<\/p>\n<pre class=\"lang:default decode:true \">var todoDB = (function(){\r\n  var tDB = {};\r\n  var datastore = null;\r\n\r\n  \/\/todo: add methods for interacting with db here\r\n  \/\/open connection to the datastore\r\n\r\n  tDB.open = function(callback){\r\n    \/\/database version\r\n    var version = 1;\r\n\r\n    \/\/open connection to db\r\n    var request = indexedDB.open('todos', version);\r\n\r\n    \/\/handle upgrades to datastore\r\n    request.onupgradeneeded = function(e) {\r\n      var db = e.target.result;\r\n\r\n      e.target.transaction.onerror = tDB.onerror;\r\n\r\n      \/\/delete old datastore\r\n      if(db.objectStoreNames.contains('todo')) {\r\n        db.deleteObjectStore('todo');\r\n      }\r\n\r\n      \/\/create a new datastore\r\n      var store = db.createObjectStore('todo', {\r\n        keyPath: 'timestamp'\r\n      });\r\n\r\n    };\/\/end request.onupgradeneeded function\r\n\r\n    \/\/handle success datastore access\r\n    request.onsuccess = function(e) {\r\n      \/\/get a reference to the DB\r\n      datastore = e.target.result;\r\n\r\n      \/\/execute callback\r\n      callback();\r\n    };\r\n    \/\/handlerrors opening datastore\r\n    request.onerror = tDB.onerror;\r\n\r\n  };\/\/end tDB.open = function\r\n\r\n  \/\/fetch all the todo items in the datastore\r\n  tDB.fetchTodos = function(callback) {\r\n    var db = datastore;\r\n    var transaction = db.transaction(['todo'], 'readwrite');\r\n    var objStore = transaction.objectStore('todo');\r\n\r\n    var keyRange = IDBKeyRange.lowerBound(0);\r\n    var cursorRequest = objStore.openCursor(keyRange);\r\n\r\n    var todos = [];\r\n\r\n    transaction.oncomplete = function(e) {\r\n      \/\/execute callback\r\n      callback(todos);\r\n    };\r\n\r\n    cursorRequest.onsuccess = function(e){\r\n      var result = e.target.result;\r\n\r\n      if(!!result == false) {\r\n        return;\r\n      }\r\n\r\n      todos.push(result.value);\r\n\r\n      result.continue();\r\n    };\r\n\r\n    cursorRequest.onerror = tDB.onerror;\r\n  };\r\n\r\n  \/**\r\n  * create new todo item\r\n  *\/\r\n  tDB.createTodo = function(text, callback){\r\n    \/\/ get reference to db\r\n    var db = datastore;\r\n\r\n    \/\/intiate new transaction\r\n    var transaction = db.transaction(['todo'], 'readwrite');\r\n\r\n    \/\/get the datastore\r\n    var objStore = transaction.objectStore('todo');\r\n\r\n    \/\/create a timestamp for todo item\r\n    var timestamp = new Date().getTime();\r\n\r\n    \/\/create an object for the todo itemsv\r\n    var todo = {\r\n      'text': text,\r\n      'timestamp': timestamp\r\n    };\r\n\r\n    \/\/create the datastore cursorRequest\r\n    var request = objStore.put(todo);\r\n\r\n    \/\/handle the success put\r\n    request.onsuccess = function(e) {\r\n      \/\/execute callback\r\n      callback(todo);\r\n    };\r\n\r\n    \/\/handle handlerrors\r\n    request.onerror = tDB.onerror;\r\n  };\/\/end tDB.createTodo function\r\n\r\n  \/**\r\n  * delete a todo item\r\n  *\/\r\n  tDB.deleteTodo = function(id, callback){\r\n    var db = datastore;\r\n    var transaction = db.transaction(['todo'], 'readwrite');\r\n    var objStore = transaction.objectStore('todo');\r\n\r\n    var request = objStore.delete(id);\r\n\r\n    request.onsuccess = function(e) {\r\n      callback();\r\n    }\r\n    \/\/writes errors to the console\r\n    request.onerror = function(e){\r\n      console.log(e);\r\n    }\r\n  };\/\/end tDB.deleteTodo function\r\n  \/\/export the tDB object\r\n  return tDB;\r\n}());\r\n<\/pre>\n<p>And here is the one where I did quite a bit of customization, you can see the drag and drop &#8220;slip&#8221; references at the bottom.<\/p>\n<pre class=\"lang:default decode:true \">window.onload = function(){\r\n  \/\/todo: app code goes here\r\n  \/\/display todo items by passing in the refreshTodos function as a parameter?\r\n  todoDB.open(refreshTodos);\r\n\r\n  \/\/get references to html elements\r\n  var newTodoForm = document.getElementById('new-todo-form');\r\n  var newTodoInput = document.getElementById('new-todo');\r\n  var submitBtn = document.querySelector('.submitBtn');\r\n  \/\/my form submitter addEventListener\r\n  newTodoForm.addEventListener('submit', triggerForm);\r\n\r\n  submitBtn.addEventListener('click', triggerForm);\r\n\r\n  \/\/handle the form sumissions\r\nfunction triggerForm() {\r\n    \/\/get text\r\n    var text = newTodoInput.value;\r\n\r\n    \/\/check tomake sure test is not blank or just spaces\r\n    if(text.replace(\/ \/g,'') != ''){\r\n      \/\/create the todo item\r\n      todoDB.createTodo(text, function(todo) {\r\n        refreshTodos();\r\n      });\r\n    }\r\n\r\n    \/\/reset input field\r\n    newTodoInput.value = '';\r\n\r\n    \/\/don't send the form\r\n    return false;\r\n  };\/\/end triggerForm function\r\n\r\n  \/\/update the list of todo items.\r\n  function refreshTodos(){\r\n    todoDB.fetchTodos(function(todos){\r\n      var todoList = document.getElementById('todo-items');\r\n      todoList.innerHTML = '';\r\n\r\n      for(var i = 0; i &lt; todos.length; i++){\r\n        \/\/read the todo items backwards-most recent first\r\n        var todo = todos[(todos.length - 1 - i)];\r\n\r\n        var li = document.createElement('li');\r\n        li.id = 'todo-' + todo.timestamp;\r\n        \/\/li.setAttribute('draggable', 'true');\r\n        var checkbox = document.createElement('button');\r\n        checkbox.textContent = 'X';\r\n        checkbox.className = 'todo-checkbox';\/\/\"\"\r\n        checkbox.setAttribute('data-id', todo.timestamp);\/\/\"\"\r\n\r\n\r\n\r\n        var span = document.createElement('span');\r\n        span.innerHTML = todo.text;\r\n\r\n        li.appendChild(span);\r\n        li.appendChild(checkbox);\r\n        todoList.appendChild(li);\r\n\r\n        \/\/set up listener checkbox\r\n        checkbox.addEventListener('click', function(e){\r\n          var id = parseInt(e.target.getAttribute('data-id'));\r\n\r\n          todoDB.deleteTodo(id, refreshTodos);\r\n        });\r\n      }\/\/end for i &lt; todos.length\r\n\/\/my non mobile dragger function used to be here.\r\n\r\n    });\/\/end todoDB.fetchTodos\r\n    newTodoInput.focus();\r\n\r\n  }\/\/end function refreshTodos\r\n\/\/begin slippery functions\r\n\/\/slideThem = &lt;ul id=\"todo-items\"&gt;\r\nvar slideThem = document.querySelector('#todo-items');\r\nnew Slip(slideThem);\/\/apply slip library to &lt;ul&gt;\r\nslideThem.addEventListener('slip:reorder', function(e){\r\n  e.target.parentNode.insertBefore(e.target, e.detail.insertBefore);\r\n});\r\n\/\/end slipper function\r\n};\/\/end window.onload\r\n<\/pre>\n<p>The style sheet got fairly long \u00a0because&#8230;well&#8230;I&#8217;m sort of artsy and I wanted it to be pretty.<\/p>\n<pre class=\"lang:default decode:true \">* {\r\n  -moz-box-sizing: border-box;\r\n  -webkit-box-sizing: border-box;\r\n  box-sizing: border-box;\r\n}\r\n\r\nbody, html {\r\n  padding: 0;\r\n  margin: 0;\r\n}\r\n\r\nbody {\r\n\r\n  color: #545454;\r\n  background: #f7f7f7;\r\n}\r\n#page-wrapper {\r\n    font: 2em Verdana, Helvetica, sans-serif;\r\n  width: 100%;\r\n  max-width: 750px;\r\n  margin: 0.2em auto;\r\n  background: #fff;\r\n  box-shadow: 0 1px 3px rgba(0,0,0,0.2);\r\n  border-radius: 12px;\r\n}\r\n#page-wrapper footer {\r\nborder-top: 2px solid #0088cc;\r\npadding: 0.5em;\r\nbackground-color: rgb(207, 240, 245);\r\nborder-bottom-left-radius: 12px;\r\nborder-bottom-right-radius: 12px;\r\n}\r\n#page-wrapper footer h3 {\r\n  font-size: 1.2em;\r\n  color: #61bfee;\r\n  margin:0;\r\n  padding: 0;\r\n  text-align: center;\r\n}\r\n#new-todo-form {\r\n  padding: 0.5em;\r\n  background: #0088cc;\r\n  border-top-left-radius: 12px;\r\n  border-top-right-radius: 12px;\r\n}\r\n\r\n#new-todo {\r\n  width: 100%;\r\n  padding: 0.5em;\r\n  font-size: 1em;\r\n  border-radius: 3px;\r\n  border: 0;\r\n}\r\n\r\n#todo-items {\r\n  list-style: none;\r\n  padding: 0.3em 0.3em;\r\n  margin: 0;\r\n}\r\n\r\n#todo-items li {\r\n  margin: 0.5em 0 0 0;\r\n  padding: 0;\r\n  background-color: rgb(246, 232, 161);\r\n  border: 1px solid rgb(153, 129, 67);\r\n  display: flex;\r\n  justify-content: space-between;\r\n\/* cursor: move; *\/\r\n}\r\n#todo-items li.dragElem {\r\n  opacity: 0.6;\r\n}\r\n#todo-items li.over {\r\n  border-top: 4px solid red;\r\n}\r\n#todo-items li span {\r\n  margin: 0.7em 0.2em;\r\n\r\n}\r\n#page-wrapper button:hover {\r\n  background-color: #0088cc;\r\n  color: white;\r\n}\r\n.submitBtn {\r\n      color: white;\r\n      font-size: 1em;\r\n      padding: 0.5em;\r\n      margin: 0.5em 0 0 0;\r\n      background-color: rgb(255, 162, 4);\r\n}\r\n\r\n.todo-checkbox {\r\n  color: white;\r\n  background-color: rgb(213, 119, 21);\r\n  font-size: 1.2em;\r\n  padding: 0.5em;\r\n}\r\n<\/pre>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I spent a full day trying to debug an indexedDB JavaScript\u00a0lesson. It would work on my iPhone, and on codepen, but nowhere else. It wouldn&#8217;t \u00a0work at all on my MacBook, except for codepen.com. I knew my code was clean because typing errors are always the first thing I check. And the way it was [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1944,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1,179],"tags":[188,189],"class_list":["post-1932","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog","category-programming","tag-indexeddb-javascript","tag-to-do-list-tutorial"],"_links":{"self":[{"href":"https:\/\/websterart.com\/wordpress\/wp-json\/wp\/v2\/posts\/1932","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/websterart.com\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/websterart.com\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/websterart.com\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/websterart.com\/wordpress\/wp-json\/wp\/v2\/comments?post=1932"}],"version-history":[{"count":8,"href":"https:\/\/websterart.com\/wordpress\/wp-json\/wp\/v2\/posts\/1932\/revisions"}],"predecessor-version":[{"id":1943,"href":"https:\/\/websterart.com\/wordpress\/wp-json\/wp\/v2\/posts\/1932\/revisions\/1943"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/websterart.com\/wordpress\/wp-json\/wp\/v2\/media\/1944"}],"wp:attachment":[{"href":"https:\/\/websterart.com\/wordpress\/wp-json\/wp\/v2\/media?parent=1932"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/websterart.com\/wordpress\/wp-json\/wp\/v2\/categories?post=1932"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/websterart.com\/wordpress\/wp-json\/wp\/v2\/tags?post=1932"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}