Monthly Chronicler LocalStorage Tutorial

Monthly Chronicler LocalStorage Tutorial

Monthly Chronicler LocalStorage Tutorial

Are you familiar with “LocalStorage” at the client side of web application usage, as an alternative to HTTP Cookie usage? It can store much more information than HTTP Cookies, but has no expiry date as such. This is not to say that you can’t “removeItem” “LocalStorage” entries.

Where and why is this of interest in terms of our “Monthly Chronicler” web application last talked about with the recent Monthly Chronicler Date Range Tutorial? Well, as you can imagine, especially for somebody using this web application like a “Diary” the daily entries could get quite long. “LocalStorage” can handle this data where HTTP Cookies will “fail”.

What does it mean to “fail” in Javascript? Think, the (Javascript) use of try/catch in our proof of concept of this idea below …


<!doctype html>
<html>
<head>
<title>Proof of concept HTTP Cookies to localStorage usage - RJM Programming - June, 2018</title>
<style> td { text-align: center; } </style>
<script type='text/javascript'>
var qcok=true, cok=true, uselocal=false;
var str="0123456789", prestr="0123456789";
function vr_cookieVal(thisl) {
var tCookie, subfor="";
if (uselocal) {
if (localStorage) {
var lthere=null;
eval("lthere=localStorage." + thisl);
if (lthere) {
subfor=thisl + '=' + localStorage.getItem(thisl) + "; ";
tCookie=subfor.split("; ");
}
}
}
if (subfor != "" || document.cookie != '') {
if (subfor == "") {
tCookie=document.cookie.split("; ");
}
for (var j=0; j<tCookie.length; j++) {
if (("" + thisl.trim()) == tCookie[j].split("=")[0]) {
return decodeURIComponent(tCookie[j].split("=")[1]);
}
}
}
return '';
}
function vr_setCookie(thisl, thisg) { // thanks to JavaScript and Ajax by Tom Negrino and Dori Smith
var expireDate = new Date();
expireDate.setMonth(expireDate.getMonth()+6);
try {
if (uselocal) {
if (localStorage) {
localStorage.setItem(thisl, encodeURIComponent(thisg));
return true;
} else {
document.cookie = "" + thisl + "=" + encodeURIComponent(thisg) + "; expires=" + expireDate.toGMTString();
if (vr_cookieVal(thisl) != thisg) return false;
return true;
}
} else {
document.cookie = "" + thisl + "=" + encodeURIComponent(thisg) + "; expires=" + expireDate.toGMTString();
if (vr_cookieVal(thisl) != thisg) return false;
return true;
}
} catch(error) {
try {
if (uselocal) {
document.cookie = "" + thisl + "=" + encodeURIComponent(thisg) + "; expires=" + expireDate.toGMTString();
return false;
} else {
expireDate.setMonth(expireDate.getMonth()-6);
document.cookie = "" + thisl + "=; expires=" + expireDate.toGMTString();
if (localStorage) {
localStorage.setItem(thisl, encodeURIComponent(thisg));
return false;
} else {
document.cookie = "" + thisl + "=" + encodeURIComponent(thisg) + "; expires=" + expireDate.toGMTString();
return false;
}
}
} catch(errortwo) {
return false;
}
}
}
function startoff() {
if (qcok && cok) {
str+=prestr; //"0123456789";
if (str.length < 520) prestr=str;
document.title='' + str.length;
cok=vr_setCookie("ourstring", str);
cok=vr_setCookie("ourstring2", str);
if (cok) {
document.getElementById('Cookie').innerHTML='' + str.length;
document.getElementById('Cookie2').innerHTML='' + str.length;
setTimeout(startoff,200);
} else {
uselocal=true;
document.getElementById('localStorage').innerHTML='' + str.length;
document.getElementById('localStorage2').innerHTML='' + str.length;
setTimeout(startoff,200);
}
} else {
prestr=str.substring(0,2000);
qcok=false;
cok=true;
for (var ii=0; ii<1; ii++) {
str+=prestr; //"0123456789";
document.title='' + str.length;
cok=vr_setCookie("ourstring", str);
cok=vr_setCookie("ourstring2", str);
if (cok) {
document.getElementById('localStorage').innerHTML='' + str.length;
document.getElementById('localStorage2').innerHTML='' + str.length;
setTimeout(startoff,200);
} else if (document.getElementById('localStorage').innerHTML.indexOf(' failed at ') == -1) {
localStorage.removeItem("ourstring");
localStorage.removeItem("ourstring2");
document.getElementById('localStorage').innerHTML+=' failed at ' + str.length;
document.getElementById('localStorage2').innerHTML+=' failed at ' + str.length;
prestartoff();
}
}
}
}
function prestartoff() {
var xexpireDate = new Date(); xexpireDate.setMonth(xexpireDate.getMonth()-6); document.cookie = "" + "ourstring" + "=; expires=" + xexpireDate.toGMTString(); document.cookie = "" + "ourstring2" + "=; expires=" + xexpireDate.toGMTString();
}
</script>
</head>
<body onload=' prestartoff(); startoff();'>
<table style='width:100%;'>
<tr><th>Cookie</th><th>localStorage</th></tr><tr>
<td id=Cookie></td><td id=localStorage></td></tr>
<td id=Cookie2></td><td id=localStorage2></td></tr>
</table>
</body>
</html>

… and that you can either download to your local web server (such as MAMP local Apache/PHP/MySql) and execute to test this yourself.

“Failure” is relative. How do you learn without failure? Why do we come down so hard on “failure” when a lot of what we learn can only be learnt through failure? I expect an answer to these by tomorrow, otherwise we’ll just have to tell your parents, OK?! Well, no, it’s not OK … it’s okay … but, well it’s not OK … okay?!

It could be some time, or never that today’s (changed code of) monthly_chronicler.html‘s live run link comes into play, but we’re here for the “long game”. Quidditch, anyone?!


Previous relevant Monthly Chronicler Date Range Tutorial is shown below.

Monthly Chronicler Date Range Tutorial

Monthly Chronicler Date Range Tutorial

Today’s crucial Javascript function is called “nothing” (or more specifically setTimeout(nothing,600); (to have a small delay to when it gets used)). How did this come to pass? Well, today, with some new onclick event logic allowing for the user to define date range “Monthly Chronicle” data entries, we define new onclick logic on a whole lot of table td (cell) HTML elements. You click on a table cell then click on another (or its nested a link) and that’s two dates ready to define a date range … right? Well, yes, “right”, but with event logic, events flow through to parent HTML elements as well, and so without some care, we could end up with ugly consequences … right?!

Without that care, of calling “nothing” (a Javascript function that resets some arrays …


function nothing() {
range=[];
tdclicklist=[];
}

… back to pre-cell clicking (or touching) days good times) a lot, at the correct times for non-cell “nested”


function getinfo(bb) {
if ((document.URL.split('#')[0] + '&').indexOf('close=&') != -1) theopen='';
var rv='', sparerv='';
rv=vr_cookieVal('c' + bb);
if (rv != '') {
sparerv=rv.replace(/\'/g,'`').replace(/\"/g,'`');
while (sparerv.indexOf(String.fromCharCode(10)) != -1) {
sparerv=sparerv.replace(String.fromCharCode(10), ' ');
}
while (sparerv.indexOf('<br>') != -1) {
sparerv=sparerv.replace('<br>', ' ');
}
while (sparerv.indexOf('<div>') != -1) {
sparerv=sparerv.replace('<div>', ' ');
}
while (sparerv.indexOf('</div>') != -1) {
sparerv=sparerv.replace('</div>', ' ');
}
while (sparerv.indexOf(' ') != -1) {
sparerv=sparerv.replace(' ', ' ');
}
return '<details onclick="setTimeout(nothing,600);" title="' + sparerv.split('<')[0] + '" id=dt' + bb + theopen + '><summary onclick="setTimeout(nothing,600);" title="' + sparerv.split('<')[0] + '" id=sy' + bb + '></summary><div onclick="setTimeout(nothing,600);" onblur="makeit(' + "'c" + bb + "',this.innerHTML" + ');" contenteditable="true">' + rv + '</details>';
}
return rv;
}

… elements, we’ll not be able to differentiate a click of an a link (ie. right on the number) from a “first of two” click date range definition.

If you think about it, this “propogation” of event logic (and please know there are Javascript methods such as stopPropogation which may help you out here too, as an alternative approach) can actually be used to your advantage the other way when you want to departmentalize several things you want to do with a click (or touch) anywhere within a table cell. It’s just not what we want today, though.

And so, apart from “nothing”, what do we have today amongst the new Javascript logic for this?


var appendmode=false;
var xtowhat='', xdwo='', xnta='';
var tdclicklist=[];
var range=[];

function qt(twp, suggestion) {
var dateo;
if (tdclicklist.length == 1) {
var towhat=tdclicklist[0];
range=[];
var tds=document.getElementsByTagName('a');
for (var ia=eval(-1 + tds.length); ia>=0; ia--) {
if (tds[ia].id.substring(1).length == towhat.substring(1).length && tds[ia].id.substring(1) >= towhat.substring(1) && tds[ia].id.substring(1) <= twp) {
dateo=new Date(eval(tds[ia].id.substring(1).substring(0,4)), eval(-1 + eval(tds[ia].id.substring(1).substring(4,6))), eval(tds[ia].id.substring(1).substring(6,8)));
range.push('d' + tds[ia].id.substring(1) + ',' + daysofweek[dateo.getDay()]);
}
}
tdclicklist=[];
if (range.length == 0) return suggestion;
appendmode=false;
return 'date range from date ' + range[eval(-1 + range.length)].split(',')[1] + ', ' + towhat.substring(1).substring(6) + ' ' + months[eval(-1 + eval(towhat.substring(1).substring(4,6)))] + ' ' + towhat.substring(1).substring(0,4) + ' to ' + suggestion + ' (prefix by + to append what is said here to any pre-existing chronicles)';
}
return suggestion;
}

function tdclick(tdo) {
if (tdclicklist.length == 1) {
document.getElementById('a' + tdo.id.substring(2)).click();
} else {
tdclicklist.push('a' + tdo.id.replace('td',''));
}
}

function preask() {
ask(xtowhat, xdwo, xnta);
if (range.length == 1) {
document.getElementById('t' + xtowhat).style.backgroundColor='yellow';
range=[];
tdclicklist=[];
} else {
if (range.length >= 1) {
document.getElementById('t' + xtowhat).style.backgroundColor='yellow';
xdwo=range[0].split(',')[1];
xtowhat=range[0].split(',')[0];
setTimeout(preask, 500);
} else {
document.getElementById('t' + xtowhat).style.backgroundColor='yellow';
tdclicklist=[];
range=[];
appendmode=false;
}
}
}

function ask(towhat, dwo, nta) {
xtowhat=towhat;
xdwo=dwo;

var retval=document.getElementById(towhat).innerHTML.split('</div></details>')[0].split('</details>')[0].split('</div>')[0];
if (retval.indexOf('</summary>') != -1) {
retval=retval.split('</summary>')[1];
if (retval.indexOf('">') != -1) {
retval=retval.replace(retval.split('">')[0] + '">','');
while (retval.indexOf(String.fromCharCode(10)) != -1) {
retval=retval.replace(String.fromCharCode(10), ' ');
}
while (retval.indexOf('<br>') != -1) {
retval=retval.replace('<br>', ' ');
}
while (retval.indexOf('<div>') != -1) {
retval=retval.replace('<div>', ' ');
}
while (retval.indexOf('</div>') != -1) {
retval=retval.replace('</div>', ' ');
}
while (retval.indexOf(' ') != -1) {
retval=retval.replace(' ', ' ');
}
}
} else if (retval.indexOf('<div') != -1 && retval.indexOf('">') != -1) {
retval=retval.replace(retval.split('">')[0] + '">','');
while (retval.indexOf(String.fromCharCode(10)) != -1) {
retval=retval.replace(String.fromCharCode(10), ' ');
}
while (retval.indexOf('<br>') != -1) {
retval=retval.replace('<br>', ' ');
}
while (retval.indexOf('<div>') != -1) {
retval=retval.replace('<div>', ' ');
}
while (retval.indexOf('</div>') != -1) {
retval=retval.replace('</div>', ' ');
}
while (retval.indexOf(' ') != -1) {
retval=retval.replace(' ', ' ');
}
}
var newretval;
if (nta.trim() != '') {
if (appendmode && retval != '') {
newretval=retval + ' <br>' + nta;
} else {
newretval=nta;
}
} else {

if (document.URL.toLowerCase().replace('HTTP://localhost','https://localhost').indexOf('http:') == 0 || (!passwordpassed && passwordrequired)) {
newretval=prompt('What do you want to chronicle for ' + qt(towhat.substring(1), 'date ' + dwo + ', ' + towhat.substring(1).substring(6) + ' ' + months[eval(-1 + eval(towhat.substring(1).substring(4,6)))] + ' ' + towhat.substring(1).substring(0,4)) + '? (NB: <br> is line feed)', retval);
} else {
newretval=prompt('What do you want to chronicle for ' + qt(towhat.substring(1), 'date ' + dwo + ', ' + towhat.substring(1).substring(6) + ' ' + months[eval(-1 + eval(towhat.substring(1).substring(4,6)))] + ' ' + towhat.substring(1).substring(0,4)) + '? (NB: <br> is line feed) (append with ?password=[yourPassword] to ask for a password to access from here on)', retval);
}
}
if (newretval == null) {
return retval;
} else if (nta == '' && newretval != '') {
if (newretval.substring(0,1) == '+') {
appendmode=true;
if (retval != '') {
if (newretval != '') xnta=newretval.substring(1);
var xnewretval=retval + ' <br>' + newretval.substring(1);
newretval=xnewretval;
} else {
if (newretval != '') xnta=newretval.substring(1);
newretval=newretval.substring(1);
}
} else {
xnta=newretval;
}

}
if (newretval.indexOf('?password=') != -1) {
//document.getElementById('pd').innerHTML=newretval.split('?password=')[0];
makepd(document.getElementById('pd').innerHTML, newretval.split('?password=')[1]);
}
if (newretval.indexOf('/password/') != -1) {
//document.getElementById('pd').innerHTML=newretval.split('/password/')[0];
makepd(document.getElementById('pd').innerHTML, newretval.split('/password/')[1]);
}
if (newretval.split('?password=')[0].split('/password/')[0] != retval) {
vr_setCookie('c' + towhat.substring(1), newretval.split('?password=')[0].split('/password/')[0]);
}
document.getElementById(towhat).innerHTML='<div onblur="makeit(' + "'c" + towhat.substring(1) + "',this.innerHTML" + ');" contenteditable="true">' + newretval + '</div>';
if (range.length == 1) {
range=[];
tdclicklist=[];
} else if (range.length > 1) {
document.getElementById('t' + towhat).style.backgroundColor='yellow';
range.shift();
if (range.length >= 1) {
xdwo=range[0].split(',')[1];
xtowhat=range[0].split(',')[0];
setTimeout(preask, 500);
}
} else {
range=[];
tdclicklist=[];
setTimeout(nothing, 600);
}

return newretval;
}

… set into motion via the new onclick logic for table cells established in the document.body onload event populate function’s …


mstr=mstr.replace(' title=' + thedayindex + '&gt;', ' onclick="tdclick(this);" title=' + thedayindex + ' id=td' + tddaystr + '&gt;' + alink(dmoy, tddaystr, daysofweek[eval(thedayindex % 7)]));

… where you will see us [array].pushing and [array].shifting and you can appreciate the value of setTimeout delays when creating your own event simulations.

You can join with us, trying this out at (the changed) monthly_chronicler.html‘s live run link.


Previous relevant Monthly Chronicler Password Protect Tutorial is shown below.

Monthly Chronicler Password Protect Tutorial

Monthly Chronicler Password Protect Tutorial

When you have sensitive data involving personal information and monetary matters you ideally need to …

  • involve SSL (ie. https: type URLs) … and as far as passwords are concerned …
  • it is best to hash your passwords rather than encrypt them

… but just bear in mind there is a fair bit to that latter idea. Today, we are going to offer users a way to shield prying eyes from seeing their “diary” entries with our “Monthly Chronicler” web application, but we will not be hashing passwords to achieve this. Please do not think that none of the concepts below will not affect your security of data here …

  • somebody clearing the web browser cache … though the affect of this is that your data is wiped rather than found out
  • somebody examining the HTTP Cookies separately to find your data … there is mild encryption, but as you will read on the “net”, encryption is not enough for true password protection, database usage with password hashing is much better

… nevertheless, for the most part, what we do today extending the functionalities introduced with yesterday’s Monthly Chronicler Primer Tutorial serve a purpose for the vast majority of users on the net, that being if you indicate that you want some data not to be shared, then the user, as a reasonable person, will oblige, and not go snooping.

Our simple approach today is that with either or both …

  • personal details … and/or …
  • any date clicked/touched to chronicle something

… now has functionality available to the user (by appending ?password=[theirPassword] to their displayed data) to password protect the current web browser and https://www.rjmprogramming.com.au/HTMLCSS/monthly_chronicler.html usage pairing. Others not knowing this password (and personal details pairing) can still use the web application, but any HTTP Cookie stored information “does not stick” for them.

Feel free to rejoin us, trying it out at (the changed) monthly_chronicler.html‘s live run link.


Previous relevant Monthly Chronicler Primer Tutorial is shown below.

Monthly Chronicler Primer Tutorial

Monthly Chronicler Primer Tutorial

We’ve got a new web application for you today. We’re calling it a “Monthly Chronicler”, which is a web application like a “Calendar” or “Planner” or “Diary”, where the user can personalize the entries according to a date, with whatever information they want.

This idea is of limited applicability unless it is a bit accountable, and at this first draft, the accountability comes in the form of saving a user’s personalized data into HTTP Cookie data form. As you might know from online shopping experience that you have, the use of HTTP Cookies can mean a web application can resume another call of the web application and load in your previous data, ideal for a “Monthly Chronicler”.

Other features are …

  • use of HTML details/summary element pairing (to allow for display. or not, of previously entered data) … nesting …
  • use of HTML div contenteditable=”true” (for ease of user editing purposes)

Join us trying it out at monthly_chronicler.html‘s live run link. With such a web application it can suit being a web browser’s home page to …

If this was interesting you may be interested in this too.


If this was interesting you may be interested in this too.


If this was interesting you may be interested in this too.


If this was interesting you may be interested in this too.

This entry was posted in eLearning, Event-Driven Programming, Tutorials and tagged , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>