- XML Reference Guide
- Overview
- What Is XML?
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- Table of Contents
- The Document Object Model
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- DOM and Java
- Informit Articles and Sample Chapters
- Books and e-Books
- Implementations
- DOM and JavaScript
- Using a Repeater
- Repeaters and XML
- Repeater Resources
- DOM and .NET
- Informit Articles and Sample Chapters
- Books and e-Books
- Documentation and Downloads
- DOM and C++
- DOM and C++ Resources
- DOM and Perl
- DOM and Perl Resources
- DOM and PHP
- DOM and PHP Resources
- DOM Level 3
- DOM Level 3 Core
- DOM Level 3 Load and Save
- DOM Level 3 XPath
- DOM Level 3 Validation
- Informit Articles and Sample Chapters
- Books and e-Books
- Documentation and Implementations
- The Simple API for XML (SAX)
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- SAX and Java
- Informit Articles and Sample Chapters
- Books and e-Books
- SAX and .NET
- Informit Articles and Sample Chapters
- SAX and Perl
- SAX and Perl Resources
- SAX and PHP
- SAX and PHP Resources
- Validation
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- Document Type Definitions (DTDs)
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- XML Schemas
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- RELAX NG
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- Schematron
- Official Documentation and Implementations
- Validation in Applications
- Informit Articles and Sample Chapters
- Books and e-Books
- XSL Transformations (XSLT)
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- XSLT in Java
- Java in XSLT Resources
- XSLT and RSS in .NET
- XSLT and RSS in .NET Resources
- XSL-FO
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- XPath
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- XML Base
- Informit Articles and Sample Chapters
- Official Documentation
- XHTML
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- XHTML 2.0
- Documentation
- Cascading Style Sheets
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- XUL
- XUL References
- XML Events
- XML Events Resources
- XML Data Binding
- Informit Articles and Sample Chapters
- Books and e-Books
- Specifications
- Implementations
- XML and Databases
- Informit Articles and Sample Chapters
- Books and e-Books
- Online Resources
- Official Documentation
- SQL Server and FOR XML
- Informit Articles and Sample Chapters
- Books and e-Books
- Documentation and Implementations
- Service Oriented Architecture
- Web Services
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- Creating a Perl Web Service Client
- SOAP::Lite
- Amazon Web Services
- Creating the Movable Type Plug-in
- Perl, Amazon, and Movable Type Resources
- Apache Axis2
- REST
- REST Resources
- SOAP
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- SOAP and Java
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- WSDL
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- UDDI
- UDDI Resources
- XML-RPC
- XML-RPC in PHP
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- Ajax
- Asynchronous Javascript
- Client-side XSLT
- SAJAX and PHP
- Ajax Resources
- JSON
- Ruby on Rails
- Creating Objects
- Ruby Basics: Arrays and Other Sundry Bits
- Ruby Basics: Iterators and Persistence
- Starting on the Rails
- Rails and Databases
- Rails: Ajax and Partials
- Rails Resources
- Web Services Security
- Web Services Security Resources
- SAML
- Informit Articles and Sample Chapters
- Books and e-Books
- Specification and Implementation
- XML Digital Signatures
- XML Digital Signatures Resources
- XML Key Management Services
- Resources for XML Key Management Services
- Internationalization
- Resources
- Grid Computing
- Grid Resources
- Web Services Resource Framework
- Web Services Resource Framework Resources
- WS-Addressing
- WS-Addressing Resources
- WS-Notifications
- New Languages: XML in Use
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- Google Web Toolkit
- GWT Basic Interactivity
- Google Sitemaps
- Google Sitemaps Resources
- Accessibility
- Web Accessibility
- XML Accessibility
- Accessibility Resources
- The Semantic Web
- Defining a New Ontology
- OWL: Web Ontology Language
- Semantic Web Resources
- Google Base
- Microformats
- StructuredBlogging
- Live Clipboard
- WML
- XHTML-MP
- WML Resources
- Google Web Services
- Google Web Services API
- Google Web Services Resources
- The Yahoo! Web Services Interface
- Yahoo! Web Services and PHP
- Yahoo! Web Services Resources
- eBay REST API
- WordML
- WordML Part 2: Lists
- WordML Part 3: Tables
- WordML Resources
- DocBook
- Articles
- Books and e-Books
- Official Documentation and Implementations
- XML Query
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- XForms
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- Resource Description Framework (RDF)
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- Topic Maps
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation, Implementations, and Other Resources
- Rich Site Summary (RSS)
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- Simple Sharing Extensions (SSE)
- Atom
- Podcasting
- Podcasting Resources
- Scalable Vector Graphics (SVG)
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- OPML
- OPML Resources
- Summary
- Projects
- JavaScript TimeTracker: JSON and PHP
- The Javascript Timetracker
- Refactoring to Javascript Objects
- Creating the Yahoo! Widget
- Web Mashup
- Google Maps
- Indeed Mashup
- Mashup Part 3: Putting It All Together
- Additional Resources
- Frequently Asked Questions About XML
- What's XML, and why should I use it?
- What's a well-formed document?
- What's the difference between XML and HTML?
- What's the difference between HTML and XHTML?
- Can I use XML in a browser?
- Should I use elements or attributes for my document?
- What's a namespace?
- Where can I get an XML parser?
- What's the difference between a well-formed document and a valid document?
- What's a validating parser?
- Should I use DOM or SAX for my application?
- How can I stop a SAX parser before it has parsed the entire document?
- 2005 Predictions
- 2006 Predictions
- Nick's Book Picks
At this point, we have a working Javascript time tracker application we can run in the browser. Before we turn it into a widget, however, we'll clean things up a bit by refactoring the code into Javascript objects. Once we've done that, it'll be easier to add additional functionality, such as creating a new project.
Now, you're already familiar with Javascript objects. You use them all the time, in statements such as:
document.write("Hey there!");
or
window.open("newwin", "http://www.nicholaschase.com");
These are predefined objects, which means they're available as part of the Javascript implementation.
In earlier parts of this project, we created the timesheet
and project
objects, accessing their attributes, as in:
document.write(timesheet.username);
or
document.write(project.time_this_period);
But those were anonymous objects, not related to any particular class. Now we're going to remedy that.
Creating classes in Javascript is fairly straightforward. For example, we can create Timesheet
and Project
classes:
function Timesheet (){ this.user = "Nick"; this.projects = null; } function Project () { this.projectName = "undefined"; this.time_this_period = 0; this.total_time = 0; this.id = -1; } timesheetObj = new Timesheet(); document.write(timesheetObj.username); //outputs "Nick"; timesheetObj.username = "Sarah"; document.write(timesheetObj.username); //outputs "Sarah";
Just as in many other languages, the keyword this
refers to the current object.
Notice also that we can assign a default value for an attribute, and that we can change that value after we create the object.
We can even add methods for these classes, which we can call using the objects. For example,
if we analyze what we're doing with the Timesheet
and Project
objects,
we need the following methods:
Timesheet: display() load() save() addProject() Project: display() updateDisplay() updateTime()
We can add these methods by creating a function and assigning it to a method name, as in:
... function displayProjects(){ outputString = ""; for (i = 0; i < projects.length; i++) { outputString = outputString + '<div id="'+i+'" onclick="activateProject(this)">'; outputString = outputString + projects[i].project_name; outputString = outputString + ' (<span id="time'+i+'">'+projects[i].time_this_period; outputString = outputString + '</span>/<span id="totaltime'+i+'">'+projects[i].total_time+'</span>)</div>'; } targetDiv = document.getElementById("display"); targetDiv.innerHTML = outputString; } function Timesheet (){ this.user = "Nick"; this.projects = null; this.display = displayProjects; } ...
We can then create the object and use it to call the method:
var timesheetObject = new Timesheet(); timesheetObject.display();
Classes and the weakly-typed language
Now, just because we create a structure doesn't meant that Javascript will automatically use it. For example,
the Timesheet
class mimics the structure of the timesheet
object we've been using,
but we can't just use the class's method and assume it's going to work, as in:
timesheet.display()
This won't work because Javascript doesn't know it's a Timesheet
object. Similarly,
var timesheetObject = new Timesheet(); timesheetObject = timesheet; timesheetObject.display();
won't work because Javascript is a "weakly typed" language. When we make the assignment, it loses
its association with the Timesheet
class. Instead, we have to directly assign the attributes, as in:
var timesheetObject = new Timesheet(); timesheetObject.user = timesheet.user; timesheetObject.projects = timesheet.projects; timesheetObject.display();
But that's not very maintainable. Instead, it would be better to create a constructor that takes care of that for us, as in:
function Timesheet (timesheetIn){ this.user = timesheetIn.user; this.projects = timesheetIn.projects; this.display = function (){ outputString = ""; for (i = 0; i < projects.length; i++) { outputString = outputString + '<div id="'+i+'" onclick="activateProject(this)">'; outputString = outputString + projects[i].project_name; outputString = outputString + ' (<span id="time'+i+'">'+projects[i].time_this_period; outputString = outputString + '</span>/<span id="totaltime'+i+'">'+projects[i].total_time+'</span>)</div>'; } targetDiv = document.getElementById("display"); targetDiv.innerHTML = outputString; } } var timesheetObject = new Timesheet(timesheet); timesheetObject.display();
Of course, we'll ultimately make it even more modular, letting each project display itself, but this should give you an idea of how this works.
That said, let's take all of this and refactor our existing application to use objects instead of procedural code.
A little cleanup: timesheet.php
First, to make things neater, alter the timesheet.php
file so that it outputs an object called timesheetData
, so we can use the
name timesheet
elsewhere:
... $json = new Services_JSON(); $returnString = $json->encode($timeSheet); echo "var timesheetData = ".$returnString;
And now, the HTML page: the Timesheet class
If we restructure the timesheet.html
page to use objects
rather than procedural HTML, it looks something like this:
<html> <head><title>Timesheet Tracker</title></head> <body> <h1>TimeTracker</h1> <div id="display"></div> <form name="saveForm" id="saveForm" action="http://www.nicholaschase.com/timesheet/savetimesheet_test.php" onsubmit="prepareSaveForm();return true;" method="post"> <input type="hidden" name="timesheet" id="timesheetValue" value="" /> <input type="submit" value="Save Time Information" /> </form> <script type="text/javascript" src="https://www.nicholaschase.com/timesheet/timesheet.php"></script> <script type="text/javascript" src="./json.js" ></script> <script type="text/javascript"> /* ****************** */ /* TIMESHEET */ /* ****************** */ function Timesheet (timesheetIn){ this.username = timesheetIn.user; this.projects = new Array(); for (i = 0; i < timesheetIn.projects.length; i++){ theProject = new Project(timesheetIn.projects[i], i); this.projects[i] = theProject; } this.display = function () { for (i = 0; i < this.projects.length; i++) { this.projects[i].display(); } } this.currentProject = -1; this.startTime = new Date(); } /* ****************** */ /* PROJECT */ /* ****************** */ function project_display(){ displayDiv = document.getElementById("display"); projectDiv = document.createElement("div"); projectDiv.setAttribute("id", this.index); projectDiv.setAttribute("onclick", "activateProject(this)"); projectDiv.appendChild(document.createTextNode(this.project_name + " (")); timeSpan = document.createElement("span"); timeSpan.setAttribute("id", "time"+this.index); timeSpan.appendChild(document.createTextNode(this.time_this_period)); projectDiv.appendChild(timeSpan); projectDiv.appendChild(document.createTextNode("/")); totalSpan = document.createElement("span"); totalSpan.setAttribute("id", "totaltime"+this.index); totalSpan.appendChild(document.createTextNode(this.total_time)); projectDiv.appendChild(totalSpan); projectDiv.appendChild(document.createTextNode(")")); displayDiv.appendChild(projectDiv); } function project_udpateDisplay(status) { var weight = "normal"; if (status == "open") { weight = "bold"; } targetDiv = document.getElementById(this.index); targetDiv.style.fontWeight = weight; spanName = "time"+this.index; targetSpan = document.getElementById(spanName); targetSpan.innerHTML = this.time_this_period; spanName = "totaltime"+this.index; targetSpan = document.getElementById(spanName); targetSpan.innerHTML = this.total_time; } function project_updateData (){ currentTime = new Date(); elapsedTime = Math.floor((currentTime - timesheet.startTime)/1000); timeSoFar = this.time_this_period; this.time_this_period = parseInt(timeSoFar) + parseInt(elapsedTime); this.total_time = parseInt(this.total_time) + parseInt(elapsedTime); } function project_open() { timesheet.startTime = new Date(); timesheet.currentProject = this.index; this.updateDisplay("open"); } function project_close(){ this.updateData(); this.updateDisplay("closed"); } function Project (projectIn, index){ this.project_name = projectIn.project_name; this.time_this_period = projectIn.time_this_period; this.total_time = projectIn.total_time; this.id = projectIn.id; this.index = index; this.display = project_display; this.open = project_open; this.updateDisplay = project_udpateDisplay; this.updateData = project_updateData; this.close = project_close; } /* ******************** */ /* UTILITY FUNCTIONS */ /* ******************** */ function activateProject(clickedProj){ projId = clickedProj.id; if (timesheet.currentProject != -1){ timesheet.projects[timesheet.currentProject].close(); } timesheet.projects[projId].open(); } function prepareSaveForm(){ if (timesheet.currentProject != -1){ timesheet.projects[timesheet.currentProject].close(); } var returnObjectStr = JSON.stringify(timesheet); document.getElementById("timesheetValue").setAttribute("value", URLencode(returnObjectStr)); } function URLencode(sStr) { return escape(sStr).replace(/\+/g, '%2B').replace(/\"/g,'%22').replace(/\'/g, '%27').replace(/\//g,'%2F'); } /* ****************** */ /* MAIN */ /* ****************** */ var timesheet = new Timesheet(timesheetData); timesheet.display(); </script> </body> </html>
Now, I know that's a lot of code, but don't panic; none of it is actually new. We've just moved things around a bit to accomodate the Object-Oriented way of doing things. Let's start at the top and work our way down.
First off, we have our display
div
and the form that's going to
actually send off the data. These appear first, because we'll need to reference the
div
, at least, from the code, so rather than interesperse the script and the
HTML, we've put the HTML first.
Next, we're importing the timesheet data (via timesheet.php
) and the JSON
library, and starting the actual code block.
The code block itself starts with the definition of the Timesheet
class. The user
is easy; we simply use the user
value from
the timesheetData
object, which we'll be feeding to this constructor as timesheetIn
.
The projects
array is a little tricker; we could just assign the array
from the timesheetIn
object, but that will leave us with a collection of
anonymous objects. Instead, we need to create instances of the Project
class, which we'll define in a moment, and add them to the projects
array.
Once we've got the Timesheet
data in place, we create the
display
method. Notice that rather than defining the function and
assigning it, as we did in the previous example, we're creating the function
as part of the class definition. Unless you're going to reuse functions between
classes, the choice of which way to do it is entirely up to you. The function itself is
straightforward. Rather than actually displaying the data, it simply tells each
Project
to display itself.
Finally, we've taken the currentProject
and startTime
values, which were previously global variables, and make them into attributes of
the Timesheet
class. Because we'll only have one Timesheet
object, we can use them like static values.
Now, we've done a lot of defining, but we haven't actually done anything yet. In fact, the entire "main" part of the application consists of only two lines, at the bottom of the file:
var timesheet = new Timesheet(timesheetData); timesheet.display();
We create the Timesheet
object, timesheet
, and tell
it to display itself. Everything else follows from there.
Now let's look at the Project
class.
The Project class
All the Timesheet
class does, when you come right down to it,
is tell the projects to display themselves. So let's take a deeper look at
how that happens.
First, we've defined five functions that we'll use as methods for the class.
The first two, project_display()
and project_updateDisplay()
,
take care of displaying the project information on the page. The actual code is
virtually identical to what we had before, but now we're calling it from the context
of the object. Similarly, project_updateData()
, project_open
and project_close()
perform the same functions as before, manipulating
the data in the object and setting values such as currentProject
and
startTime
, albeit this time from within the timesheet
object.
The actual class definition is pretty straightforward, taking the approprate values and assigning the methods to the functions.
Now let's review
So we call timesheet.display()
, which runs through each Project
in the projects
array and calls its display()
method. That
method creates the div
that we were previously creating by hand, complete
with onclick
attribute, and adds it to the display
div.
When the user clicks on of those div
s, the activateProject()
function closes any open function and opens the clicked function, just as before, but
now all of that code is encapsulated in the Project
class.
Finally, down at the bottom, the prepareSaveForm()
and URLencode()
functions are virtually untouched, acting just as they did before, but now based on
information in the objects rather than global variables.
The fruits of our labor: adding a new project
If you run this code, you'll find that there's no difference from the user's point of view. The page is still displayed with a list of projects, which activate and deactivate when clicked. So why did we bother?
We bothered because now that we've got this structure in place, it becomes easy to add functionality. For example, we can add a button that enables us to create a new project:
... <form name="saveForm" id="saveForm" action="http://www.nicholaschase.com/timesheet/savetimesheet_test.php" onsubmit="prepareSaveForm();return true;" method="post"> <input type="hidden" name="timesheet" id="timesheetValue" value="" /> <input type="submit" value="Save Time Information" /> <input type="button" value="Create New Project" onclick="addProject()" /> </form> <script type="text/javascript" src="https://www.nicholaschase.com/timesheet/timesheet.php"></script> <script type="text/javascript" src="./json.js" ></script> <script type="text/javascript"> /* ****************** */ /* TIMESHEET */ /* ****************** */ function timesheet_addProject(){ var projectName = prompt('Please enter a project name: '); var newProject = new BlankProject(); newProject.project_name = projectName; numProjects = timesheet.projects.length; newProject = new Project(newProject, numProjects); timesheet.projects[newProject.index] = newProject; newProject.display(); } function Timesheet (timesheetIn){ this.username = timesheetIn.user; this.projects = new Array(); for (i = 0; i < timesheetIn.projects.length; i++){ theProject = new Project(timesheetIn.projects[i], i); this.projects[i] = theProject; } this.display = function () { targetDiv = document.getElementById("display"); targetDiv.innerHTML = ""; for (i = 0; i < this.projects.length; i++) { this.projects[i].display(); } } this.addProject = timesheet_addProject; this.currentProject = -1; this.startTime = new Date(); } /* ****************** */ /* PROJECT */ /* ****************** */ ... this.updateDisplay = project_udpateDisplay; this.updateData = project_updateData; this.close = project_close; } function BlankProject() { this.projectName = "undefined"; this.time_this_period = 0; this.total_time = 0; this.id = -1; this.index = 0; } /* ******************** */ /* UTILITY FUNCTIONS */ /* ******************** */ ... document.getElementById("timesheetValue").setAttribute("value", URLencode(returnObjectStr)); } function addProject(){ timesheet.addProject(); } function URLencode(sStr) { ...
We start with a button that, when clicked, calls the addProject()
function. That function (at the bottom, with the other utility functions), calls timesheet.addProject()
. We mapped the addProject()
method to timesheet_addProject()
.
Now, we want to create a new Project
object, but the constructor
expects an object and an index. We'll oblige by creating a new BlankProject
object and populating it with the name provided by the user in the JavaScript prompt. We'll get the index by checking how many projects the timesheet already has.
Once we have the feeder object and the index, we can simply create the Project
, and add it to the timesheet
, and tell it to display itself. No muss, no fuss.
We do have to say the new project, however. Fortunately, it simply gets added to the timesheet
object, so sending it to the server doesn't involve any extra work. We just need to deal with it once it gets to the savetimesheet.php
file:
... for ($i=0; $iid; $project_name = $thisProject->project_name; $total_time = $thisProject->total_time; $time_this_period = $thisProject->time_this_period; if ($id == -1) { $sql = "insert into projects2 (project_name, time_this_period, total_time) values ('". $project_name."', ".$total_time.", ".$time_this_period.")"; } else { $sql = "update projects2 set total_time = ".$total_time.", time_this_period = ".$time_this_period." where id = ".$id; } $result = mysql_query($sql) or die('Query failed: ' . mysql_error()); if ($result){ echo "Project '".$project_name."' updated.
"; } else { echo "Nothing updated."; } ...
Because the BlankProject
objects get created with an id
of -1
, they are easy to identify and insert into the database rather than simply updating.
[SECURITY NOTE: I've done the database this way to keep things simple, but using user supplied strings in the SQL statement is an invitation to disaster. See the PHP documentation for much better way to handle your data.]
Similarly, clearing the current periods time involves just a few simple steps:
... <form name="saveForm" id="saveForm" action="http://www.nicholaschase.com/timesheet/savetimesheet_test.php" onsubmit="prepareSaveForm();return true;" method="post"> <input type="hidden" name="timesheet" id="timesheetValue" value="" /> <input type="submit" value="Save Time Information" /> <input type="button" value="Create New Project" onclick="addProject()" /> <input type="button" value="Clear Time Data" onclick="clearTime()" /> </form> <script type="text/javascript" src="https://www.nicholaschase.com/timesheet/timesheet.php"></script> <script type="text/javascript" src="./json.js" ></script> <script type="text/javascript"> /* ****************** */ /* TIMESHEET */ /* ****************** */ ... function Timesheet (timesheetIn){ ... this.addProject = timesheet_addProject; this.clearTime = timesheet_clearTime; this.currentProject = -1; this.startTime = new Date(); } /* ******************** */ /* UTILITY FUNCTIONS */ /* ******************** */ ... function addProject(){ timesheet.addProject(); } function clearTime(){ timesheet.clearTime(); } function URLencode(sStr) { return escape(sStr).replace(/\+/g, '%2B').replace(/\"/g,'%22').replace(/\'/g, '%27').replace(/\//g,'%2F'); } ...
The button calls clearTime()
, which in turn calls timesheet.clearTime ()
. The timesheet died clearTime ()
function simply loops through each project, sets its time_this_.
20, and tells the project to update its own display.
At this point, we have a fully functional, object-oriented timesheet application. You can use this, as is, to track how you spend your day.
Or, you can turn it into a widget, which we'll do next.