Monthly Archive for April, 2006

Computing Graph Limits

If you've ever drawn a graph (manually or with a computer), you know that you have to pick upper and lower bounds for the x and y axes based on the data points you're graphing.  Usually, this is a relatively simple matter, but, at least for me, it's proven to be a difficult problem to solve in a general manner.

I've built some time-based charting components with SVG (because CFCHART sucks), and while they work very well, I simply can't find an algorithm for finding appropriate bounds that works effectively.  My first thought was to extend the range a few percent above and below the min and max, but that usually ends up with grid lines labels that are ugly (1, 1.333333, 1.6666666, 2, etc.).

I'm currently using an implementation that scales based on the number of gridlines, applied to the order of magnitude of the range boundaries.  It works pretty well, except where the order of magnitude changes close to a boundary.  I.e. if the range is from 0.01 up to 1.01, the scale will end up being from 0 to 10, which isn't desirable, because the top 89% of the chart is empty.

So with the success of my last to "fishing for feedback" posts (on BlogCFC, and tabbed documents), here's the next inline.  I'd love to hear about other people's solutions to this sort of problem, because it's quite a nasty one (or I'm an idiot and missing the obvious).

Prototype Patch

Prototype is JavaScript library, of a similar nature to Neuromancer, that I've been using of late.  It's got some really cool features that Neuromancer doesn't have (particularly when extended by Script.aculo.us), but it's not a complete replacement.  For example, no remoting support.  While working with it, I noticed that all POSTs were sent as urlencoded query strings, which works until you've got an unescaped ampersand lurking.  So I patched it to support multipart POSTs.

To use the new feature, do everything you've been doing to this point, just change your postBody parameter to be a generic object with name/value pairs representing the form fields you want to submit, rather than a query string.  The patch (for src/ajax.js) has been submitted to the Rails team under ticket #4613, and is also available below.  Note that the patch is for the source, not the assembled prototype.js, so you'll either need to get the source, patch it, and the build it (which requires Ruby), or apply the patch to prototype.js manually (which is straightforward, just have to find the right spot in the file).

Index: /home/barneyb/public_html/neuro_preso_2006_04/static/prototype/src/ajax.js
===================================================================
— /home/barneyb/public_html/neuro_preso_2006_04/static/prototype/src/ajax.js (revision 4182)
+++ /home/barneyb/public_html/neuro_preso_2006_04/static/prototype/src/ajax.js (working copy)
@@ -50,6 +50,8 @@
});

Ajax.Base = function() {};
+Ajax.Base.urlencodedContentType = 'application/x-www-form-urlencoded';
+Ajax.Base.multipartContentType = 'multipart/form-data';
Ajax.Base.prototype = {
setOptions: function(options) {
this.options = {
@@ -55,7 +57,7 @@
this.options = {
method: 'post',
asynchronous: true,
- contentType: 'application/x-www-form-urlencoded',
+ contentType: Ajax.Base.urlencodedContentType,
parameters: "
}
Object.extend(this.options, options || {});
@@ -93,6 +95,26 @@
this.url += (this.url.match(/\?/) ? '&' : '?') + parameters;

Ajax.Responders.dispatch('onCreate', this, this.transport);
+
+ var body = this.options.postBody ? this.options.postBody : parameters;
+
+ if (this.options.method == 'post' && typeof body != 'string') {
+ // we're POSTing and body is an object, so we need to do multipart encoding
+ var fields = body;
+ body = "";
+ var boundary = "proto" + Math.random();
+
+ for (var i in fields) {
+ body += "–" + boundary + "\nContent-Disposition: form-data;name=\"" + i + "\"\n";
+ body += "\n";
+ body += fields[i] + "\n";
+ body += "\n";
+ }
+ body += "–" + boundary + "–";
+
+ // we also have to update the Content-Type header
+ this.options.contentType = Ajax.Base.multipartContentType + "; boundary=" + boundary;
+ }

this.transport.open(this.options.method, this.url,
this.options.asynchronous);
@@ -103,8 +125,6 @@
}

this.setRequestHeaders();
-
- var body = this.options.postBody ? this.options.postBody : parameters;
this.transport.send(this.options.method == 'post' ? body : null);

} catch (e) {

Tabbed Documents

I was doing some prototyping today, and wanted to save four individual text buffers as a tabbed document.  More specifically, I wanted to save the four buffers as a single file with four sections, and when reopened, have the four sections load into four separate buffers (tabs) in the editor.

Excel has had this feature for worksheets in a spreadsheet for years, browsers have had it for a year or two, but nothing for documents.  Does that seem weird to anyone else?  Because this isn't the first time I've wanted to do something like this.

If anyone's got an editor that'll do this (and Eclipse's ability to have open editors persist across a close/open cycle doesn't count - those are separate files), I'd love to hear it.

Edit: I'm talking about a editing program here that saves files to my hard disk, not any sort of web-based thing.  I wasn't very clear on that point.  I.e. Eclipse, TextPad, Emacs, etc.

Neuromancer Again

While working on my CFUG preso for this month, I found another bug: dates weren't handled properly coming back from web service calls.  So I added support for them.  Patch file for js/io/RemoteObject.js below, or you can get the update and supporting test scripts from Subversion.

Index: RemoteObject.js
===================================================================
— RemoteObject.js (revision 10)
+++ RemoteObject.js (revision 11)
@@ -26,6 +26,7 @@
var DATATYPE_BOOLEAN = "soapenc:boolean";
var DATATYPE_NUMBER = "soapenc:double";
var DATATYPE_NUMBER2 = "xsd:double";
+var DATATYPE_DATE_TIME = "xsd:dateTime";

/**
* Variable: REMOTE_OBJECT_VERSION
@@ -85,6 +86,10 @@
{
value = parseFloat(dItem.item(z).firstChild.nodeValue);
}
+ else if (xsiType == DATATYPE_DATE_TIME)
+ {
+ value = DefaultHandler.xmlDate2JSDate(dItem.item(z).firstChild.nodeValue);
+ }
else if(xsiType == DATATYPE_MAP)
{
value = new Map();
@@ -95,7 +100,7 @@
}
}
}
- nstruct.put(key,value);
+ nstruct.put(key,value);
}
}
}
@@ -716,6 +721,10 @@
{
eval(__dfh__variable + " = parseFloat(resvalnodes.item(0).firstChild.nodeValue)");
}
+ else if (returntype == DATATYPE_DATE_TIME)
+ {
+ eval(__dfh__variable + " = DefaultHandler.xmlDate2JSDate(resvalnodes.item(0).firstChild.nodeValue)");
+ }
//}
//this is a structure (a coldfusion struct)
//else if(complextypeid != null && complextypeid.length > 1)
@@ -765,6 +774,8 @@
//show the value to stdout
if(__dfh__variable == "__neuro__myvar__")
{
+ // TODO: this throws not defined errors. Probably should be if(typeof neuro_SystemOut == "function") instead
+ // Hopefully, however, no one will ever get here, because they'll always be using a callback.
if(neuro_SystemOut != null)
{
neuro_SystemOut("\n");
@@ -774,4 +785,29 @@
neuro_Runner("");
}
}
+};
+
+DefaultHandler.xmlDate2JSDate = function __xmlDate2JSDate(xmlDate) {
+ var val = xmlDate.split("T");
+ // split it into date and time portions
+ var date = val[0];
+ var time = val[1];
+ date = date.split("-");
+ time = time.split(":");
+ // rip out the date portions
+ var year = date[0];
+ var month = date[1] - 1; // JS uses 0-11, not 1-12
+ var day = date[2];
+ // rip out the time portions
+ var hours = time[0];
+ var minutes = time[1];
+ var seconds = parseFloat(time[2]);
+ // convert fractional seconds to milliseconds
+ var millis = Math.round((seconds - Math.floor(seconds) ) * 1000);
+ seconds = Math.floor(seconds);
+ // assemble the completed date
+ var completeDate = new Date(year, month, day, hours, minutes, seconds, millis);
+ // adjust the time from UTC (Zulu) to local time
+ // TODO: change the to check for the appropriate adjustment, rather than blindly assuming UTC
+ return new Date(completeDate.getTime() - (completeDate.getTimezoneOffset() * 60 * 1000));
};

To BlogCFC or not to BlogCFC…

Currently my blog (along with my wife's, my sister's, and my sister-in-law's), runs on a custom amalgamation of technologies: MovableType in a 'service' role, custom JSP frontend, custom CFML admin area.

As happens to nearly every small personal project that evolves over several years, the piecemeal nature of the system is starting to be a burden.  So I'm looking into rebuilding, and I'm considering Ray's BlogCFC as a base, which seems to generally be the system of choice for CF blogs.

However, I wanted to solicit some community feedback first, particularly regarding ease of extension.  As well as the core blog, the other three blogs that would be using it must support blog-bound photo galleries, customized skinability, and a few other features, all of which aren't tied to the core premise of blogging, and therefore outside BlogCFC's area of responsibilty.  The trick, of course, is that the users must be unaware (as they are now) that the blogging system is a separate system from the gallery system: they just sign into the admin and do their thing.

So, comment away: I'm listenting….

Neuromancer [Re]Addition

A while back I need to do a standard form POST via Neuromancer, so I'd added a doFormPostRequest method to the JSRemote object.  I just needed it again, and for whatever reason, it hadn't made it's way into the core distribution.  So I merged my modded sources in (I love version control ; )), and thought I'd share.

As before, the updates are in the Subversion repo, or you may use the patch below.  Note that this time it's for js/io/Gateway.js, not RemoteObject.js.

Index: Gateway.js
===================================================================
— Gateway.js (revision 8)
+++ Gateway.js (revision 9)
@@ -161,6 +161,38 @@
};

/**
+ * Method: JSRemote.doFormPostRequest
+ * This method implements a multi-field form submission via a POST,
+ * using the 'fields' object as a set of name:value pairs to pass as
+ * the form fields. It simply delegates to doPostRequest for the
+ * actual processing; the only functionality is serializing the fields.
+ *
+ * Note that this method's parameter ordering does NOT correspond to
+ * doPostRequest's.
+ *
+ * Parameters:
+ * url - the url to POST to
+ * fields - the form fields to POST
+ * handler - the callback function to send results to
+ */
+JSRemote.prototype.doFormPostRequest = function _doFormPostRequest(url, fields, handler) {
+ var body = "";
+ var headers = new Object();
+ var boundary = "neuro" + Math.random();
+
+ for (var i in fields) {
+ body += "–" + boundary + "\nContent-Disposition: form-data;name=\"" + i + "\"\n";
+ body += "\n";
+ body += fields[i] + "\n";
+ body += "\n";
+ }
+ body += "–" + boundary + "–";
+
+ headers["Content-Type"] = "multipart/form-data; boundary=" + boundary;
+ this.doPostRequest(url, handler, body, headers);
+}
+
+/**
* Method: JSRemote.doPostRequest
* Does a simple post request, passing the bodyinfo as the body of the
* request - meaning the only way to get the bodyinfo out is to do
@@ -171,7 +203,7 @@
* func_handler - the callback function to send the results to
* bodyinfo - what to send in the body of the POST
*/
-JSRemote.prototype.doPostRequest = function _doPostRequest(url, func_handler, bodyinfo)
+JSRemote.prototype.doPostRequest = function _doPostRequest(url, func_handler, bodyinfo, extraHeaders)
{
log.info("doPostRequest to " + url);
log.info("using pipe: " + this.connectionid);
@@ -211,6 +243,11 @@
conn.setRequestHeader("XLibrary", "Neuromancer 1.5beta");
if(bodyinfo.indexOf("

Subversion Rules!

I've been using Subversion for a while now.  Not sure exactly how long, but about a year, I'd guess.  Before that it was CVS for a number of years.  I have to say, first of all, if you're not using version control, start.  It's worth a bajillion times more than the few hours it'll take to set it up.  Second, the Subversion guys had their heads on straight.

I just tried externals definitions for the first time this evening, and talk about sweet.  Basically, they let you store (via Subversion properties, aka in-repository metadata), references to external projects subversion repositories, and allow you to transparently work with your multi-repository working directory in totally supported fashion.

Perhaps an example would be good.  I'm working on a CFUG presentation on JS remoting, and I'm using Neuromancer and Script.aculo.us as part of it.  Since both have Subversion repositories, and I have commit access to the Neuromancer repository (and may want to commit bug fixes while I'm working), externals are perfect.  I define a simple svn:externals property on my root directory, and then do an svn update, and BAM, I have my working directory updated, including fresh checkouts of the Neuromancer and Script.aculo.us code as well.  Make some mods, run svn status, and again, all the mods on all three projects are nicely laid out hierarchially, ignorant of the fact that they're source resides in three totallly separate SVN repositories.

Also, if I were to check out a fresh working copy on some other machine, guess what happens?  I also get the two external projects for free, because the external references are part of the SVN metadata, so they're included, and they're versioned.  All for free.

Now this might not seem like a particularly useful feature, but perhaps you have intra-project dependancies, and you need your app to rely on a specific version of a module that is also tracked in your same SVN repository.  Create a tag for the sub module, and create an external definition for that tag.  Then, until someone updates the svn:externals property, everyone will always get that tag of the submodule, regardless of where the submodule's development takes it.  Better yet, when you update svn:externals, as soon as you run svn update on your working directory, you'll magically get the new version of the tag.

Magical….