Installing and Running the eHow Earnings Tracker

To install the eHow Earnings Tracker, click on this link.  That will take you to the following page:

install1

Because the add-on hasn’t yet gone through the approval process to make it public, you need to check the box next to “Let me install this experimental add-on”.  That will enable the “Add to Firefox” button:

let me install

Click on “Add to Firefox” and you will be taken to the “please donate” page:

roadblock

Once again, click on the checkbox and then “Add to Firefox”.  That will take you to the end user license agreement:

license agreement

Click on “Accept and Install” and you will get the Software Installation dialog:

install dialog

Click on “Install Now” and the tracker will be installed.  After installation, you will be prompted to restart Firefox:

restart

Click on “Restart Firefox”.  After Firefox restarts, you should see a dialog box telling you that the tracker was installed:

add on has been installed

The eHow Earnings Tracker has now been installed, so close the dialog box show above and log onto your eHow account.  Go to “My Profile” and then click on the “Articles” tab:

ehow page

Now you can run the tracker by going to Tools – eHow Earnings Tracker – Update Earnings or by right-clicking somewhere on the page:

tracker menu

You should see the screen flash a few times as the tracker navigates through your article library.  When the tracker is finished, it will open a new window with your earnings data in it:

tracker run

eHow Earnings Tracker Version 1.0.1 Available

Version 1.0.1 of the eHow Earnings Tracker is available on the downloads page.  This version has all of the major functionality that I had planned.

Changes since 0.4.0:

*==> Version 1.0.1 (August 5, 2009)

* Fixed bug with Tracker not working if the Windows user name had an apostrophe in it

*==> Version 1.0.0 (August 3, 2009)

* Added ability to toggle table display between earnings and views

*==> Version 0.6.0 (August 2, 2009)

* Added column sorting
* View table still gone, will be coming back soon!

*==> Version 0.5.0 (July 30, 2009)

* Added ability to select a date range
* Views table is gone, will return next version

Using the JavaScript Date Object

Since this is my first JavaScript blog post, I need to take a moment to recommend this book:

As far as I’m concerned, this is the JavaScript book to get.  It explains the language at a level of detail that you simply won’t find reading online tutorials and samples.  It covers everything from basic language syntax to AJAX.  It has sections on regular expressions, XML, DOM scripting, and client-side graphics.  It also has great reference sections for the various JavaScript objects: Date, Array, Number, etc.  I am constantly referring to it whenever I’m programming in JavaScript.

Anyway, the purpose of this post is to show a couple of neat things that I discovered about the JavaScript Date object that aren’t readily apparent from most of the samples and tutorials that you’ll find.  w3schools.com has a pretty good Date object reference if you need one.

One thing that you need to keep in mind when dealing with the JavaScript Date object is that the integer value for months is 0 based, not 1 based.  This means that January = 0, February = 1, etc.  For example, the following code:

var today = new Date();  // Creates a date object set to the current date and time
alert( today.toDateString() + "\nMonth integer = " + today.getMonth());

Will show the following message:

dateinteger

What’s really cool about the JavaScript Date object is that it does date math for you.  For example, we could set a date object to be January 1, 2009 and subtract 7 days from it:

var jan1_2009 = new Date(2009, 0, 1);  // Creates a date object set to the January 1, 2009
var oneWeekBefore = new Date(2009, 0, 1 - 7);  // Creates a date object set to the January 1, 2009 minus 7 days

alert("One week before " + jan1_2009.toDateString() + " is " + oneWeekBefore.toDateString());

var oneWeekBefore2 = new Date(2009, 0, 1);  // Creates a date object set to the January 1, 2009
oneWeekBefore2.setDate( oneWeekBefore2.getDate() - 7);  // Subtract 7 days

alert("One week before " + jan1_2009.toDateString() + " is " + oneWeekBefore.toDateString());

Both alerts in the code above would show the following:

oneweekdate

You can also use the Date object to figure out how many days are in a given month.  This is particularly useful for determining how many days are in February for a given year.  When you create a Date object with a year, month, and a day of 0 then Date object automatically sets itself to the last day of the previous month.  Remembering that month integers are zero based:

var day = new Date(2009, 2, 0);  // Sets the date to the 0th day in March, which is the last day in February.

alert(day.toDateString() + " : February of 2009 has " + day.getDate() + " days.");

This will display:

daysinmonth

Programming with XML in Firefox Add-ons

The eHow Earnings Tracker stores its data in XML format, so one of the things that I needed to learn how to do was program with XML in Firefox Add-ons.  Mozilla has a whole section of code snippets dedicated to XML but I will walk through exactly what I did in the development of the tracker and point out some gotchas that I figured out along the way.

The first step in this process was to figure out how to create an XML DOM tree in memory.  This turned out to be pretty straightforward to do thanks to Mozilla’s How to create a DOM tree documentation.  I wound up with the following code to create my basic XML DOM hierarchy:

var createEarningsDOM = function()
{
    m_trackerDOM = document.implementation.createDocument(null, null, null);

    var trackerNode = m_trackerDOM.createElement(m_xmlNodeTracker);
    trackerNode.setAttribute(m_xmlAttrVersion, m_xmlVersion);

    m_articleListElem = m_trackerDOM.createElement(m_xmlNodeArticleList);
    trackerNode.appendChild(m_articleListElem);

    m_statsElem = m_trackerDOM.createElement(m_xmlNodeStats);
    trackerNode.appendChild(m_statsElem);

    m_trackerDOM.appendChild(trackerNode);
};

You should notice a couple of things about the source code above.  First, I create variables to hold my node and attribute names.  This ensures consistency when I refer to particular nodes and attributes throughout my code.  It also means that if I decide to change the string for a node or attribute name, I only need to do it in one spot.  The second thing is that I create variables to hold references to DOM nodes so that I can easily manipulate them later in the code.

Now that I was able to create an XML DOM tree in memory, it was time to figure out how to write it out to a file.  Mozilla has some decent documentation on parsing and serializing XML.  I combined the information there with what I learned about file I/O and wrote the following code to serialize my XML DOM to a file:

var s = new XMLSerializer();
XML.prettyIndent = 4;
var xmlString = XML(s.serializeToString(m_trackerDOM)).toXMLString();

if(!FileIO.write(m_earningsFile, xmlString))
{
    throw Error("Failed to save file " + m_earningsFile.path);
}

The “pretty” serialization for XML strings is really cool because it formats the XML so that it’s very readable.  It makes life a lot easier when you need to look at the contents the XML file.

Now that I was able to write out XML to a file, I needed a way to read it back in again later.  I used the DOMParser.  The one thing to watch out for with the DOMParser is that when the parsing fails, it doesn’t throw an exception.  Instead, it returns an XML document that contains the parsing error.

This is the code I came up with for loading the XML DOM from a file:

var fileContents = FileIO.read(currentFile);

if(fileContents)
{
    logMessage("\tParsing file");
    var parser = new DOMParser();
    m_trackerDOM = parser.parseFromString(fileContents, "text/xml");

    logMessage("\tLooking for " + m_xmlNodeArticleList);
    m_articleListElem = m_trackerDOM.getElementsByTagName(m_xmlNodeArticleList)[0];

    if(!m_articleListElem)
    {
        // XML parsing failed, grab error
        var s = new XMLSerializer();
        XML.prettyIndent = 4;
        logMessage(XML(s.serializeToString(m_trackerDOM)).toXMLString());                    
        throw Error("Failed to parse XML data file");
    }
}

In this piece of code, I read in the file contents and parse it, attempting to find the DOM element that I’m looking for.  If I don’t find the element then something has gone wrong – either the parsing failed, or the XML is not what I’m expecting it to be.  In either case, I log the results of the parseFromString() function to my log file so that I know what went wrong.

How to Host WPF Content in MFC Applications

This is something that I figured out a while back but wanted write about it here since I spent a few hours piecing together the information.

There is an MSDN Walkthrough that gets you most of the way there, but there are a couple of key pieces that I found elsewhere. For example, the walkthrough tells you to place the line [System::STAThreadAttribute] before the _tWinMain() definition but if you’re implementing a standard MFC application then you don’t have _tWinMain() in your source code.

Step 1: Configure the MFC application to compile with CLR support

The best way to achieve interoperability between native C++ and managed .NET code is to compile the application as managed C++ rather than native C++. This is done by going to the Configuration Properties of the project. Under General there is an option “Common Language Runtime support”. Set this to “Common Language Runtime Support /clr”.

Step 2: Add the WPF assemblies to the project

Right-click on the project in the Solution Explorer and choose “References”. Click “Add New Reference”. Under the .NET tab, add WindowsBase, PresentationCore, PresentationFramework, and System. Make sure you Rebuild All after adding any references in order for them to get picked up.

Step 3: Set STAThreadAttribute on the MFC application

WPF requires that STAThreadAttribute be set on the main UI thread. Set this by going to Configuration Properties of the project. Under Linker->Advanced there is an option called “CLR Thread Attribute”. Set this to “STA threading attribute”.

Step 4: Create an instance of HwndSource to wrap the WPF component

System::Windows::Interop::HwndSource is a .NET class that handles the interaction between MFC and .NET components. Create one using the following syntax:

System::Windows::Interop::HwndSourceParameters^ sourceParams = gcnew     System::Windows::Interop::HwndSourceParameters("MyWindowName");
sourceParams->PositionX = x;
sourceParams->PositionY = y;
sourceParams->ParentWindow = System::IntPtr(hWndParent);
sourceParams->WindowStyle = WS_VISIBLE | WS_CHILD;

System::Windows::Interop::HwndSource^ source = gcnew System::Windows::Interop::HwndSource(*sourceParams);
source->SizeToContent = System::Windows::SizeToContent::WidthAndHeight;

Add an HWND member variable to the dialog class and then assign it like this: m_hWnd = (HWND) source->Handle.ToPointer();  The source object and the associated WPF content will remain in existence until you call ::DestroyWindow(m_hWnd).

Step 5: Add the WPF control to the HwndSource wrapper


</strong>System::Windows::Controls::WebBrowser^ browser = gcnew System::Windows::Controls::WebBrowser();

browser->Height = height;
browser->Width = width;
source->RootVisual = browser;<strong>

Step 6: Keep a reference to the WPF object

Since the browser variable will go out of scope after we exit the function doing the creation, we need to somehow hold a reference to it. Managed objects cannot be members of unmanaged objects but you can use a wrapper template called gcroot to get the job done.

Add a member variable to the dialog class:


<span>#include <vcclr.h></span>
<pre><code><span>gcroot</span><span><</span><span>System</span><span>::</span><span>Windows</span><span>::</span><span>Controls</span><span>::</span><span>WebBrowser</span><span>^></span><span> m_webBrowser</span><span>;</span><span> </span></code>

Then add the following line to the code in Step 5:

<code><span>m_webBrowser </span><span>=</span><span> browser</span><span>;</span><span> </span></code>

Now you can access properties and methods on the WPF component through m_webBrowser.

File I/O with Firefox Add-ons

One of the major pieces of the eHow Earnings Tracker involves writing data to an XML file and reading it back later.  Figuring out how to do file I/O in a Firefox add-on was not straightforward due to Mozilla’s wide array of Chrome APIs and spotty documentation.

Unfortunately, file I/O is not one of Mozilla’s new FUEL APIs.  FUEL is a Javascript Library available to Firefox add-ons that is far easier to use than the older XPCOM API.  It was introduced in Firefox 3.0 and Mozilla has been slowly adding functionality to it.

Starting with Mozilla’s file I/O code snippets was a quite confusing – the documentation meanders all over the place showing bits and pieces of code without explaining anything in much depth.  On top of that, they suggest that you use the io.js wrappers at the beginning of the page but then none of the examples shown use it.

Basically what it comes down to is that file I/O is done by using the XPCOM interfaces nsIFile and nsILocalFile.  The io.js wrappers are utility functions used to encapsulate the tedious syntax needed to use them.  I don’t fully understand the purpose of everything in io.js but I will show you what I did figure out and ultimately use in the tracker implementation.

The first thing that I needed to do was get a hold of the current Firefox profile directory since that’s where I wanted to store my data.  I chose the current profile directory because I wanted to be able to support multiple Firefox profiles using the tracker without overwriting each other’s data.

I wound up with the following function to do it:

var getProfileDir = function()
{
    var dir = DirIO.get('ProfD');
    if(!dir || !dir.exists())
    {
        throw Error("Failed to open profile directory");
    }

    return dir;
};

What this code does is create an nsIFile object that represents the profile directory.  ‘ProfD’ is a special string that refers to the profile directory.  You can see a list of the supported strings in the file I/O code snippets.  If you want to open a specific directory path, use DirIO.open() instead of get().

Once you have an nsIFile object, you can do a bunch of things with it.  The following code snippet will try to open a file in the profile directory and create it if it doesn’t already exist:

var openEarningsBackupFile = function(username)
{
    var currentFile = getProfileDir();
    currentFile.append(username + ".xml");

    if(!currentFile.exists())
    {
        if(!FileIO.create(currentFile))
        {
            throw Error("Failed to create earnings backup file");
        }
    }
    return currentFile;
}

The append function of the nsIFile object lets you navigate a file hierarchy one level at a time.  You could go down multiple levels like so:

currentFile.append("path1");
currentFile.append("path2");
currentFile.append("file.xml");

This would navigate to [currentFile.path]\path1\path2\file.xml.  The create() function will create any path segments that don’t already exist.

Keep in mind that the append function modifies the calling object so if you want to open up two different files in a particular directory then the easiest way to do it is to create an nsIFile object that points to the directory and then clone it:

var file1 = getProfileDir();
var file2 = file1.clone();
file1.append("file1.xml");
file2.append("file2.xml");

Once you have an nsIFile object pointed to an existing file you can read from and write to it.  The following copy function shows how to do reads and writes with nsIFile objects using the io.js wrappers:

// srcFile and destFile should be nsIFile objects
var copyFile = function(srcFile, destFile)
{
    var srcText = FileIO.read(srcFile);

    if(!srcText)
    {
        throw Error("Failed to read " + srcFile.path);
    }

    if(!FileIO.write(destFile, srcText))
    {
       throw Error("Failed to copy " + srcFile.path + " to " + destFile.path);
    }
 };

Another not-so-obvious thing to figure out was how to get the path where the add-on is installed.  After quite a bit of searching, I came up with this bit of code:

var getExtensionDir = function()
{
    var dir = Components.classes["@mozilla.org/extensions/manager;1"].getService(Components.interfaces.nsIExtensionManager).getInstallLocation(m_extensionId).getItemLocation(m_extensionId);

    if(!dir)
    {
        throw Error("Failed to get extension installation directory");
    }

    return dir;
};

File I/O in Firefox add-ons is pretty easy to do once you see how all of the pieces fit together.


				

eHow Earnings Tracker Version 0.4.0 Available

Yet another version of the eHow Earnings Tracker has been uploaded to to the Firefox Addons site. Head over to the downloads page to get it.

Changes since 0.3.0:

*==> Version 0.4.0 (July 13, 2009)

* Added “Totals” and “Change from previous day” rows for earnings and views

*==> Version 0.3.1 (July 13, 2009)

* Fixed problem with URL parsing when users had dashes(-) in their name.

eHow Earnings Tracker Version 0.3.0 Available

Another version of the eHow Earnings Tracker has been uploaded to to the Firefox Addons site. Head over to the downloads page to get it.

Changes since Version 0.2.0:

*==> Version 0.3.0 (July 13, 2009)

* Added “Copy Earnings Data to Desktop” menu option
* Added Views to HTML report

*==> Version 0.2.3 (July 12, 2009)

* Additional logging
* Fixed issue with invalid XML characters getting stored as part of titles
* Added “Delete Earnings Data” menu option

*==> Version 0.2.2 (July 12, 2009)

* Fixed bug related to addition/removal of articles from the article library

*==> Version 0.2.1 (July 12, 2009)

* Added more error logging
* A backup earnings.xml file is now created on a successful update

eHow Earnings Tracker Version 0.2.0 Available

I just uploaded the latest version of the eHow Earnings Tracker to the Firefox Addons site. Head over to the downloads page to get it.

Changes since 0.1.1:

*==> Version 0.2.0 (July 12, 2009)

* eHow earnings report now opens in its own tab
* Added styling to $0.00 value fields so that they are italics with gray font
* Added eHow graphics & color scheme
* Added “View Last Update” which views the last HTML stats page rather than regenerating it from eHow article earnings pages
* Added error logging and menu item to copy log file to desktop

*==> Version 0.1.2 (July 9, 2009)

* Added CSS styling to HTML report

Once development work settles down I’ll write some more posts about the tracker and how to write Firefox Addons.

eHow Earnings Tracker : Initial Release (Version 0.1.1)

So, it’s done – the initial release of the eHow Earnings Tracker is available from the Mozilla Addons site.  Head on over to my downloads page for more details on obtaining the addon.  The tracker is currently classified as “experimental” which is just Mozilla’s way of saying that they haven’t reviewed it and placed their stamp of approval on it.  Apparently there are over 400 addons in the review queue so that won’t be happening any time soon :) .

I must say that overall, it was a very smooth process to get my addon hosted by Mozilla.  I just had to create an account, fill out a few fields, and upload the addon.  The hardest part was deciding which software license I wanted to release my code  under.  The legalese was overwhelming and I just picked the simplified BSD license.  I have no idea if it was the right choice or not.

The tracker is currently pretty barebones, but it is functional from the limited testing I did with it.  A few fellow eHow writers (Carol McKenzie, Dianne Cass, and knyc) were kind enough to volunteer their services in testing it out for a few days before I start broadcasting its availability to the eHow community.

What it Does

As I originally mentioned, eHow gives writers very limited earnings data.  Currently there is no way to writers to figure out which articles get income on a day to day basis without doing a whole lot of manual work.  The tracker collects data each time that it is run and keeps a history so that writers can see earnings increase over time on a per article basis.  Since earnings updates only occur at most once per day, the tracker keeps data on a per day basis.  This means that if the tracker is run multiple times during a single day, it only keeps the last set of data for that day.

Using the tracker is very easy.  Upon installation, it adds submenus to both the main Tools menu and the context menu.  The user simply needs to navigate to one of their article earnings pages and select the menu option “Update Earnings”.  The tracker will then read all of the earnings pages and generate an HTML report.  The HTML report is currently very barebones and ugly but the plan is to fix that up.

tracker context menueHow Earnings Tracker context menu

How it Works

The tracker works by first examining the URL of the web page that’s currently in the browser.  eHow has very specific URL formats for the earnings pages so the tracker can figure out whether or not it can be run.

Once it is run, the tracker examines the HTML of the page and extracts data based on HTML elements and CSS class names.  It can also figure out how to navigate to the other earnings pages by using the “Next” anchor link at the bottom of the page and gathers information off each page as it loads.

ehow earnings html structureHTML structure of the eHow article earnings pages

The tracker stores its data in an XML file in the Firefox profiles directory.  If you’re curious, you can track it down by looking in the same directory as the URL that appears in the HTML report after the tracker is run.  The file is called eHowEarningsTracker_17of26_earnings_[ehow user name].xml.

Caveats

The tracker requires Firefox 3.5 because I needed to use some functionality that only became available in 3.5.

The tracker can only collect data when it is explicitly run.  Since it can only read what’s in the rendered HTML pages, it can’t know anything that the user doesn’t see.  This means that if the tracker will never have data for days that it wasn’t run.

This tool is completely tied to eHow’s HTML code.  If eHow does anything to change the HTML layout of their earnings pages then the tracker will not be able to successfully read any more data until I update it to handle the new HTML layout.  However, all of the past collected data would still be there.

I have no idea how this thing is going to handle large amounts of data.  Some people have hundreds of articles and tracking earnings data over weeks and months is going to get big.

Upcoming Changes

There’s really only two things I need to work on at this point.  The first one is to do some serious work on the HTML report.  It needs some nice styling and I really want to add some functionality to it (such as being able to sort by column).

The other thing that I want to do is to provide a way to export/backup the data in CSV or XML format.