What is So Dynamic About Dynamic HTML?
Building a Dynamic Thank You Page
An Introduction to Microsoft Layers
You are here: irt.org | Articles | Dynamic HTML (DHTML) | Family Trees [ previous next ]
Published on: Saturday 31st January 1998 By: Martin Webb
This article will show how you create a dynamic Family Tree that you can navigate around. It uses arrays to hold the data to be displayed in the Family Tree. The example included uses the British Royal Family to demonstrate the varying amounts of information that can be held.
The Family Tree uses frames to show a tree structure in one frame, plus a dynamically changing detail in the other.
Three files are required for the Family Tree, tree.htm which contains the JavaScript and the frameset definition, topFrame.htm and botFrame.htm which make up the contents of the two frames topFrame and botFrame.
The contents of topFrame.htm is fairly simple. The JavaScript function updateDetails() which appears at first sight not to be used, and the writing to the document of the results from the parent documents (i.e. the tree.htm file) showTree() function which is passed the parent frames personToShow< variable.
<BODY BGCOLOR="papayawhip" TEXT="black" LINK="black" ALINK="black" VLINK="black"> <SCRIPT LANGUAGE="JavaScript"><!-- function updateDetails(index) { if (index != parent.details) { parent.botFrame.document.open(); parent.botFrame.document.write(parent.PersonArray[index].details); parent.botFrame.document.close(); parent.details = index; } } document.write(parent.showTree(parent.PersonToShow)); //--></SCRIPT> </BODY>
The unused updateDetails() function will, when used, update the contents of the botFrame using the documents open(), write() and close() methods. The data written to the screen will be the details property of parent frames personArray[] arrays index entry. Once the frame contents have been replaced the details variable in the parent frame is updated with the passed index.
The writing of the details to the botframe will not occur if the frame already holds the details, by comparing the passed index with the details variable held in the parent frame.
The contents of the botFrame.htm file are even simpler. The data written to the screen will be the details property of parent frames personArray[] arrays PersonToShow index entry held in the parent frame. This is similar to the functionality provided by the updateDetails() function in the topFrame.
<SCRIPT LANGUAGE="JavaScript"><!-- document.write(parent.PersonArray[parent.PersonToShow].details); //--></SCRIPT>
First we need to define all the JavaScript functions that will be used in the Family Tree:
The first section is now pretty routine stuff, for all those that have been reading previous articles. The utility functions, padout(), y2k() and makeArray() are used by the HowOld() function to calculate the 'age' between two dates. This is used later on to calulate a persons age today, or their age when they died.
<HTML> <HEAD> <SCRIPT LANGUAGE="JavaScript"><!-- function padout(number) { return (number < 10) ? '0' + number : number; } function y2k(number) { return (number < 1000) ? eval(number) + 1900 : number; } function makeArray() { for (i = 0; i<makeArray.arguments.length; i++) this[i+1] = makeArray.arguments[i]; } var months = new makeArray('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'); var today = new Date(); var thisYear = y2k(today.getYear()); var thisMonth = today.getMonth()+1; var thisDay = today.getDate(); function HowOld(day,month,year,thisDay,thisMonth,thisYear) { var yearsold = thisYear - year, monthsold = 0, daysold = 0; if (thisMonth >= month) monthsold = thisMonth - month; else { yearsold--; monthsold = thisMonth + 12 - month; } if (thisDay >= day) daysold = thisDay - day; else { if (monthsold > 0) monthsold--; else { yearsold--; monthsold+=11; } daysold = thisDay + 31 - day; } if (yearsold < 0) return ''; if ((yearsold == 0) && (monthsold == 0) && (daysold == 0)) return ''; return yearsold + ' years, ' + monthsold + ' months, ' + daysold + ' days '; }
The show() changes the contents of the two frames, by reloading the frame contents.
function show(index) { if (index != 0) { PersonToShow = index; window.topFrame.location.href = 'topframe.htm'; window.botFrame.location.href = 'botframe.htm'; } }
The show() function is invoked when one of the links formated by the showTree() function is selected (i.e. when the user navigates around the Family Tree). If the show() function is passed zero as the value of the index variable, then the documents will not be updated. This is used to good effect in the following showTree() function, where the current person is shown as a link with an onMouseover event handler, but clicking the link does not perform anything.
The showTree() function formats the HTML to be loaded into the topFrame which create the Family Tree structure (Father, Mother, Spouse and Children). It uses various properties of the PersonArray[] array using the passed index. It formats part of the output as links which, as well as invoking the parent frames show() function when the link is clicked, also have onMouseover event handlers, which invoke the, sofar unused, updateDetails() function in the topFrame.htm file. This replaces the contents on the botFrame with the details of the person that the mouse is currently over. When the mouse moves away from the link, then the original details of the currently selected person are reshown in the botFrame.
function showTree(index) { var a = '<TR><TD ALIGN=CENTER WIDTH=50%><FONT FACE="verdana,arial,helvetica">'; var b = '<\/TD><TD ALIGN=CENTER WIDTH=50%><FONT FACE="verdana,arial,helvetica">'; var c = '<\/TD><\/TR>'; var d = '<FONT FACE="verdana,arial,helvetica">_'; var fatherNo = PersonArray[index].fatherNo; var motherNo = PersonArray[index].motherNo; var spouseNo = PersonArray[index].spouseNo; var output = '<CENTER><TABLE HEIGHT=100%><TR><TD><TABLE WIDTH="590">' + a + '<B>Father:<\/B><BR>' + '<A HREF="javascript:parent.show(' + fatherNo + ')" ' + 'onMouseover="updateDetails(' + fatherNo + ')">' + PersonArray[index].father + '<\/A>' + '<\/TD><TD>' + d + b + '<B>Mother:<\/B><BR>' + '<A HREF="javascript:parent.show(' + motherNo + ')" ' + 'onMouseover="updateDetails(' + motherNo + ')">' + PersonArray[index].mother + '<\/A>' + c + a + '|' + '<TD><\/TD>' + b + c + a + '<A HREF="javascript:parent.show(0)" ' + 'onMouseover="updateDetails(' + index + ')" ><B>' + PersonArray[index].name + '<\/B><\/A>' + '<\/TD><TD>' + d + b + '<B>Spouse:<\/B><BR>' + '<A HREF="javascript:parent.show(' + spouseNo + ')" ' + 'onMouseover="updateDetails(' + spouseNo + ')">' + PersonArray[index].spouse + '<\/A>' + c + a + '|' + '<\/TD><TD>' + b + c + a + '<B>Children<\/B>:<BR>'; for (var i=0; i<PersonArray[index].children; i++) { var childNo = PersonArray[index]['child'+i+'No']; output += '<A HREF="parent.show(' + childNo + ')" ' + 'onMouseover="updateDetails(' + childNo + ')">' + PersonArray[index]['child'+i] + '<\/A><BR>'; } output += '<\/TD><TD>' + b + c + '<\/TABLE><\/TD><\/TR><\/TABLE><\/CENTER>'; return output; }
The two functions setPerson() and Person() are used to create an instance of and define the Person object, which is subsequently held in the PersonArray[] array.
The Person() function sets the properties of the Person object that were passed to it, as well as calculating other appropriate properties, birth date, death date, age. Further details not yet know at the time the Person object is created are left initialised.
The function also registers a method of the Person object called setRelations(), when a Person objects setRelations() method is used, the resolveRelations() function is invoked.
function setPerson(name,dobDay,dobMonth,dobYear,dobLocation,dodDay,dodMonth,dodYear,dodLocation) { return PersonArray[PersonArrayIndex++] = new Person(name,dobDay,dobMonth,dobYear,dobLocation,dodDay,dodMonth,dodYear,dodLocation); } function Person(name,dobDay,dobMonth,dobYear,dobLocation,dodDay,dodMonth,dodYear,dodLocation) { this.No = PersonArrayIndex - 1; this.name = name; if (dobDay != 0 && dobMonth !=0 && dobYear !=0) this.birth = padout(dobDay) + '-' + months[dobMonth] + '-' + y2k(dobYear) + ' <B>in<\/B> ' + dobLocation; else this.birth = 'unknown'; if (dodDay != 0 && dodMonth !=0 && dodYear !=0) { this.death = padout(dodDay) + '-' + months[dodMonth] + '-' + y2k(dodYear) + ' <B>in<\/B> ' + dodLocation; if (this.birth != 'unknown') this.age = HowOld(dobDay,dobMonth,dobYear,dodDay,dodMonth,dodYear); else this.age = 'unknown'; } else { this.death = 'N/A'; if (this.birth != 'unknown') this.age = HowOld(dobDay,dobMonth,dobYear,thisDay,thisMonth,thisYear); else this.age = 'unknown'; } // The following details will all be resolved later: this.father = ''; this.fatherNo = 0; this.mother = ''; this.motherNo = 0; this.spouse = ''; this.spouseNo = 0; this.children = 0; this.details = '<BODY BGCOLOR="papayawhip" TEXT="black"><\/BODY>'; this.setRelations = resolveRelations; }
The following resolveRelations() function will update all those properties of the Person object that were originally just initialised. The function is passed three or more other Person objects which are used to update the properties. It expects to receive a father, mother and spouse Person object as a minimum. It uses the resolveRelations() functions arguments[] array to retrieve any additional child person objects. For each child parent object received it creates additional properties of the current Person object.
The function also preformats HTML code which is placed in the Person objects details property. This is used to later overwrite the contents of the botFrame. Because the HTML code is formated and stored in the details property, it can be used to change the contents of the botFrame frame instantaneously. There is no need to wait for a document to be loaded.
function resolveRelations(father,mother,spouse) { this.father = father.name; this.fatherNo = father.No; this.mother = mother.name; this.motherNo = mother.No; if (spouse) { this.spouse = spouse.name; this.spouseNo = spouse.No; } for (var i = 3; i < resolveRelations.arguments.length; i++, this.children++) { this['child' + this.children] = resolveRelations.arguments[i].name; this['child' + this.children + 'No'] = resolveRelations.arguments[i].No; } var a = '<TR><TD VALIGN=TOP><FONT FACE="verdana,arial,helvetica"><B>'; var b = '<\/B><\/TD><TD WIDTH=100% VALIGN=TOP><FONT FACE="verdana,arial,helvetica">'; var c = '<\/TD><\/TR>'; var details = '<CENTER><H1><FONT FACE="verdana,arial,helvetica">' + this.name + '<\/FONT><\/H1><HR><TABLE WIDTH="590">' + a + 'Date of Birth:' + b + this.birth + c + a + 'Date of Death:' + b + this.death + c + a + 'Age:' + b + this.age + c + a + 'Spouse:' + b + this.spouse + c + a + 'Children:' + b; for (var i=0; i<this.children; i++) details += this['child' + i] + '<BR>'; details += c + '<\/TABLE><\/CENTER><HR>'; this.details = '<BODY BGCOLOR="papayawhip" TEXT="black">' + details; }
The following data defines a small subset of the British Royal Family Tree. It uses the setPerson() function to create Person objects which are stored in the PersonArray[] array. The data for each person consists of their name, date of birth, location of birth, date of death and location of death of relevant. A reference to each object is also held in a named variable, the variable names are chosen to represent the Person obect.
var jan=1,feb=2,mar=3,apr=4,may=5,jun=6,jul=7,aug=8,sep=9,oct=10,nov=11,dec=12; var PersonArrayIndex = 0; var PersonArray = new Array(); // Define an unknown person: var anon = setPerson('','',0,0,0,'',0,0,0,'','',''); // Define everyone: var gVI = setPerson('George VI WINDSOR, <I>King of England<\/I>',14,dec,1895,'York Cottage, Sandringham, Norfolk, England',6,feb,1952,'Sandringham, Norfolk, England'); var e_a_m = setPerson('<I>Lady<\/I> Elizabeth Angela Marguerite BOWES-LYON',4,aug,1900,'London, England',0,0,0,''); var eII = setPerson('Elizabeth II Alexandra Mary WINDSOR, <I>Queen of England<\/I>',21,apr,1926,'17 Bruton St., London, W1, England',0,0,0,''); var pp = setPerson('<I>Prince<\/I> Philip MOUNTBATTEN',10,jun,1921,'Isle of Kerkira, Mon Repos, Corfu, Greece',0,0,0,''); var pc = setPerson('<I>Prince<\/I> Charles Philip Arthur WINDSOR',14,nov,1948,'Buckingham Palace, London, England',0,0,0,''); var d_f_s = setPerson('<I>Lady<\/I> Diana Frances SPENCER',1,jul,1961,'Park House, Sandringham, Norfolk, England',31,aug,1997,'Paris, France'); var pw = setPerson('<I>Prince<\/I> William Arthur Philip WINDSOR',21,jun,1982,'St. Mary\'s Hosp., Paddington, London, England',0,0,0,''); var ph = setPerson('<I>Prince<\/I> Henry Charles Albert WINDSOR',15,sep,1984,'St. Mary\'s Hosp., Paddington, London, England',0,0,0,''); var psa = setPerson('<I>Princess<\/I> Anne Elizabeth Alice WINDSOR',15,aug,1950,'Clarence House, St. James, England',0,0,0,''); var m_a_p = setPerson('<I>Captain<\/I> Mark Anthony Peter PHILLIPS',22,sep,1948,'',0,0,0,''); var p_m_a = setPerson('Peter Mark Andrew PHILLIPS',15,nov,1977,'St. Mary\'s Hosp., Paddington, London, England',0,0,0,''); var z_a_e = setPerson('Zara Anne Elizabeth PHILLIPS',15,nov,1977,'St. Mary\'s Hosp., Paddington, London, England',0,0,0,''); var pa = setPerson('Andrew Albert Christian WINDSOR, <I>Duke of York<\/I>',19,feb,1960,'Belgian Suite, Buckingham Palace, England',0,0,0,''); var s_m_f = setPerson('Sarah Margaret FERGUSON, <I>Duchess of York<\/I>',15,oct,1959,'',0,0,0,''); var psb = setPerson('<I>Princess<\/I> Beatrice Elizabeth Mary WINDSOR',8,aug,1988,'Portland Hosp., England',0,0,0,''); var pse = setPerson('<I>Princess<\/I> Eugenie Victoria Helena WINDSOR',23,mar,1990,'London, England',0,0,0,''); var pe = setPerson('<I>Prince<\/I> Edward Anthony Richard WINDSOR',10,mar,1964,'Buckingham Palace, London, England',0,0,0,''); var psm = setPerson('<I>Princess<\/I> Margaret Rose WINDSOR',21,aug,1930,'Glamis Castle, Angus, Scotland',0,0,0,''); var a_c_r = setPerson('Anthony Charles Robert ARMSTRONG-JONES, <I>Earl of Snowdon<\/I>',7,mar,1930,'',0,0,0,''); var d_a_r = setPerson('David Albert Charles ARMSTRONG-JONES, <I>Vicount Linley<\/I>',3,nov,1961,'',0,0,0,''); var s_f_e = setPerson('<I>Lady<\/I> Sarah Frances Elizabeth ARMSTRONG-JONES',1,may,1964,'',0,0,0,'');
Once all the Person objects have been created we can resolve all the relationships between them. Using the setRelations() function as a function of each Person object in turn, we set the father, mother, spouse and child properties by passing the named variable reference the appropriate Person objects.
To place boundaries on the family tree, i.e. where we are not interested in people or we don't know the details, we use the anon Person object created above. The anon Person as empty values for its properties, which allows us to use it in the family tree as a normal relationship, but because the properties are blank the HTML links formatted are empty.
// Resolve relationships: gVI.setRelations(anon,anon,e_a_m,eII,psm); e_a_m.setRelations(anon,anon,gVI,eII,psm); eII.setRelations(gVI,e_a_m,pp,pc,psa,pa,pe); pp.setRelations(anon,anon,eII,pc,psa,pa,pe); pc.setRelations(pp,eII,d_f_s,pw,ph); d_f_s.setRelations(anon,anon,pc,pw,ph); pw.setRelations(pc,d_f_s,null); ph.setRelations(pc,d_f_s,null); psa.setRelations(pp,eII,m_a_p,p_m_a,z_a_e); m_a_p.setRelations(anon,anon,psa,p_m_a,z_a_e); p_m_a.setRelations(m_a_p,psa,null); z_a_e.setRelations(m_a_p,psa,null); pa.setRelations(pp,eII,s_m_f,psb,pse); s_m_f.setRelations(anon,anon,pa,s_m_f,psb,pse); psb.setRelations(pa,s_m_f,null); pse.setRelations(pa,s_m_f,null); pe.setRelations(pp,eII,null); psm.setRelations(gVI,e_a_m,a_c_r,d_a_r,s_f_e); a_c_r.setRelations(anon,anon,psm,d_a_r,s_f_e); d_a_r.setRelations(a_c_r,psm,null); s_f_e.setRelations(a_c_r,psm,null); var details = PersonToShow = eII.No; //--></SCRIPT>
The last line of the above script, sets the starting point PersonToShow to the No property of eII, in this case Elizabeth II Alexandra Mary WINDSOR, Queen of England. Each Person object has a No property that represents its index entry within the PersonArray[] array. It also sets the value of the details variable to this starting point, which is used to determine whether the botFrame should be replaced.
Finally all thats required is the frameset definition:
</HEAD> <FRAMESET ROWS="50%,*" FRAMEBORDER=0 BORDER=0> <FRAME SRC="topframe.htm" NAME="topFrame" SCROLLING=AUTO MARGINWIDTH=1 MARGINHEIGHT=1 NORESIZE> <FRAME SRC="botframe.htm" NAME="botFrame" SCROLLING=AUTO MARGINWIDTH=1 MARGINHEIGHT=1 NORESIZE> </FRAMESET>
Try this example: British Royal Family Tree.
You can view the source code of the three files:
What is So Dynamic About Dynamic HTML?
Building a Dynamic Thank You Page
An Introduction to Microsoft Layers
An Introduction to Netscape Layers