Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

MyFamily.Show: A Simple JQuery Mobile Family Tree

4.74/5 (6 votes)
3 Mar 2015CPOL11 min read 18.7K   226  
MyFamily.Show : A simple Family Tree Mobile App to keep details of your family tree.

Introduction

The purpose of this project is to demonstrate how one can create a Single Page Application to store a persons information in various tabs without additional pages. It is to also explorer the datepicker widgets, the slider, horizontal radio buttons and the switch control in JQuery Mobile. This also demonstrates how to load a SelectMenu dynamically from stored data that resides in LocalStorage and also left align the SelectMenu text. For the listing of family members, I demonstrate how to feed count-bubbles with a member's age, and show the contact details on each listview item of the family member. Interesting also is the fact that all family members will always be listed in ascending order no matter how you add them. That is through a script to sort the JSON when a save is performed by the web app.

Here is the complete source code for MyFamily.Show. Download MyFamily.Show.zip

Background

I wanted to find out how I can use navigation tabs on my JQuery Mobile App to split forms that into various information parts. With that I also wanted to know how I can load a SelectMenu dynamically from information stored from my LocalStorage and this information should be related e.g. parents of a person. I wanted the information parts of a person to reside within one page separated by div elements and not be loading from another page.

In developing a Family Tree, basic information about a person is needed. These parts I have concluded to be related to the following items:

  • Personal Information (Gender, Full Name, Salutation, ID Number / SSN, Date of Birth, Age, Is Living? and Place of Birth)
  • Contact Details - (Cellphone / Mobile phone number, Email Address)
  • Relationships - (Father and Mother) and
  • Death - (Date of Death, Place of Death)

This app of couse can be customised to have additional information but I wanted to keep it to basic information for this demonstration.

The information captured gets stored in LocalStorage.

Using the code

Like all HTML5 web apps, the user interface should be designed and then the linkage of the various buttons that save, retrieve and delete the information created. We will define a page with four navigation bar items, these being Personal Information, Contact Details, Relationships and Death. Each of these tabs will have information that will be requested from the user and the user saves the details of the family member at the Death screen. I'm still to explore how I can make this into a wizard like interface.

I am following the same approach I have been using in creating the NoteKeeper application for CRUD application development depicted here.  

I will delve into the important stuff and you can review the source code for more details. This will be defining the navigation bar and the pages and the source scripts that link them.

The Navigation Bar - html definition and output

HTML
<div id="pgAddFamilyMember" data-role="page">

<header id="pgAddFamilyMemberheader" data-role="header" data-position="fixed">
<h1>MyFamily.Show > Add Family Member</h1>
<a data-role="button" id="pgAddFamilyMemberBack" data-icon="arrow-l" class="ui-btn-left">Back</a>
<div id="pgAddFamilyMemberHdrNav" data-role="navbar">
<ul>
<li><a href="#" data-href="pgAddFamilyMemberPersonalInformation" id="pgAddFamilyMemberPersonalInformationBtn" class="ui-btn-active">Personal Information</a>
</li>
<li><a href="#" data-href="pgAddFamilyMemberContactDetails" id="pgAddFamilyMemberContactDetailsBtn">Contact Details</a>
</li>
<li><a href="#" data-href="pgAddFamilyMemberRelationships" id="pgAddFamilyMemberRelationshipsBtn">Relationships</a>
</li>
<li><a href="#" data-href="pgAddFamilyMemberDeath" id="pgAddFamilyMemberDeathBtn">Death</a>
</li>
</ul>
</div>
</header>

The page to add a family member is called pgAddFamilyMember and the one to update called pgEditFamilyMember, these have a similar navigation bar. Each element in the navigation bar has a data-href attribute that stores the name of the div to navigate to when each button is pressed. I had to add an id attribute to each button because each time this page is added, I wanted it to default to the first screen, i.e. Personal Details, irrespective of where the user was last time. This is achieved by firing a click event of the header button using the id. That trigger is part of this code below, that gets fired before a page is shown.

The resulting output of the header from above is depicted in Figure 1 below, running from an iPad Ripple emulator.

Figure 1

Image 1

 

JavaScript
// code to run before showing the page that lists the records.
//before records listing is shown, check for storage
$(document).on('pagebeforechange', function(e, data){
//get page to go to
var toPage = data.toPage[0].id;
switch (toPage) {
case 'pgFamilyMember':
// restart the storage check
app.checkForFamilyMemberStorage();
break;
case 'pgEditFamilyMember':
// clear the add page form fields
pgEditFamilyMemberClear();
//load related select menus before the page shows
app.pgEditFamilyMemberLoadFather();
app.pgEditFamilyMemberLoadMother();
//show the first nav item.
$('#pgEditFamilyMemberPersonalInformationBtn').trigger('click');
break;
case 'pgAddFamilyMember':
// clear the add page form fields
pgAddFamilyMemberClear();
//load related select menus before the page shows
app.pgAddFamilyMemberLoadFather();
app.pgAddFamilyMemberLoadMother();
//show the first nav item.
$('#pgAddFamilyMemberPersonalInformationBtn').trigger('click');
break;
default:
}
});

 

What the code above basically does is:

1. Before the listing of the family members, check if there are any saved in Local Storage and update a listview

2. Before the screen to edit family members is shows, clear the contents of all the controls in that screen, loady the father and mother selectmenus with names that are already stored in the family tree records and then

3. Trigger a click event to show the first screen, i.e. being Personal Information.

The code to make the navigation to work with the divs sits in the attached navbar.js file and this file should be included before the jquery mobile.js script call.

JavaScript
// include this file before your jquery-mobile script tag
$(document).delegate('.ui-navbar ul li > a', 'click', function() {
  //search the navbar to deactivate the active button
  $(this).closest('.ui-navbar').find('a').removeClass('ui-btn-active');
  //change the active tab
  $(this).addClass('ui-btn-active');
  //hide the siblings
  $('#' + $(this).attr('data-href')).show().siblings('.tab-content').hide();
  return false;
});

For each item in the navigation bar's list, when its selected, remove the active indicator of that button, i.e. the last active button, then add an active indicator to the selected button. After that read the data-href attribute and show the linked div and hide everything else. Each div has a class of tab-content defined in it as will be shown below and a style was added to the source to ensure this all works.

The style added to make this all work, i.e. navigation without moving from page to page is:

CSS
.tab-content {display:none;}
.tab-content:first-child { display: block;}

We have explained the Navigation Bar, now lets deal with the various information parts.

Personal Information - html definition and output.

Figure 2 below depicts the output of the Personal Details screen as defined in the fields mentioned above.

Figure 2

Image 2

HTML
<div id="pgAddFamilyMemberPersonalInformation" class="tab-content">
<div data-role="fieldcontain">
<fieldset id="fspgAddFamilyMemberGender" data-role="controlgroup" data-type="horizontal" data-mini="true">
<legend>Gender<span style='color:red;'>*</span></legend>
<input type="radio" name="pgAddFamilyMemberGender" id="pgAddFamilyMemberGendermale" autocomplete="off" title="" value="male" required></input>
<label for="pgAddFamilyMemberGendermale" id="lblpgAddFamilyMemberGendermale">Male<span style='color:red;'>*</span></label>
<input type="radio" name="pgAddFamilyMemberGender" id="pgAddFamilyMemberGenderfemale" autocomplete="off" value="female"></input>
<label for="pgAddFamilyMemberGenderfemale" id="lblpgAddFamilyMemberGenderfemale">Female</label>
</fieldset>
</div>
<div data-role="fieldcontain">
<label for="pgAddFamilyMemberFullName" id="lblpgAddFamilyMemberFullName">Full Name<span style='color:red;'>*</span></label>
<input type="text" name="pgAddFamilyMemberFullName" id="pgAddFamilyMemberFullName" placeholder="Enter full name here." autocomplete="off" data-clear-btn="true" title="Enter full name here." required></input>
</div>
<div data-role="fieldcontain">
<fieldset id="fspgAddFamilyMemberSalutation" data-role="controlgroup" data-type="horizontal" data-mini="true">
<legend>Salutation<span style='color:red;'>*</span></legend>
<input type="radio" name="pgAddFamilyMemberSalutation" id="pgAddFamilyMemberSalutationMr" autocomplete="off" value="Mr"></input>
<label for="pgAddFamilyMemberSalutationMr" id="lblpgAddFamilyMemberSalutationMr">Mr</label>
<input type="radio" name="pgAddFamilyMemberSalutation" id="pgAddFamilyMemberSalutationMs" autocomplete="off" value="Ms"></input>
<label for="pgAddFamilyMemberSalutationMs" id="lblpgAddFamilyMemberSalutationMs">Ms</label>
<input type="radio" name="pgAddFamilyMemberSalutation" id="pgAddFamilyMemberSalutationMrs" autocomplete="off" value="Mrs"></input>
<label for="pgAddFamilyMemberSalutationMrs" id="lblpgAddFamilyMemberSalutationMrs">Mrs</label>
<input type="radio" name="pgAddFamilyMemberSalutation" id="pgAddFamilyMemberSalutationDr" autocomplete="off" value="Dr"></input>
<label for="pgAddFamilyMemberSalutationDr" id="lblpgAddFamilyMemberSalutationDr">Dr</label>
<input type="radio" name="pgAddFamilyMemberSalutation" id="pgAddFamilyMemberSalutationProf" autocomplete="off" value="Prof"></input>
<label for="pgAddFamilyMemberSalutationProf" id="lblpgAddFamilyMemberSalutationProf">Prof</label>
</fieldset>
</div>
<div data-role="fieldcontain">
<label for="pgAddFamilyMemberIDNumber" id="lblpgAddFamilyMemberIDNumber">ID Number<span style='color:red;'>*</span></label>
<input type="number" name="pgAddFamilyMemberIDNumber" id="pgAddFamilyMemberIDNumber" placeholder="Enter id number here." autocomplete="off" data-clear-btn="true" title="Enter id number here." required></input>
</div>
<div data-role="fieldcontain">
<label for="pgAddFamilyMemberDateofBirth" id="lblpgAddFamilyMemberDateofBirth">Date of Birth<span style='color:red;'>*</span></label>
<input data-options='{"mode":"flipbox","dateFormat":"%Y-%m-%d","overrideDateFormat":"%Y-%m-%d"}' type="text" name="pgAddFamilyMemberDateofBirth" id="pgAddFamilyMemberDateofBirth" placeholder="Enter date of birth here." autocomplete="off" data-role="datebox" title="Enter date of birth here." required></input>
</div>
<div data-role="fieldcontain">
<label for="pgAddFamilyMemberAge" id="lblpgAddFamilyMemberAge">Age<span style='color:red;'>*</span></label>
<input min="0" max="200" type="range" name="pgAddFamilyMemberAge" id="pgAddFamilyMemberAge" autocomplete="off" title="" required></input>
</div>
<div data-role="fieldcontain">
<label for="pgAddFamilyMemberLiving" id="lblpgAddFamilyMemberLiving">Is Living<span style='color:red;'>*</span></label>
<select name="pgAddFamilyMemberLiving" id="pgAddFamilyMemberLiving" data-role="slider" data-mini="true" dir="ltr" class="required">
<option value="no">No</option>
<option value="yes">Yes</option>
</select>
</div>
<div data-role="fieldcontain">
<label for="pgAddFamilyMemberPlaceofBirth" id="lblpgAddFamilyMemberPlaceofBirth">Place of Birth<span style='color:red;'>*</span></label>
<input type="text" name="pgAddFamilyMemberPlaceofBirth" id="pgAddFamilyMemberPlaceofBirth" placeholder="Enter place of birth here." autocomplete="off" data-clear-btn="true" title="Enter place of birth here." required></input>
</div>
</div>

The above script defines the html behind the Personal Information screen. As you can see, the first line of the script has a class called 'tab-content' to tell the app that this will be treated within the confined of a tab, this ensures that our navigation is in sync.

Contact Details - html definition and output

The contact details screen is not as complex, only the email and cellphone details are requested.

HTML
<div id="pgAddFamilyMemberContactDetails" class="tab-content">
<div data-role="fieldcontain">
<label for="pgAddFamilyMemberCellphone" id="lblpgAddFamilyMemberCellphone">Cellphone</label>
<input type="number" name="pgAddFamilyMemberCellphone" id="pgAddFamilyMemberCellphone" placeholder="Enter cellphone here." autocomplete="off" data-clear-btn="true"></input>
</div>
<div data-role="fieldcontain">
<label for="pgAddFamilyMemberEmail" id="lblpgAddFamilyMemberEmail">Email</label>
<input type="email" name="pgAddFamilyMemberEmail" id="pgAddFamilyMemberEmail" placeholder="Enter email here." autocomplete="off" data-clear-btn="true"></input>
</div>
</div>
<div id="pgAddFamilyMemberRelationships" class="tab-content">
<div dir="ltr" data-role="fieldcontain">
<label for="pgAddFamilyMemberFather" id="lblpgAddFamilyMemberFather">Father</label>
<select name="pgAddFamilyMemberFather" id="pgAddFamilyMemberFather" data-native-menu="false" data-mini="true" data-inline="true" dir="ltr">
<option value="null" data-placeholder="true">Select Father</option>
<option value="Father1">Father1</option>
<option value="Father2">Father2</option>
<option value="Father3">Father3</option>
</select>
</div>
<div dir="ltr" data-role="fieldcontain">
<label for="pgAddFamilyMemberMother" id="lblpgAddFamilyMemberMother">Mother</label>
<select name="pgAddFamilyMemberMother" id="pgAddFamilyMemberMother" data-native-menu="false" data-mini="true" data-inline="true" dir="ltr">
<option value="null" data-placeholder="true">Select Mother</option>
<option ></option>
</select>
</div>
</div>

You will notice that the SelectMenu control for Father has some options loaded by default when the app starts. However when you run the app, these Father1,Father2 and Father3 options will never be listed at all because the Father selectmenu will be loaded dynamically with existing Full Names from our storage. Figure 3 below is the resulting output of the Contact Details Definition.

Figure 3

Image 3

Relationships - html definition and output

Figure 4 below asks for the Father and Mother's full names. These names are loaded from existing family members that have been captured on MyFamily.Show web app as stored from LocalStorage.

Figure 4:

Image 4

As you will note, these DropDown lists don't fill the whol screen like they normally do. This is because a few tricks were applied to ensure that happens and I will explain these below.

Death- html definition and output

In case one of the family members has passed on, you can also capture their date of death and where they died. This is just to keep a family history going on. The definition of this screen/div is also simple and not fancy, as depicted in Figure 5 below.

HTML
<div id="pgAddFamilyMemberDeath" class="tab-content">
<div data-role="fieldcontain">
<label for="pgAddFamilyMemberDateofDeath" id="lblpgAddFamilyMemberDateofDeath">Date of Death</label>
<input data-options='{"mode":"flipbox","dateFormat":"%Y-%m-%d","overrideDateFormat":"%Y-%m-%d"}' type="text" name="pgAddFamilyMemberDateofDeath" id="pgAddFamilyMemberDateofDeath" placeholder="Enter date of death here." autocomplete="off" data-role="datebox"></input>
</div>
<div data-role="fieldcontain">
<label for="pgAddFamilyMemberPlaceofDeath" id="lblpgAddFamilyMemberPlaceofDeath">Place of Death</label>
<input type="text" name="pgAddFamilyMemberPlaceofDeath" id="pgAddFamilyMemberPlaceofDeath" placeholder="Enter place of death here." autocomplete="off" data-clear-btn="true"></input>
</div>
<div><button type="submit" id="pgAddFamilyMemberSave" class="ui-btn ui-corner-all ui-shadow ui-btn-b">Save Family Member</button>
</div>
</div>
</div>

Figure 5

Image 5

I have decided to have the Update Family Member button on the Death div as the user must enter all the information in the tabs, where available and then save. I will explorer a Back, Next navigation approach next time to make this seamless.

The Family Member Edit/Update Screen

The only difference between the Add Member / Update Member screen are just the control names. I did not want to use the same screen for Add/Update functions as such at times proves a challenge to maintain, yes, its doable, but for the sake of code maintenance, Ive chosen not to follow that approach. As you might have noted by now, all Add related controls are prefixed by pgAdd and all edit controls prefixed by pgEdit. That's the same approach that I followed even with the NoteKeeper web app.

Listing Family Members

The approach to list family members has taken the listview further than my previous posts to now include a count bubble, and contact details.

HTML
<div id="pgFamilyMembercontent" data-role="content">
<ul data-role="listview" data-inset="true" id="pgFamilyMemberList" data-autodividers="true" data-filter="true" data-filter-placeholder="Search Family Members" data-filter-reveal="false">
<li data-role="list-divider">FamilyMemberHdr</li>
<li id="noFamilyMember">You have no family members</li>
</ul>
</div>

Whilst the family listing is defined here, it depends on various scripts within the web app. These are depicted below.

JavaScript
var FamilyMemberLi = '<li ><a href="#pgEditFamilyMember?FullName=Z2"><h2>Z1</h2><p>DESCRIPTION</p><span class="ui-li-count">COUNTBUBBLE</span></a></li>';
var FamilyMemberHdr = '<li data-role="list-divider">FamilyMemberHdr</li>';
var noFamilyMember = '<li id="noFamilyMember">You have no family members</li>';

// display existing records in listview of Records listing.
//display records in listview during runtime.
app.displayFamilyMember = function(){
// get Family Member records.
var FamilyMemberObj = app.getFamilyMember();
// create an empty string to contain html
var html = '';
// make sure your iterators are properly scoped
var n;
// loop over records and create a new list item for each
//append the html to store the listitems.
for (n in FamilyMemberObj){
//get the record details
var FamilyMemberRec = FamilyMemberObj[n];
//define a new line from what we have defined
var nItem = FamilyMemberLi;
//update the href to the key
nItem = nItem.replace(/Z2/g,n);
//update the title to display, this might be multi fields
var nTitle = '';
//assign cleaned title
nTitle = n.replace(/-/g,' ');
//replace the title;
nItem = nItem.replace(/Z1/g,nTitle);
//there is a count bubble, update list item
var nCountBubble = '';
nCountBubble += FamilyMemberRec.Age;
//replace the countbubble
nItem = nItem.replace(/COUNTBUBBLE/g,nCountBubble);
//there is a description, update the list item
var nDescription = '';
nDescription += FamilyMemberRec.Cellphone;
nDescription += ', ';
nDescription += FamilyMemberRec.Email;
//replace the description;
nItem = nItem.replace(/DESCRIPTION/g,nDescription);
html += nItem;
}
//update the listview with the newly defined html structure.
$('#pgFamilyMemberList').html(FamilyMemberHdr + html).listview('refresh');
};

Resulting in something more along these lines, as depicted in Figure 6.

Figure 6

Image 6

You will notice that the family members when adding them, always display in ascending order when being listed. Selecting each family member of course opens up the Update Family Member screen. This sorting of the saved family members happens when the records are saved in LocalStorage.

This is performed through this little code as added to the save methods.

Sorting the Family Member Details JSON

JavaScript
//update local storage object

FamilyMemberObj[FullName] = FamilyMemberRec;
//sort the objects
var keys = Object.keys(FamilyMemberObj);
keys.sort();
var sortedObject = Object();
var i;
for (i in keys) {
key = keys[i];
sortedObject[key] = FamilyMemberObj[key];
}
FamilyMemberObj = sortedObject;
//save all existing records to localstorage
localStorage['myfamilyshow-familymember'] = JSON.stringify(FamilyMemberObj);

The speed of this small code script is a variable perhaps it could be optimized in some way, I have a feeling that for very lardge datasets, one would have to display a progressbar to show something happening on the screen. I will explorer progress bars in follow up posts. The code above gets all the keys of the JSON after a family members details have been saved. The primary key for each family member is the Full Name. For family members that share the same name, perhaps we can add I, II, III, suffixes to the names as the web app will over-write an existing member. The sortedObject is then assigned to the object to stringify and save to localstorage.

Loading Fathers and Mothers to SelectMenu

Besides the single page navigation I wanted to add, loading SelectMenus dynamically was a very interesting task I wanted to do. My thoughts were not just about this, but about other web apps that will have relationships between the various models and I would need to add for example a Category on Products screen. I needed something generic that I can easily customise easily for my RAD tool.

Three motheds basically achieve the SelectMenu updates. We want fathers and mothers, thus we need to get all full names first from all stored records, we defined that function. Note that the function name is the ModelName and FieldName combination. Thus for each model that you might want to use, such a combination might suffice.

JavaScript
//get all existing FamilyMember-FullName
app.getFamilyMemberFullName = function(){
// get Family Member records
var FamilyMemberObj = app.getFamilyMember();
// loop through each record and get the fields we want
// make sure your iterators are properly scoped
var n;
var dsFields = [];
for (n in FamilyMemberObj){
//get the record details
var FamilyMemberRec = FamilyMemberObj[n];
var dsField = FamilyMemberRec.FullName;
dsFields.push(dsField);
}
return dsFields;
};

 

1. This gets all family members and assigns that to a FamilyMemberObj.

2. We loop through each family member record and get the FullName and push these full names to an array.

The next function then parses this array and updates the specific Select Menu, before the page is shown as explained above.

JavaScript
//load the field names for data sources to control 
app.pgAddFamilyMemberLoadFather = function(){
//read the data source data field combination array
var FamilyMemberObj = app.getFamilyMemberFullName();
var dsdf;
//clear the select menu
$('#pgAddFamilyMemberFather').empty();
//ensure its refreshed
$('#pgAddFamilyMemberFather').selectmenu('refresh');
//loop through each item and load it.
var options = [];
options.push('<option value="null" data-placeholder="true">Select Father</option>');
for (dsdf in FamilyMemberObj){
var Father = FamilyMemberObj[dsdf];
options.push("<option value='" + Father + "'>" + Father + "</option>");
}
$('#pgAddFamilyMemberFather').append(options.join("")).selectmenu();
//refresh the select menu, just in case
$('#pgAddFamilyMemberFather').selectmenu('refresh');
};

The app.getFamilyMemberFullName calls a getFamily member method which basically reads information thats stored in this format from LocalStorage, for each member.

{"Anele-Mbanga":{"Image":"","Gender":"male","FullName":"Anele Mbanga","Salutation":"Mr","IDNumber":

"7304155526089","DateofBirth":"2015-03-02","Age":"42","Living":"yes","PlaceofBirth":"East London",

"Father":"null","Mother":"null","Cellphone":"0817366739","Email":"anele@mbangas.com",

"DateofDeath":"","PlaceofDeath":""},"Esona":{"Image":"","Gender":"female","FullName":

"Esona","Salutation":"Ms","IDNumber":"20100401","DateofBirth":"2010-04-02","Age":"5",

"Living":"yes","PlaceofBirth":"Johannesburg","Father":"Anele Mbanga","Mother":"null",

"Cellphone":"","Email":"esona@mbangas.com","DateofDeath":"","PlaceofDeath":""},

"Olothando-Mbanga":{"Image":"","Gender":"female","FullName":"Olothando Mbanga",

"Salutation":"Ms","IDNumber":"","DateofBirth":"2015-03-02","Age":"6","Living":"no",

"PlaceofBirth":"Johannesburg","Father":"Anele Mbanga","Mother":null,"Cellphone":"",

"Email":"olothando@mbangas.com","DateofDeath":"","PlaceofDeath":""},

"Usibabale-Mbanga":{"Image":"","Gender":"male","FullName":"Usibabale Mbanga",

"Salutation":"Mr","IDNumber":"","DateofBirth":"2015-03-02","Age":"12",

"Living":"yes","PlaceofBirth":"Johannesburg","Father":"Anele Mbanga","Mother":"null",

"Cellphone":"","Email":"usibabale@mbangas.com","DateofDeath":"","PlaceofDeath":""}}

This being stored in LocalStorage key:

myfamilyshow-familymember

myfamilyshow being the name of the web app and familymember being the name of the model we are storing information for.

Points of Interest

From this I learned that I needed to clear my controls before additions or edits to ensure that everything is reset. I also discovered how to use a navigation bar buttons without navigating from one page to another. Setting values for the Radio Group proved challenging and I ended up setting the actual value on the code by using scripts like

JavaScript
//use the actual value of the radio button to set it
var opts = 'pgEditFamilyMemberGender' + FamilyMemberRec.Gender;
$('#' + opts).prop('checked', true);
$('#' + opts).checkboxradio('refresh');
$('#pgEditFamilyMemberFullName').val(FamilyMemberRec.FullName);
//use the actual value of the radio button to set it

For example, this will set a control named pgEditFamilyMemberGendermale to true as setting the attribute using the name attribute just would not work properly for me. The unfortunate part is that information is still scattered in the web about how to do these simple coding exercises. I've head to check most methods I found, purported to work which did not for me to end up with a solution. Due to my not so much experience with JQuery and its API, producing this has been a trial and error exercise. I however am working on a RAD tool I call JQM.Show to automate most of this development in three easy steps.

The other find was the datepicker widget that shows a date in this format as depicted in Figure 7 when a user selects a date. You can check the source code of the js that has the control and visit their site for how you can customise it.

Image 7

History

The basis of these articles originate from my article with the NoteKeeper. Compared to that, the sorting algorithim to sort the JSON before it being saved has been an eye opener. Also being able to customize the listview depending on options I choose has been a marvel.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)