Click here to Skip to main content
13,248,087 members (38,144 online)
Click here to Skip to main content
Add your own
alternative version

Stats

7.6K views
85 downloads
1 bookmarked
Posted 15 Mar 2015

MyFamily.Show: A Simple JQuery Mobile Family Tree : Part 2

, 15 Mar 2015
Rate this:
Please Sign up or sign in to vote.
Demonstrating background image on forms, slide panel, custom transitions, d3 animated tree, export svg to image, iOS like springboard, flat data to tree conversion, separation of concerns and pagecontainer.

Introduction

This article is an continuation of my previous article that entailed creating a family tree application as depicted here. Our previous family tree app was only just able to add, update, delete and view available family members. We want more. We want to have a background image in our app springboard, we want a family tree displayed in a tree, we want to be able to export our family tree to an image. I would also be nice if we have have a left side panel to ease navigation in this app. The listview based navigation on the springboard was not displaying nicely on small devices, can that be changed to a more fluent UI? With this version we will attempt to do these additions and thus enhancing the Family Tree app as a whole. These enhancements however can be applied to any JQuery Mobile app that one can create.

Let me just introduce the new enhancements with screens first. I'm starting to enjoy doing this with the ripple simulators.

Download MyFamily.Show.zip

Figure 1: Springboard with background image and iOS like navigation buttons.

I have added some css to each anchor here to have an iOS like navigation springboard. Whilt the buttons do not resize to the screen like my previous listview based springboard, I found these to be nicely organized and easy to the eye on all the devices I tested. The background image here is added as a style to the page defintion. This image resizes itself on orientation change, that was a nice discovery.

Figure 2: Slide Panel

For the Add & Edit Family Member screens, we have added a slide panel on the left of the form with buttons inside a listview. These proved easy to use and improved the navigation of the app as a whole. Let me explain these listview buttons:

1. New - takes one to add a new member

2. Relationships > Father - displays a d3 tree chart with relationships of fathers and children

3. Relationships > Mother - displays a d3 tree chart with relationships of mothers and children

4. Reports - takes one to the report screen. This is an exportable table. I started discussing tables and exporting them in this article. This app, like that one is also a write once and run everywhere app.

A slide panel can either be on the left or right of a page. It also has properties to show it like reveal, push and overlay. More information about panels is available from the JQuery Mobile Website in this link.

Figure 3: Custom Transitions

There is a new css file added to the app called animation.css. With this, one can add their own custom animation for their transitions. This is besids the JQuery Mobile animations available here. The new MyFamily.Show used a customFade transition. I have declared a global variable in my app.js file called pgtransition as depicted below:

var pgtransition = 'customFade';

The enumerations for the transitions are customRotate, customBounce, customFade, customFlip and customCrazy. I sourced the animation from here. Wow.

NB: Should you change a transition, also update the index.html file. Go on, replace 'customFade' with 'customCrazy', quiet impressive neh?

Figure 4: d3 animated tree

After much trial and error, this finally worked. My data is flat and thus needed a few tricks to make it a tree so that it could finally work with this and also get the export to an image working. This tree is zoomable, clickable, collapsible, draggable etc. When displaying, the tree is always centred on the view area, this the # node will be in the middle centre of the screen and one has to move the tree chart to view it like above. Whilst not a huddle, it would be nice for it to display on the top left of the screen. The vieweing area width and height of the device were also other deciding factors.

Figure 5: Exporting to image

The trick to making the d3 tree exportable is to ensure that your css definitions are within the javascript definitions of each node and or link and not only defined within the <style></style> of the page. I loved the fact that the exported png is transparent however it only exports the visible viewport, thus for many trees, one would need to enlarge the screen.

Clicking export will activate an export of the tree and this will be downloaded automatically.

I opened the export and pasted in it on MS Word (image above).

Background

Before I go to the crux of this article, let me refresh...

1. I have discussed creating CRUD applications for JQuery Mobile here. MyFamily.Show still stores data in LocalStorage.

2. I have also discussed Reporting and Exporting a table to Excel here. That entailed reading the data from LocalStorage and create a table with toggable columns to display the data.

3. I started the development of MyFamily.Show here, and this is a revamp of that article. That demonstrated splitting form input into various sections of the family tree input. For that we used a header navigation bar with Personal Information, Contact Details, Relationships and Death details. We demonstrated how to make the first navigation button active when the screen opens by clicking it. We also used a single page using a div for the various information parts and not different html files as it is normally done at times.

We also demonstrated displaying more information on the ListView item, e.g. countbubbles, description etc. Including sorting the json data stored on localstorage by fullname. This included showing how to load DropdownLists from stored information using relationships, i.e. loading fathers and mothers from the current stored list of family members. NBI will tweak this to add Females on Mothers and Males on Fathers.

That article also demonstrated a datepicker widget that has sliding dates for date selection.

A lot has happened with this version. For example, we separated concerns by removing the javascript code from the index.html file and putting it in its own separate file. This as a result makes the code easier to maintain without affecting the view. We have also added a global variable for page transition for ease of code maintenance too. We also fixed a few bugs within the previous codes that we found. We have also run the code with JSLint at a minimal level though and still implementing its recommendations where its necessary. That works perfectly with NotePad++ as a plugin. Fold also helped with indenting the code.

Using the code

1. Creating the new springboard with a background image

HTML Definition

<div data-theme="a" style="background-image:url('b1.jpg');background-position:center center;background-repeat:no-repeat;background-size:cover;background-size: 100% 100%;" id="pgMenu" data-role="page">
        
        <header id="pgMenuHdr" data-role="header" data-position="fixed">
            <h1>MyFamily.Show</h1>
        </header>
        <div id="pgMenucontent" data-role="content">
            <div style="width:130px;height:96px;margin:15px 4px 10px 4px;float:left;padding-bottom:35px;text-align:center;">
                <a id="sbFamilyMember" data-transition="customFade" href="#pgFamilyMember"><img height="96" width="96" title="Family Members" src="apps80.png" alt="Maintain Family Members"></img>
                <strong>Family Members</strong></a>
            </div>
            <div style="width:130px;height:96px;margin:15px 4px 10px 4px;float:left;padding-bottom:35px;text-align:center;">
                <a id="sbReports" data-transition="customFade" href="#pgReports"><img height="96" width="96" title="Reports" src="apps80.png" alt="Access Reports"></img>
                <strong>Reports</strong></a>
            </div>
        </div>
        
        <footer id="pgMenuFtr" data-role="footer" data-position="fixed">
            <h1>Powered by JQM.Show © Anele Mbanga 2015</h1>
        </footer>
</div>

The springboard now features a background image. Any page that you want to have a background image can follow the same definition to have it. That will be useful for different pages having different images as there is a global way to do this via css.

<div data-theme="a" style="background-image:url('b1.jpg');background-position:center center;background-repeat:no-repeat;background-size:cover;background-size: 100% 100%;" id="pgMenu" data-role="page">

This piece of html definition renders the background image to the springboard. The file name is b1.jpg. It gets centred, does not repeat, is a cover image and the size is 100% all around. There is however a second or less delay when one opens the springboard as the image resizes itself on change of orientation or opening the page again.

2. Defining the iOS like buttons for the springboard

<div style="width:130px;height:96px;margin:15px 4px 10px 4px;float:left;padding-bottom:35px;text-align:center;">
                <a id="sbFamilyMember" data-transition="customFade" href="#pgFamilyMember"><img height="96" width="96" title="Family Members" src="apps80.png" alt="Maintain Family Members"></img>
                <strong>Family Members</strong></a>
            </div>

From the code about on the page, each button that displays on the springboard is defined as an anchor with a image that sits inside a div. The height and width of each div is set at 96 with centred text. As usual we define each transition inside the anchor and the anchor definition so that its clickable. There is no javascript linked to a click here as there is a href for each button. I got this idea from gizmo freeware reviews.

style="text-decoration: none; color: #FFFFFF;"

will remove the underline from the anchor and also display the link white, see the updated springboard below.

Figure: Updated Springboard

Compare this to the links in Figure 1.

3. Creating the side panel

To improve navigation, side panels have been added on Members Listing, Add and Update screens for family members. It is recommended that panels on a page get added before the header definition.

<div data-position="left" data-display="reveal" data-position-fixed="true" id="pgFamilyMemberPnl" data-role="panel">
                    <ul data-role="listview" id="pgFamilyMemberPnlLV">
                        <li ><a data-transition="customFade" href="#pgAddFamilyMember">New</a></li>
                        <li ><a data-transition="customFade" href="#pgFamilyMemberMindFather">Relationships > Father</a></li>
                        <li ><a data-transition="customFade" href="#pgFamilyMemberMindMother">Relationships > Mother</a></li>
                        <li ><a data-transition="customFade" href="#pgRptFamilyMember">Report</a></li>
                        <li ><a data-transition="customFade" href="#pgMenu">Back</a></li>
                    </ul>
</div>

The panel we have defined is a reveal panel that means its hidden until the menu button is selected. The menu button then acts as a toggle in this case to open and close the panel to reveal the listview with buttons. Within each panel, there are buttons inside a listview to add new members, view relationships, access reports and also go back. The Report option is controlled as one can access reports from any screen, thus to go to the last screen from reports, some code had to be written, as depicted below.

$('#pgRptFamilyMemberBack').on('click', function (e) {
                e.preventDefault();
                e.stopImmediatePropagation();
                var pgFrom = $('#pgRptFamilyMemberBack').data('from');
                switch (pgFrom) {
                    case "pgAddFamilyMember":
                    $.mobile.changePage('#pgFamilyMember', {transition: pgtransition});
                    break;
                    case "pgEditFamilyMember":
                    $.mobile.changePage('#pgFamilyMember', {transition: pgtransition});
                    break;
                    case "pgFamilyMember":
                    $.mobile.changePage('#pgFamilyMember', {transition: pgtransition});
                    break;
                    default:
                    // go back to the listing screen
                    $.mobile.changePage('#pgReports', {transition: pgtransition});
                }
});

I needed to tell the reports screen to go back to the screen it originated from, thus when each page is shown, I store the source in a data attribute of the back button of the report screen.

4. Creating the family tree with d3 chart framework

In our html definition, we added the d3 references and css scripts. The d3.min.js scripts are downloadable from the d3 website link provided above. d3tree.js is an example I got from the site to generate the d3 tree using flat data. They have a lot of examples from their side to generate any type of chart from data.

<script src="d3.min.js" type="text/javascript"></script>
<script src="d3tree.js" type="text/javascript"></script>

The css that we also added:

.node { cursor: pointer; }
.overlay{ background-color:#EEE; }
.node circle { fill: #fff; stroke: steelblue; stroke-width: 1.5px; }
.node text { font-size:10px; font-family:sans-serif; }
.link { fill: none; stroke: #ccc; stroke-width: 1.5px; }
.templink { fill: none; stroke: red; stroke-width: 3px; }
.ghostCircle.show { display:block; }
.ghostCircle, .activeDrag .ghostCircle { display: none; }

For the export, each of the various elements had to be updated to include these styles within their actual definition in javascript.

See the app.FamilyMemberMindMapFather function below in terms of how the information is read from our backend and translated into a tree for Fathers information

Html Definition - Father & Mother Relationships

The pages to show the family tree per father and mother have nothing basic except the Back and Export buttons and also a div to hold the d3 tree element. This is the Father relationships screen. The Mother's one is almost identical. I decided to split the relationships for both mother and father because I wanted to show the relationships and the source of the parents. For example, the women have been captured using their maiden names. Thus the Father's relationships tree will show anyone who has a father or does not irrespective of whether they are female or male. The children of the daughters will not be shown though the daughter is shown linked to the father or no father given. The same goes for the Mothers relationships. The figure below depics this. 

<div data-theme="a" id="pgFamilyMemberMindFather" data-role="page">                
                <header id="pgFamilyMemberMindFatherHdr" data-role="header" data-position="fixed">
                    <h1>MyFamily.Show > Relationships > Father</h1>
                    <a data-role="button" id="pgFamilyMemberMindFatherBackFather" href="#pgFamilyMember" data-icon="arrow-l" data-transition="customFade" class="ui-btn-left">Back</a>
                    <a data-role="button" id="FamilyMemberMindExportFather" data-icon="camera" class="ui-btn-right">Export</a>
                </header>
                <div id="FamilyMemberMindFather">
                </div>            

</div>

Figure: Fathers

Bulelwa Mbanga (my sister) if you look in Figure 5 at the beginning of this article has three kids however here the kids are not displayed but she is linked to Mbulelo Mbanga who is her father. By the way, Mbulelo is my father, may his soul rest in peace.

Generating this tree and others is done via code by the d3tree.js file when the relationships screen is being displayed. However, we need to read and generate the tree from our flat records stored in local storage. This is done like this.

We get all the family members information and for each member, we get the father and the full name of the family member. We then link the parent father to the child, then with data.reduce generate a tree parent child like relationship for d3 to plot. For this to work we need to have a null parent, thus we define that too within the script.

app.FamilyMemberMindMapFather = function () {
            //create an array for flat records table with parent and child name
            var data = [], rec, n;
            // get Family Member records.
            var FamilyMemberObj = app.getFamilyMember();
            for (n in FamilyMemberObj) {
                //get the record parent and child relationship
                var FamilyMemberRec = FamilyMemberObj[n];
                var parentn = FamilyMemberRec.Father;
                var childn = FamilyMemberRec.FullName;
                if (parentn == 'null') {
                    parentn = '#';
                };
                // the child relationship used parent name and child name
                rec = {};
                rec.name = childn;
                rec.parent = parentn;
                data.push(rec);
            };
            //add the parent node, make it # for sorting purposes
            rec = {};
            rec.name = '#';
            rec.parent = null;
            data.push(rec);
            //create a name based map for the nodes
            var dataMap = data.reduce(function(map, node) {
                map[node.name] = node;
                return map;
            }, {});
            //iteratively add each child to its parent
            var treeData = [];
            data.forEach(function(node) {
                // add to parent
                var parent = dataMap[node.parent];
                if (parent) {
                    // create child array if it doesn't exist
                    (parent.children || (parent.children = []))
                    // add node to child array
                    .push(node);
                    } else {
                    // parent is null or missing
                    treeData.push(node);
                }
            });
            //draw the d3 tree
            DrawTree(treeData, '#FamilyMemberMindFather');
        };

The tree needs to have a null parent first to draw as per treeData variable. The DrawTree function is what composes d3tree.js. A good understanding of d3 functions is needed to comprehend the happenings of the file. This is one of the examples I explored I got most of the file contents from the d3 website an just tweaked it to make it work in the following areas.

$(divID).html = '';
d3.select('svg').remove();

The lines above clear the div container for the d3 map and also the d3 svg. Without the second line, the tree was redrawn after an existing map even if I did a refresh or opened a page by clicking back and opening it again.

var viewerWidth = (window.innerWidth > 0) ? window.innerWidth : screen.width;
var viewerHeight = (window.innerHeight > 0) ? window.innerHeight : screen.height;

This was to determine the vieweing area of the device. Whilst one is able to make the width fit 100%, the height has been very tricky.

I also added [0] on the visit function just because we are sourcing our data from flat records that have been convered into a tree, see MindMapFather above.

visit(treeData[0], function(d) {...

and finally...

root = treeData[0];

on line 527 of the d3tree.js file.

You will notice that when you run the app, the tree is shown after the family relationships page is loaded. I played around with the pagecontainer here. This replaces some methods that are no longer going to be available in the next version of jquery mobile, i.e. 1.5 onwards e.g. pageshow etc.

$(document).on('pagecontainershow', function (e, ui) {
                var pageId = $(':mobile-pagecontainer').pagecontainer('getActivePage').attr('id');
                switch (pageId) {
                    case 'pgFamilyMemberMindFather':
                    app.FamilyMemberMindMapFather();
                    break;
                    case 'pgFamilyMemberMindMother':
                    app.FamilyMemberMindMapMother();
                    break;
                }
            });

Here, the id of the page that is displayed is obtained by getActivePage and then the building of the tree is executed depending on the page selected.

5. Exporting the family tree

Exporting the family tree to an image was not easy as I would have wanted. As much as I googled a lot to get it working it just didnt want to budge, but finally, it happened, through following an example I found. I had to tweak the css attributes within the d3tree.js file also to ensure that it works perfectly.

// export button click on records mindmap page
            $('#FamilyMemberMindExportFather').on('click', function (e) {
                e.preventDefault();
                e.stopImmediatePropagation();
                d3.selectAll('svg').attr('version', '1.1');
                d3.selectAll('svg').attr('xmlns', 'http://www.w3.org/2000/svg');
                var html = d3.select('svg').node().parentNode.innerHTML;
                var imgsrc = 'data:image/svg+xml;base64,' + btoa(html);
                var img = '<img src=' + imgsrc + '>';
                //create a canvas to store the image
                var canvas = document.createElement('canvas');
                canvas.width = (window.innerWidth > 0) ? window.innerWidth : screen.width;
                canvas.height = (window.innerHeight > 0) ? window.innerHeight : screen.height;
                var context = canvas.getContext('2d');
                var image = new Image;
                image.src = imgsrc;
                image.onload = function() {
                    context.drawImage(image, 0, 0);
                    var canvasdata = canvas.toDataURL('image/png');
                    var pngimg = '<img src=' + canvasdata + '>';
                    var a = document.createElement('a');
                    a.download = 'Father.png';
                    a.href = canvasdata;
                    a.click();
                };
            });

When one clicks the export button on either the Mother or Father relationships, the code above executes for the respective button. I have followed recommendations given from the internet to make this work, and unfortunately it does not work with putting part of the code in an href of an anchor but I guess one could put this in a js and then just call a single method.

This reads the html of the d3 svg and then runs btoa to make it a base64 object. An image is created from this. Then a canvas is created where the image is loaded. We ensured the width and height of the canvas balance back to the device screen to ensure that all the tree attributes are exported. The image is loaded to the canvas and a link added to the document to download the canvasdata created, closing with a click method to execute the automatic download.

The image is given the name of the relationship we are interested in. This results in the transparent image as depicted in figure 6, the one shown pasted onto MS Word.

Points of Interest

The first article about the family only touched the surface of the app by just providing a framework to capture family members. With this update, one is able to have a background image for the springboard, are able to view the parent child relationship in a tree like chart, and also able to export the family tree to an image. Also a side panel has been added to improve the screen navigation

The discovery of d3 for me has been a marvel. I'll explore some more about the basics of how I can use it. I enjoyed discovering orgchart to work on organisational charts and will explorer it further. This is helping me a lot on my RAD tool JQM.Show as I'm planning to develop a massive web app with it using this framework covering everything I have written about here so far. I still however have to go through some data validation as data intergrity will be crucial going forward.

The way information is scattered on the net still poses a challenge as most things take a lot of research to find them out, however finding them and making them work proves a journey worth taking.

There is a lot of discomfort with the pagecontainer and lack of backward compatibility on JQuery Mobile about that. I guess we will see as time goes on on what happens with that.

The panels have proved a worth exercise to implement for ease of navigation. I however still have some challenges with the theming here. The custom animation was a good find, the customCrazy is very amazing however I found it to be very challenging to the eye. I have opted for the new fading effect.

I wonder how all this will work with Angular... #ThinkingAloud.

License

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

Share

About the Author

Anele 'Mashy' Mbanga
Software Developer DanNora Business Solutions
South Africa South Africa
I'm a Bachelor of Commerce graduate, fell inlove with ICT years back with VB5. Used Pick & System Builder to create a windows app. Very curious, developed my first web database app called Project.Show using ExtJS. Published on Google Play Store, learned JQuery Mobile, a project manager at best. My first intranet app eFas with MySQL.

Fear closes people to a lot of things and we hold ourselves back being held by it. Thus the sooner you believe you can't do something, the sooner everything will work towards that belief. Believe in yourself at all times because you can do anything you set your mind to it!

I have a very beautiful woman and four kids, the best joys in the world. East London, South Africa is currently home.

Awards:

Best Mobile Article of February 2015 (First Prize)
http://www.codeproject.com/Articles/880508/Create-a-CRUD-web-app-using-JQuery-Mobile-and-Loca

Best Mobile Article of May 2015 (Second Prize)
http://www.codeproject.com/Articles/991974/Creating-JQuery-Mobile-CRUD-Apps-using-JQM-Show-Ge

Apps
Bible.Show (Android Store App)
https://www.facebook.com/bibleshow
https://play.google.com/store/apps/details?id=com.b4a.BibleShow

JQM.Show (Android Store App)
https://www.facebook.com/jqmshow
https://play.google.com/store/apps/details?id=com.b4a.JQMShow

CodeProject.Show (An offline CodeProject Article writer)
http://www.codeproject.com/Articles/993453/CodeProject-Show-A-CodeProject-offline-article-wri

You may also be interested in...

Pro
Pro

Comments and Discussions

 
AnswerIs that the article with bad vote? Pin
Sergey Alexandrovich Kryukov8-Apr-15 18:03
mvpSergey Alexandrovich Kryukov8-Apr-15 18:03 
GeneralAppreciation Pin
Anele 'Mash' Mbanga24-Mar-15 11:43
memberAnele 'Mash' Mbanga24-Mar-15 11:43 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.171114.1 | Last Updated 16 Mar 2015
Article Copyright 2015 by Anele 'Mashy' Mbanga
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid