Build an Object Oriented Tree Control In my last article, I

advertisement
Build an Object Oriented Tree Control
In my last article, I demonstrated how to build an Explorer-like tree control using
JavaScript. This month's installment will focus on converting the tree control to an object
oriented structure. So, the problem is:
Problem: How do I build an Explorer-like tree control that is easily extended using object
oriented principles?
Solution: Take advantage of JavaScript's polymorphic abilities and the solution is a snap.
Polymorphism
There are three fundamental principles of object oriented programming: inheritance,
encapsulation, and polymorphism. I've discussed inheritance and encapsulation in a
previous article [[Insert link here]]. This month's article will focus on the polymorphic
aspects of JavaScript in the context of the tree control.
Webopedia.com defines polymorphism like this:
Generally, the ability to appear in many forms. In object-oriented programming,
polymorphism refers to a programming language's ability to process objects differently
depending on their data type or class.
In truly object oriented languages, polymorpism is usually tied to class-based inheritance.
That is, you define a class hierarchy with abstract classes at the top and concrete
implementations further down. In the abstract class, you define a method that must be
implemented or overridden in each subclass. The implementation of that method varies
depending on the needs of the subclass.
The classic example involves shapes. You might define an abstract class called Shape
which includes a method called findArea(). You'd then define classes such as Rectangle
and Circle that inherit from Shape. In each of the subclasses, you'd implement the
findArea() method so that it returns the correct results based on the type of shape.
Essentially, you'd just call findArea() on any shape without worrying about how the
method does its work.
JavaScript doesn't support class-based inheritance but it does include polymorphic
abilities. In fact, JavaScript's prototype-based inheritance makes writing polymorphic
methods simple and the structure closely mirrors that of a true object oriented language.
Set up the HTML and CSS
Open your favorite text editor and build the HTML and CSS for this project:
<html>
<head>
<title>Object Oriented Tree</title>
<style>
body{
font: 10pt Verdana,sans-serif;
color: navy;
}
.branch{
cursor: pointer;
cursor: hand;
display: block;
}
.leaf{
display: none;
margin-left: 16px;
}
a{
text-decoration: none;
}
a:hover{
text-decoration: underline;
}
</style>
</head>
<body>
</body>
</html>
Write the Script
In any object oriented system, it's a good idea to create a hierarchy of objects that
describes the system. In this implementation, I'll include a tree object that holds a
collection of branch objects. Each branch object will, in turn hold a collection of children
that can either be branches or leaf objects. In addition, each object type will implement a
write() method that is polymorphic based on the individual object's needs. That is, a tree
object will have a method called write() whose behavior is different than the branch
object's write() method which, in turn, is different than the leaf object's write() method.
I'll also include an add() method in the tree and branch objects that allows for the addition
of children to the respective collection.
Add a script block to the head of the document that includes the Image objects and
functions necessary to achieve the effect:
<script language="JavaScript">
var openImg = new Image();
openImg.src = "open.gif";
var closedImg = new Image();
closedImg.src = "closed.gif";
function showBranch(branch){
var objBranch = document.getElementById(branch).style;
if(objBranch.display=="block")
objBranch.display="none";
else
objBranch.display="block";
swapFolder('I' + branch);
}
function swapFolder(img){
objImg = document.getElementById(img);
if(objImg.src.indexOf('closed.gif')>-1)
objImg.src = openImg.src;
else
objImg.src = closedImg.src;
}
</script>
The script sets up the image objects necessary to show the user that a folder is either open
or closed. The showBranch() and swapFolder() functions are event handlers for the
document object that get fired after the tree has been written to the screen. For an
explanation of these methods, please see my previous article [[Insert link here]].
Set up the Tree Object
Add the tree object's constructor to the script:
function tree(){
this.branches = new Array();
this.add = addBranch;
this.write = writeTree;
}
The tree() constructor specifies that each tree object will include an array called branches
that serves as the collection of children of the tree. In essence, the tree object represents
the root node of the tree. The add and write properties are the polymorphic methods.
They point to the following functions:
function addBranch(branch){
this.branches[this.branches.length] = branch;
}
function writeTree(){
var treeString = '';
var numBranches = this.branches.length;
for(var i=0;i<numBranches;i++)
treeString += this.branches[i].write();
document.write(treeString);
}
The addBranch() method simply appends the object passed to the method onto the end of
the branches array. The writeTree() method loops through all of the objects stored in the
branches array and calls the write() method of each object. That's the beauty of
polymorphism – I call the polymorphic write() method because each object in the
branches array implements its own version of the write() method. Note that, because
JavaScript's Array object allows you to store anything you'd like, in this implementation,
you can only store objects that implement a write() method in the branches array.
Set up the Branch Object
The branch object is similar to the tree object:
function branch(id, text){
this.id = id;
this.text = text;
this.write = writeBranch;
this.add = addLeaf;
this.leaves = new Array();
}
The branch constructor includes id and text properties. The id property serves as the
unique identifier for the document object written to the screen and the text property
represents the text to display next to the folder. The leaves array is the collection of
children to display for any branch node. Note that branch objects include the necessary
write() method to enable their storage in the branches array of a tree object. By including
write() and add() methods in both tree and branch objects, I've made those methods
polymorphic. Here are the implementations for write() and add():
function addLeaf(leaf){
this.leaves[this.leaves.length] = leaf;
}
function writeBranch(){
var branchString = '<span class="branch" onClick="showBranch(\'' + this.id +
'\')"';
branchString += '><img src="closed.gif" id="I' + this.id + '">' + this.text;
branchString += '</span>';
branchString += '<span class="leaf" id="';
branchString += this.id + '">';
var numLeaves = this.leaves.length;
for(var j=0;j<numLeaves;j++)
branchString += this.leaves[j].write();
branchString += '</span>';
return branchString;
}
The addLeaf() function does the same thing as the addBranch() function of the tree object
– it appends the object passed to the method to the end of the leaves collection.
The writeBranch() method first sets up the HTML string necessary for the display of the
branch and then loops through the leaves array and calls the write() method of each
object stored in the array. Once again, you can only store objects that implement a
write() method in the leaves array.
Set up the Leaf Objects
The leaf objects are actually the easiest objects in the system to set up:
function leaf(text, link){
this.text = text;
this.link = link;
this.write = writeLeaf;
}
Each leaf object gets a text property for display and a link property. In addition leaf
objects implement the write() method like this:
function writeLeaf(){
var leafString = '<a href="' + this.link + '">';
leafString += '<img src="doc.gif" border="0">';
leafString += this.text;
leafString += '</a><br>';
return leafString;
}
The writeLeaf function sets up the HTML string for display. Note that leaf objects don't
need to implement an add() method as they represent the "end" of a branch.
Build the Tree
The only thing left to do is to build the tree on the page. The process is simple: create a
tree object and add branches and/or leaves to it. Then add as many sub-branches and/or
leaves as you'd like. When you've finished building the tree, call the tree object's write()
method and the tree is written to the screen. Here's an example tree that is three levels
deep. Add the following script between the body tags:
<!-- place the tree building script where you'd like in the body -->
<script language="JavaScript">
var myTree = new tree();
var branch1 = new branch('branch1','Branch 1');
var leaf1 = new leaf('Leaf 1','#');
var leaf2 = new leaf('Leaf 2','#');
branch1.add(leaf1);
branch1.add(leaf2);
var branch2 = new branch('branch2','Branch 2');
var leaf3 = new leaf('Leaf 3','#');
branch2.add(leaf3);
branch1.add(branch2);
myTree.add(branch1);
var branch3 = new branch('branch3','Branch 3');
branch3.add(new leaf('Leaf 4','#'));
branch2.add(branch3);
var branch4 = new branch('branch4','Branch 4');
branch4.add(new leaf('Leaf 5','#'));
branch1.add(branch4);
var branch5 = new branch('branch5','Branch 5');
branch5.add(new leaf('Leaf 6','#'));
myTree.add(branch5);
myTree.add(new leaf('Leaf 7','#'));
myTree.write();
</script>
In the End
Essentially, what I've created with the object oriented tree is a set of objects that
implement what is usually called an interface in class-based inheritance languages. I've
declared three objects (tree, branch, leaf) that implement the write interface. That is, they
all include a write() method that supplies the behavior needed by the object based on its
type. In addition, two of the objects (tree, branch) implement the add interface by
supplying add() methods that work for their own respective object types.
Polymorphism represents a powerful object oriented principle that allows for the creation
of robust and scalabe systems. Using polymorphism allows me to separate the design of
the objects from their implementation. In other words, I can say that trees, branches, and
leaves should have a write() method (design) but that the way each object is written is
different (implementation).
Download