Older blog entries for Ohayou (starting at number 15)

21 Aug 2005 (updated 21 Aug 2005 at 03:50 UTC) »
Adding calendar navigation to your Blogger template

Not caring much for Blogger's idea of navigation for my personal diary, today I set about adding a calendar widget similar to one I have been accustomed to having last time I was blogging, in a self-hosted do-it-all-yourself environment. Lazy-bastardness has changed that situation since, but I digress -- let's get down to business, shall we?

Someone probably made a decent calendar widget in the past few years, right? Right. Good; then I won't waste a lot of time boring my head off tweaking HTML+CSS, which I, quite honestly, detest.

Okay, download and unzip the 1.0 release, and dig in. First things first, we put it in a suitable location on some hosting of our own, something like http://www.example.com/jscalendar-1.0/ (you probably have it on some different location, yadda yadda) and toss in the necessary bits to add it to your template's <head> portion. I tucked mine in just before the end of the head section, since we will be tweaking other things in the vicinity very soon anyway. So here we go; add these lines:

<style type="text/css">@import url("/skins/aqua/theme.css");</style>
<script type="text/javascript" src="/calendar.js"></script>
<script type="text/javascript" src="/lang/calendar-en.js"></script>
<script type="text/javascript" src="/calendar-setup.js"></script>
(don't forget to prepend your URL prefix to the src/url arguments!) and we now have a beautiful bit of GNU LGPL calendar code loaded and ready, just aching to get to do something for us. So, pick a spot in your template where you want the finished calendar widget to go, and add an empty <div id="calendar-container"></div> tag there. If you're eager to try out a dummy right away, by all means go ahead: temporarily add this right after the div:

<script type="text/javascript"><!--
  Calendar.setup( {flat:'calendar-container'} );
//--></script>
(Tada!)

Okay, not very interesting before it does anything; I agree. Before we continue, though, we need to tweak some settings (or you need to rewrite some of my code to fit your own preferences). First, track down Settings -> Archiving -> Archive Frequency and set it to "Daily". (You need this to get each entry in your blog listed in the page - and hence to show up in the calendar.) Then (and the order you perform these changes is important) set Settings -> Formatting -> Archive Index Date Format to 2005-08-20 (ISO YYY-MM-DD format; very nice to parse). In case you're alienated by practical date formats, don't panic; the visitor will not see these dates anyway. Setting Date Header Format will however be visible, but my example code further down will assume you did, so if you are lazy or share my preferences you'll want to have ISO dates here, too. These are the dates shown near the head of a post in your average Blogger template, and they are used by the script to detect which date should be highlighted as "this post". But now I'm getting ahead of myself.

Now dive into your template and look up the archive portion - it's probably surrounded by a pair of <MainOrArchivePage></MainOrArchivePage> tags. Replace those with a <div id="archive"></div> tag (since you want your navigation widget on all pages, including the single-day's-posts page), and since we want the archive portion easily targetable for hiding.

That could be done with a CSS style rule #archive { display:none; }, but I opted to perform the hiding from javascript instead, knowing that there still are a few who navigate the web with javascript turned off. This way, you won't condemn them to live without navigation, even though they don't get the benefit of your spiffy DHTML generated-live calendar widget. A shame, but that's their choice, and it's very decent of you to degrade gracefully.

Make sure the contents of that div contains something like

<BloggerArchives>
<li><a href="<$BlogArchiveURL$>"><$BlogArchiveName$></a></li>
</BloggerArchives>
(the <li> and any other additional tags besides the <a> tag aren't needed, but don't hurt either). Just make sure the div swallows up every bit you want to hide for the crows who can see your nifty calendar. Now drop the proof-of-concept <script> tag you might have tossed in below the other div, and finally add this chunk at the end of your <head> tag:

<script type="text/javascript"><!--
function calendar()
{
  var archive = document.getElementById( 'archive' );
  if( archive )
  {
    archive.style.display = 'none';
    var notes = {};
    var links = archive.getElementsByTagName( 'a' );
    if( !links.length ) return;
    var i, j, node, date, y, m, d;
    for( i=0; i<links.length; i++ )
    {
      node = links[i];
      date = node.innerHTML.split('-'); // YYYY-MM-DD
      y = parseInt( date[0], 10 ); if(!notes[y]) notes[y]    = {};
      m = parseInt( date[1], 10 ); if(!notes[y][m]) notes[y][m] = {};
      d = parseInt( date[2], 10 ); notes[y][m][d] = node.href;
    }
    var dates = document.getElementsByTagName( 'h2' ), thisDate;
    for( i=0; i<dates.length; i++ )
      if( dates[i].className == 'date-header' )
      {
        var ymd = dates[i].innerHTML.split('-'); // YYYY-MM-DD
        thisDate = new Date( parseInt( ymd[0], 10 ),
                             parseInt( ymd[1], 10 )-1,
                             parseInt( ymd[2], 10 ) );
        break;
      }
    top.notes = notes;
    Calendar.setup(
    {
      date          : thisDate,
      flat          : 'calendar-container',
      range         : [ parseInt(links[0].innerHTML), y ],
      showOthers    : true,
      flatCallback  : dateChanged,
      dateStatusFunc: disableDateP
    });
  }
}

// Returns true for all dates lacking a note, false or a css style for those having one. // Exception: today does not return true, even if it lacks a note. (improves navigation) function disableDateP( date, y, m, d ) { var now = new Date; if( (y == now.getFullYear()) && (m == now.getMonth()) && (d == now.getDate()) ) return false; return noteFromDate( date ) ? false : true; }

function noteFromDate( date ) { var note = top.notes[date.getFullYear()] || {}; note = note[date.getMonth()+1] || {}; return note[date.getDate()]; }

function dateChanged( calendar ) { if( calendar.dateClicked ) { var note = noteFromDate( calendar.date ); if( note ) window.location = note; } }//--></script>

and edit the <body> tag following it to read <body onload="calendar()"> -- and you're all set to go! Dates with entries on them are clickable, and you can navigate around among the years and months as you well please, without any time-consuming web server roundtrips too.

Of course, feel free to experiment, perhaps most easily with the parameters in the Calendar.setup() call, or indeed with any other aspect; having the date-header parsing code match your date listing preferences or whatnot. Another good exercise is probably trying out a layout where you add the widget as a popup rather than the flat embedded one - that way you'll get extremely nifty keyboard navigation via the arrow (and control) keys too in the widget.

Share and enjoy! Spread the love! :-)

17 Aug 2005 (updated 17 Aug 2005 at 16:43 UTC) »
Generating an M3U playlist live from a scriptlet

While browsing around remix.kwed.org for something nice to play while working today, I realized I was missing a scriptlet to make a play list of all mp3 files linked from any web page (and web plain web server directory listings in particular).

So I wrote one. I had estimated five minutes, but it probably took closer to ten, my usual with(document){ open('content-type'); write(page); close() } proving less than useful in a scriptlet context, since the document replacing also tosses away all javascript state, including the generated playlist page to be written.

Enter the very convenient RFC 2397 data: URL protocol -- replacing that bit with a location='content-type,'+escape(page), and we're set. (The Mozilla data: testsuite is a handy resource to get a quick idea of what you can do with this toy and how, if you are new to the concept. It's still not widely supported in browsers, but good browsers do.)

Enjoy! - the Generate M3U playlist scriptlet, in its.

11 Apr 2005 (updated 20 Jun 2005 at 20:32 UTC) »
Javascript: Using XPath as an XML query tool

Long story short: Mozilla: sure. IE / MSXML2 (3.0): well, given a strong stomach, perhaps.

I've been toying with a set top box user interface at work for some time now, all HTML, images, CSS and Javascript. Very non-web, non-computer feely, remote control rules all sort of thing. The target environment is very slightly spiced-up OEM, run of the mill wintel machines, IE the browser to run it on. I'm not overly happy about that last bit.

My development environment is rather Mozilla bound, mostly because there are tools useful when debugging and because IE6 gets error messages wrong, lacks the toSource method and lots of other very useful things that add up to make it a really unfriendly, sluggish environment providing no leverage.

On adding an EPG system to this beast, I decided on using the XMLTV format for the data, and threw together some sketchy parsing using the XPathEvaluator APIs kindly provided by the Mozilla people. It worked beautifully -- here's a comfy sample:

function query_xpath( node, xpath )
{
  var document = node.ownerDocument || node;
  var evaluator = new XPathEvaluator(), ANY = XPathResult.ANY_TYPE;
  var resolver = evaluator.createNSResolver( document.documentElement );
  return document.evaluate( path, node, resolver, ANY, null );
}

with which I count nodes matching some given criterion or criteria, for instance - or any other xpath functions. I did this happily enough and left the IE bit for later. Bad move. Do you think I'd be given that freedom with MSXML? I did. I am not there yet, after several hours of reading MSDN and net resources; as it turns out, to do anything but node set operations you must add some XSLT indirection to the brew, I gathered from an MSXML based tool page not kindly enclosing the only interesting bits: source code. Most annoying of them to mark it up well to get google hits from poor sods trying to get MSXML to cooperate, in my grumpy opinion.

This might be on the right track though, should I choose to still rely on doing any XPath based queries on the raw XMLTV data (untested):

function query_xpath( node, xpath )
{
  var xsl = new ActiveXObject( 'Msxml2.FreeThreadedDOMDocument.3.0' );
  xsl.loadXML('<xsl:stylesheet><xsl:template match="/"><xsl:value-of '+
	      'select="'+ path +'"/></xsl:template></xsl:stylesheet>');
  var xslt = new ActiveXObject( 'Msxml2.XSLTemplate.3.0' );
  xslt.stylesheet = xsl;
  var xml = node.ownerDocument || node;
  var proc = xslt.createProcessor();
  proc.input = xml;
  proc.transform();
  return proc.output;
}

Not pretty. Doesn't comply with the API of processing a node set only, but rather needs the entire document, and it reeks of needless computron waste.

End of rant. I feel a little better already.

7 Mar 2005 (updated 7 Mar 2005 at 00:58 UTC) »
Javascript

I've seriously started digging into Venkman now, the Mozilla project's javascript debugger and profiler. I'm sure it's a great tool if it's your own baby or if you have someone initiated around to teach you its ways, but short of that, you need to find good webpages to help you get anywhere, such as figuring out how to set a simple breakpoint. It's a bit like learning to make good use of Emacs, though in a GUI application. Striking.

Anyway, once you acquire some basic working skills, it has a lot to offer. I fell in love with the profiling tools, not so much because I tend to write javascript code in need of optimisation, but for being beautifully done. (Once you are sitting with the profiling data and have left Venkman's GUI safely behind, anyway.)

As it happens, though, I read through Svend Tofte's good guide referred above, and ended up at the BrainJar JavaScript Crunchinator. "Hey, cool hack!" I thought to myself, and tried feeding it my present work project, an application weighing in at 32 kilobyte, and it sat there for a long time grinding on it. Minutes later, it spat out a big chunk of code that started much like my own code and ended in mid air, three kilobyte short of the end of the file. Weird.

So I inspected my own source code, and found that I had commented out a block with /*...*/ just before where the crunchinator had given up, and the block ended in a // comment, inside the block comment -- and lo, the mystery was solved.

As I was curious to see if the crunched code would actually work, once that issue was resolved, I peeked on the comment stripper, decided it was beyond fixing and decided to run my own instead. After trying a regexp cut and paste approach, I was again annoyed at Javascript RegExps, for some reason not eating entire input strings (why does (.*) not match the rest of my input data? M'kay, I suppose I will read find the answer myself in ECMA-262 next time I'm bit by this and sufficiently annoyed to learn from the specification).

On the other hand, a regexp cut-and-paste solution is by rule of thumb always the wrong solution, for one reason or another, and after having given the matter some thought and made a brief inventory of the search methods on offer in javascript (thank you so much for Javascript: the Definitive Guide, David Flanagan!), I found a much more aesthetic solution built from String.search, String.indexOf and Array.join:

function removeComments( s )
{
  var found, code = [], commentStart = /\x2f[\x2f\x2a]/, commentEnd;
  while( (found = s.search( commentStart )) >= 0 )
  {
    code.push( s.substring( 0, found ) );
    if( s[++found] == '*' )
      commentEnd = '*/';
    else
      commentEnd = '\n';
    if( (found = s.indexOf( commentEnd, found )) >= 0 )
      s = s.substring( found + commentEnd.length );
    else
      s = '';
  }
  s = code.join(' ') + s;
  return s.replace( /\n/g, ' ' );
}

I paste it back into the crunchinator, fire away, and in mere seconds, the result pops up this time, no truncation to be seen. Surprised, I test it again. Sure enough, a speedy weasel indeed. I apply my newfound Venkman knowledge, sleep through the original code's 154.34 seconds worth of heavy processing (81 of which were spent in the original removeComments function), run my own version and get a lean 4.70 seconds for running the entire script. That's some mean garbage collection gains. Just to be sure I'm not measuring something irrelevant, I run the tests again in the other order. No difference worth mentioning.

I sumbit my improvements to the original author, notice that my additions just feel into the GPL (you know where to find the license, folks) and figure it's been a decent hack. Maybe someone could even learn from it. For reference, here is the original source code (don't do this at home):

function removeComments(s) {
  var lines, i, t;
  // Remove '/* ... */' comments.
  lines = s.split("*/");
  t = "";
  for (i = 0; i < lines.length; i++)
    t += lines[i].replace(/(.*)\x2f\x2a(.*)$/g, "$1 ");
  // Remove '//' comments from each line.
  lines = t.split("\n");

t = ""; for (i = 0; i < lines.length; i++) t += lines[i].replace(/([^\x2f]*)\x2f\x2f.*$/, "$1"); // Replace newline characters with spaces. t = t.replace(/(.*)\n(.*)/g, "$1 $2"); return t; }

"The results?" I hear you asking. Well, my original 32 kilobyte application weighed in at 19, a pleasing 59.8% of its original weight, without resorting to variable renaming and similar destructive modifications. It worked, after fixing only six slight misses, half of which were my own (missing end-of-line semicolons and a case of an operator on both sides of a newline). The other half were inside regexps -- two related to apostrophes and quotation marks, the last one being the regexp /  /, which had been optimised to // (...ow! -- and the rest of the line , or the rest of the script if you so prefer, was thus effectively cut off :-).

I suppose that means that another healthy exercise would be to rewrite the string literal parsing code too, but I would suspect that any improvements over the present would mean to parse by language grammar rather than crude string matching, and somehow it doesn't feel like very gratifying work. Not that I have peeked at the code, though.

3 Mar 2005 (updated 3 Mar 2005 at 17:44 UTC) »
Bookmarklet (and calendar rant)

I absolutely detest all numeric non-ISO date formats, M/D/Y probably most of all. So when I encountered the Kingdom of Loathing calendar some benevolent (albeit calendrally challenged) person had published, I did not track down said person to tell him how glad I was at finding what I was looking for and how I felt about the format in which it was published. The meld of feelings would just not make any sense, and after all, the information was both there and fairly easily deciphered. I just strongly feel that deciphering is best left to computers.

Enter today's bookmarklet (feel free to bookmark it). It will ask you which (numeric) date format to convert from, harvest all frames for dates on that format and reformat them to readable ISO YYYY-MM-DD dates. If you go with the default M/D/Y, it will find 3/2/5, being sillyspeak for yesterday, and turn it into 2005-03-02. Short dates in the future (such as 3 / 3 / 6) will be assumed to mean the corresponding date from last century. Run it a year from now you will see 2006-03-03, though. Unless your clock is off, by a lot.

Upon googling for date tables to try it out on, I found a hilarious hallmark of stupidity - an excel sheet featuring the column "Employee Start date", "m/d/y or y/m/d e.g. 5/17/2 or 2002/5/17". The web is a silly place. Let's not go there.

Javascript

Okay, the last procrastinational activity for today: a very (stone axe) crude kind of javascript indent tool built on GNU indent. So it doesn't really reindent Javascript code, but C. Silly me. But while it does trash the occasional RegExp literal, it's less of a hassle than doing all the work manually.

Besides, most C users wouldn't use web tools to indent their code. Not me, anyway. But if you would, feel free to use this one. The worst thing that could happen is that it might at some time in the future also work with keywords and literals you do not use in your C code, where they are not supported.

(I wish I had some tool that turns javascript input to an abstract syntax tree, which I could then just traverse to cook my own Function.prototype.toSource(). ...Okay, back to work now.)

Javascript

How does one intercept modal browser alerts about connection problems, file transfer failues and the like from javascript?

Most of all, I want to trap the "The operation timed out when attempting to contact www.kingdomofloathing.com" dialog, which pops up very frequently (typically at least once every session) when I load a new frame from my javascript code. Of course it has nothing to do with which method I employ to load it, but it's mostly when I initiate the transfers myself when I'm really interested in trapping the alert to handle it in a more constructive and less user-involving manner.

Even a solution requiring me to be a Mozilla plugin would be useful, though what I really want is something that could live in a scriptlet or web page and handle the call, ideally cross-browser. With browser Javascript not being nearly as wing-clipped today as it was back in the nineteen hundreds, I've come to expect to be able to solve issues like these. (Let's hope I'm not being overly optimistic.)

Hmm. I suppose due to the lack of talkback features here, I'd better leave a mail address -- feedback to oyasumi at gmail dot com would be very welcome indeed. I'll be sure to write some kind of article about the solution once I find or get pointed to it.

24 Feb 2005 (updated 24 Feb 2005 at 21:07 UTC) »

Mozilla

My signature-uncluttering phpBB Firefox extension seems to have been listed recently at Extension Room. And as proof of someone already using it, a few hits in the access log show that browsers are eagerly polling for updates -- and I received a second feature request, not counting my own. (With a bit of luck, I'll learn how to get a callback once just the HTML bits of a loading page have been fully loaded, so I can address my own issues with it. After all, it doesn't make any sense loading lots of graphics just to purge them a second later.)

Macromedia security team got back to me reporting that they have reproduced the problem and that it fortunately only feeds mouse events to the background flash application (not including data on which window should have seen the events). I hope the Macromedia and Mozilla people get to share intelligence on the subject; I'd be delighted if the former nailed their bug and the latter found a way of fixing the entire problem class from their side. Since IE for once seems unaffected, I have hope it's possible to somehow alter the plugin interface sandbox to catch this.

Net

Oops, I just encountered a solution Net doesn't consider valid. Minesweeper addicts beware: this game carries the same mind tying properties that your former brain virus did.

Ooo, shiny! Completed the master level of Net in 13:37 (pun not intended) m:s. What an extraordinarily geeky personal record. One probably ought to quit when encountering such divine hints that there are better ways of wasting time.

I could, for instance, finish that object oriented javascript+HTML set top box interface I have a deadline to complete before next Friday and get actual pay for doing, by the hour. That last somehow makes the code very pretty, easy to debug, extend, explain and reuse in a way my pet projects seldom seem to. Why isn't personal hobby hacking paid by the hour?

(Yeah, I know. It's amusing how some question make sense in feelings but come out inane traps of logic short circuit if spelled out in words. Life can be so much fun if you are as easily amused as I am at times.)

22 Feb 2005 (updated 22 Feb 2005 at 05:36 UTC) »

Hmm. My Firefox 1.0 lets through mouse clicks in my advogato tab to the flash game running in its own tab. When clicking places in my advogato tab where the game tab had a widget, I hear the characteristic clicking noise evoked by the game. I wonder if there's a bugzilla ticket on this yet.


...Well, now there is, anyway. (It also seems bug id:s have more than doubled in the almost three years that passed since my first and prior issue was registered.)


Upon closer inspection, that's hardly a mozilla issue at all, since XEmacs and Photoshop exhibit the same behaviour. Oh, well, now it's reported to the hopefully security minded engineers at Macromedia too.

And thank god for Google to helt bypass the many tightly woven webs of frustration on icky corporate websites to get to what you want to go. I tried to find that page unaidedly at first, and was very close to dying in the process. Never again.

6 older entries...

New Advogato Features

New HTML Parser: The long-awaited libxml2 based HTML parser code is live. It needs further work but already handles most markup better than the original parser.

Keep up with the latest Advogato features by reading the Advogato status blog.

If you're a C programmer with some spare time, take a look at the mod_virgule project page and help us with one of the tasks on the ToDo list!