- 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 fully functional, object-oriented timesheet tracker application that accesses a remote database to load and save data. The "object-oriented" part of that description is important, because it's going to enable us to easily port our application to a whole new medium.
The new medium is Yahoo! Widgets. Formerly known as Konfabulator Widgets, these small applications are cross-platform (for Windows and Mac, at least) and based on XML and JavaScript. They're also able to do a lot of things you can't necessarily do with HTML and JavaScript in the browser, such as monitoring system activity.
Widgets also provide you with an opportunity to be more artistic than your typical web page. For example, when you first install the Yahoo! Widget Engine (which you should download from http://widgets.yahoo.com/download/) It opens a variety of widgets for you. Notice the beautiful curves, the artful design. Widgets handle graphics beautifully, so if you can create images like this, your widgets will be beautiful too.
Unfortunately, I has so no such skill, so for the duration of this project you will have to content yourself with a good explanation of how to make things happen, backed by whatever I can coax out of Photoshop. [Note to self: check out Matt Kloskowski's Photoshop Reference Guide.] But I don't think anybody's ever died of bad art work, so we should be okay.
Let's start by creating and running a basic widget. The first step is to install the Yahoo! Widget Engine, if you haven't already. Make sure you get the actual engine, and not just the SDK. When you install the engine, as I mentioned earlier, you'll see a number of different widgets on your screen. Don't worry, you're not stuck with them cluttering up your desktop. You can right-click on each and choose "Close Widget," but you might want to take a moment to see what they are and the types of things they can do, such as track the CPU load of your computer or monitor weather reports via Web services.
Each of these widgets is contained in a *.widget
file. (The installer
puts them in the My Widgets folder in the My Documents, but that's not required.) The
.widget
file is actually a zip file in disguise. In fact, if you change
the name, you can open it right up using any zip utility. When you do, you'll see a
number of files and folders, arranged in a hierarchy such as:
MyWidget.widget Contents MyWidget.kon //the main controller file OtherCode.js //other scripts may also be included Resources //images and other resource files Background.png house.png
In actuality, all you really need is the .kon
file, but to distribute
your widget you'll need to package it appropriately.
Let's start with a simple widget that includes a couple of images and some text:
<?xml version="1.0" encoding="UTF-8"?> <widget> <debug>on</debug> <window> <name>main_window</name> <title>Time Tracker</title> <height>360</height> <width>360</width> <vOffset>100</vOffset> <visible>true</visible> <image src="Resources/background.png"> <name>background</name> </image> <image src="Resources/save.png"> <name>saveButton</name> <hOffset>35</hOffset> <vOffset>300</vOffset> </image> <image src="Resources/clear.png"> <name>clearButton</name> <hOffset>135</hOffset> <vOffset>300</vOffset> </image> <image src="Resources/add.png"> <name>addButton</name> <hOffset>235</hOffset> <vOffset>300</vOffset> </image> <text> <name>myText</name> <color>#FF0000</color> <size>36</size> <alignment>left</alignment> <vOffset>30</vOffset> <hOffset>2</hOffset> <data>Time Tracker</data> </text> </window> </widget>
What we have here is an XML file that defines the widget. All widgets have a
widget
root element, in which all of the other XML and JavaScript resides. In this case, I've turned on debugging using the debug
element. This makes it easier not only to see what's going on, but also to reload the widget after we make changes. (You'll see the debug window in a moment, when we run the widget.
Inside the widget
element we are defining a window
. A single
widget may have more than one window, but we'll keep it simple with just one. The window
itself has a number of different attributes, primary among them the name
,
which acts as a unique identifier for the element, and title and display information,
such as the vertical and horizontal offset. We can also control whether the window
(or any other object, for that matter) is visible. Right now, we've defined the visible
value using XML, but we can actually control it using JavaScript, which you'll see in a moment.
Inside the window
, we've defined four images and a text object. Starting with
the images, notice that we have both attributes and elements. Widgets can be defined
using either, or both. For example, the element:
<image src="Resources/save.png"> <name>saveButton</name> <hOffset>35</hOffset> <vOffset>300</vOffset> </image>
could also have been
<image> <src>Resources/save.png</src> <name>saveButton</name> <hOffset>35</hOffset> <vOffset>300</vOffset> </image>
or
<image src="Resources/save.png" name="saveButton" hOffset="35" vOffset="300" />
The widget engine will accept any combination of attributes and elements, with a few notable exceptions such as defining JavaScript functions.
To run the widget, you can simply double click the .kon
file. Because this
is the first time you're running the widget, you'll get a warning:
Click of the "allow" button to see the widget:
Because we included the debug
element with a value of on
, you can see both the widget itself and the debug window. To close the widget, you can either click the Close Widget
button, or right-click the widget itself and choose Close Widget
.
Before we get too complicated, let's look at adding a little bit of interactivity. We can do this with the help of two handy features of widgets. One is the ability to specify event handlers on objects, and the other is the ability to programmatically handle objects that have been defined as elements. For example, we can tell the widget to make the text disappear when we click the "clear" button:
... <image src="Resources/clear.png"> <name>clearButton</name> <hOffset>135</hOffset> <vOffset>300</vOffset> <onMouseUp> myText.visible = false; </onMouseUp> </image> ... <text> <name>myText</name> <color>#FF0000</color> ... </text> </window> </widget>
To see the changes, simply click in the Reload
button on the debug window.
Because you've made changes to the code, you will get the warning once more. Click "Allow". The widget will initially look just as it did before, but if you click the "Clear" button, you'll see the text disappear:
Now let's look at what we did here. First, we defined an event --
and the onMouseUp
event -- and added JavaScript right into it. In this case, we
have a very simple script, but in the case of something more substantial, we'll enclose the
code in a CDATA section to prevent problems. This is an XML file, after all.
Now let's look at the code itself. First off, notice that the text actually exists as
an object with the name that we gave it, myText
. Notice also that we were able
to control one of that object attributes -- visible
-- directly from the JavaScript. We can also create objects directly from JavaScript:
... </text> </window> <action trigger="onLoad"><![CDATA[ var button1 = new Image(); button1.src = "Resources/button.png"; button1.hOffset = 35; button1.vOffset = 55; var button2 = new Image(); button2.src = "Resources/button.png"; button2.hOffset = 35; button2.vOffset = 95; var button3 = new Image(); button3.src = "Resources/button.png"; button3.hOffset = 35; button3.vOffset = 135; var project1 = new Text(); project1.data = "InformIT Column"; project1.size = 18; project1.color = "#FF0000"; project1.hOffset = 50; project1.vOffset = 80; var project2 = new Text(); project2.data = "Web Services Proposals"; project2.size = 18; project2.color = "#FF0000"; project2.hOffset = 50; project2.vOffset = 120; var project3 = new Text(); project3.data = "Miscellaneous"; project3.size = 18; project3.color = "#FF0000"; project3.hOffset = 50; project3.vOffset = 160; ]]> </action> </widget>
You see that we have created three images and three text objects, and set their attributes programmatically. We've done this in the onLoad
action. The action
element enables you to define what should happen for a variety of actions. In this case, we are adding code to be executed when the widget first loads. If you reload the widget, you'll see both the graphics and text in place:
OK, let's review where we are at this moment. We have a widget, into which we can add various elements using both XML and JavaScript. Using JavaScript, we can control those elements/objects and their attributes. All this means it should be relatively straightforward to add our Timetracker application to the widget. After all, the logic will remain the same; we only need to change the methods of display. Let's see how that works:
<?xml version="1.0" encoding="UTF-8"?> <widget> <debug>on</debug> <window> <name>main_window</name> <title>Time Tracker</title> <height>360</height> <width>360</width> <vOffset>200</vOffset> <visible>true</visible> <image src="Resources/background.png"> <name>background</name> </image> <image src="Resources/save.png"> <name>saveButton</name> <hOffset>35</hOffset> <vOffset>300</vOffset> <onMouseUp> prepareSaveForm(); </onMouseUp> </image> <image src="Resources/clear.png"> <name>clearButton</name> <hOffset>135</hOffset> <vOffset>300</vOffset> <onMouseUp> timesheet.clearTime(); </onMouseUp> </image> <image src="Resources/add.png"> <name>addButton</name> <hOffset>235</hOffset> <vOffset>300</vOffset> <onMouseUp> timesheet.addProject(); </onMouseUp> </image> </window> <action trigger="onLoad"> <![CDATA[ include ("json.js"); var timesheetSource = new URL(); timesheetSource.location = "http://www.nicholaschase.com/timesheet/timesheet_widget.php"; var timesheetString = timesheetSource.fetch(); var timesheetData = JSON.parse(timesheetString); /* ****************** */ /* TIMESHEET */ /* ****************** */ function timesheet_clearTime(){ for (i = 0; i < this.projects.length; i++) { this.projects[i].time_this_period = 0; if (this.currentProject != i){ this.projects[i].updateDisplay("closed"); } else { this.projects[i].updateDisplay("open"); } } } 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_save(){ var returnObjectStr = JSON.stringify(timesheet); var timesheetSink = new URL(); timesheetSink.location = "http://www.nicholaschase.com/timesheet/savetimesheet_widget.php"; timesheetSink.postData = "timesheet="+returnObjectStr; var responseString = timesheetSink.fetch(); alert(responseString); } function Timesheet (timesheetIn){ this.user = 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.addProject = timesheet_addProject; this.clearTime = timesheet_clearTime; this.save = timesheet_save; this.currentProject = -1; this.startTime = new Date(); } /* ****************** */ /* PROJECT */ /* ****************** */ function project_display(){ projectButton = new Image(); projectButton.src = "Resources/button.png"; projectButton.vOffset = 35 + (this.index * 40); projectButton.hOffset = 35; projectButton.name = "button"+this.index; projectButton.onMouseUp = "activateProject("+this.index+");" projectButton.opacity = 128; this.button = projectButton; projectName = new Text(); projectName.data = this.project_name; projectName.name = "projectName"+this.index; projectName.color = "#FF0000"; projectName.size = 18; projectName.vOffset = 60 + (this.index * 40); projectName.hOffset = 40; projectName.opacity = 128; this.projectname = projectName; projectTime = new Text(); projectTime.data = "("+this.time_this_period+" / "+this.total_time+")";; projectTime.name = "projectTime"+this.index; projectTime.color = "#FF0000"; projectTime.size = 18; projectTime.vOffset = 60 + (this.index * 40); projectTime.hOffset = 240; projectTime.opacity = 128; this.projecttime = projectTime; } function project_open() { timesheet.startTime = new Date(); timesheet.currentProject = this.index; this.updateDisplay("open"); } function project_udpateDisplay(status) { this.projecttime.data = "("+this.time_this_period+" / "+this.total_time+")";; var opacityValue = 128; if (status == "open") { opacityValue = 255; } this.button.opacity = opacityValue; this.projectname.opacity = opacityValue; this.projecttime.opacity = opacityValue; } 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); timesheet.startTime = currentTime; } 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; } function BlankProject() { this.projectName = "undefined"; this.time_this_period = 0; this.total_time = 0; this.id = -1; this.index = 0; } /* ******************** */ /* UTILITY FUNCTIONS */ /* ******************** */ function activateProject(clickedProj){ projId = clickedProj; if (timesheet.currentProject != -1){ timesheet.projects[timesheet.currentProject].close(); } timesheet.projects[projId].open(); } function prepareSaveForm(){ if (timesheet.currentProject != -1){ timesheet.projects[timesheet.currentProject].close(); } timesheet.save(); } 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(); ]]> </action> </widget>
Again, I know this is a lot of code, but I want you to see both what has changed and, almost more importantly, what has not changed. This code is virtually identical to the code we used in the HTML version. It is changed in only two places: accessing the data, and displaying the data.
Starting at the beginning of the process, we see that rather than simply using
script
tags to include the data, we have to specifically request it using the
built-in URL
object. The timesheet_widget.php
file is identical to
timesheet.php
, except that instead of returning a statement ( timesheet
space = space <object data>
) it simply returns the data itself:
... $json = new Services_JSON(); $returnString = $json->encode($timeSheet); echo $returnString; ?>
(In fact, in a production application, I'd move the common code to an include file for easier maintenance.)
We can then use JSON to parse a string into an object. (Make sure that the json.js
file is available in the same directory as the .kon
file.)
Similarly, rather than using an HTML form to save the data, we use the URL
object to post the data directly in the timesheet.save()
method. The savetimesheet_widget.php
file is also identical to its predecessor except in one respect: the widget adds slashes to "escape" all of the quotes in the data, so we have to strip them out before we can process it:
... require_once("JSON.phps"); $json = new Services_JSON(); $stripped = stripslashes(urldecode($_POST['timesheet'])); $timesheet = $json->decode($stripped); ...
That takes care of data access. The only other difference is in the presentation. We handle all of that in the Project.display()
and Project.updateDisplay()
methods. Rather than creating div
and span
elements, we are creating Image
and Text
objects, which we directly assign to the Project
. We start them out with only half of their normal opacity, and when we open a project, we highlight it by setting it to full opacity:
Everything works as before, right on down to adding a new project and using the same data that we used from the web page.
Before we wrap it up, let's add one final twist. Widgets also allow us to add timers, so we can set it to update the display once a minute:
... </onMouseUp> </image> </window> <timer> <name>timer</name> <interval>60</interval> <ticking>true</ticking> <onTimerFired> if (timesheet.currentProject != -1){ timesheet.projects[timesheet.currentProject].updateData(); timesheet.projects[timesheet.currentProject].updateDisplay("open"); } </onTimerFired> </timer> <action trigger="onLoad"> <![CDATA[ include ("json.js"); ...
Every 60 seconds, the widget will now tell the current project to update its current data based on the current time, and then update its display.
So that's it. We have gone from a simple DHTML and JavaScript page that uses JSON to pass data back and forth to an object-oriented time tracker to a widget that you can not only display on your desktop but also distribute.
Really, we've only scratched the very barest surface of what widgets can do. I strongly recommend that you check out the reference manual to see all of the properties and actions that are available to you. Any of them are manipulatable using the techniques we have learned here. And if you do put one together, please let me know. I'd be curious to see what comes from this.
Happy coding!