Adept Software Development

Adept: (A)pplication (D)evelopment (E)nterprise to (P)ersonal (T)ransition. It is a system I am developing to leverage Enterprise developer skills to produce stand-alone software for other market segments. This is a general software development blog discussing issues about project, architecture, design and development. The emphasis will be in Java, but many of the issues will be more general. Almost all will be technical.

http://marringtons.com

Monday, August 27, 2007

Disk Caches and Notebooks

Now that I am working in a research job again I will be reviving my blogging. As Software Manager I just did not have time or enough excess energy. While I am thinking up a list of topics I will start off with a complaint session. With the new contract I bought a Macbook Pro with wireless broadband so I could access things like Safari Books Online without worrying that a net-nanny had decided that programming was bad language. I am not a mac-o-phile, but when I did the analysis this time the top of the line Macbook turned out to be the best value for money. My notebook before that was an IBM running Windows 2000 and then XP. With 384Mb of RAM I expected a fair bit of hard disk activity. The Mac has 4Gb, so why does the hard disk run all day? Fortunately "Activity Monitor" has a disk activity pane. With the system idling I see a write every 15 to 30 seconds. No reads, as expected. Unfortunately the monitor did not tell me who was writing to disk. So, for fun (?) I started killing off tasks one at a time. I hoped to find a culprit. No such luck. The space apart between right became wider. Once I had removed most applications it was as long as 4 minutes. I had the drive wind-down set at 5 minutes and it powered down once. The truth is that in this modern day of multi-thread programs it makes sense to have threads running in the background to monitor the context and save it in case the computer closes down unexpectedly. Or to do housekeeping that either writes do disk directly or makes changes to the virtual memory balance. It could be something as simple as a log being updated to say that no activity has taken place. My gut feeling is that most hard disk spin most of the time. I wonder how many tons of carbon a day that equates to for the world's desktops? Or closer to home, how much longer I can run on battery without the hard disk spinning all the time. I was going to wait for a solid state drive, but they weren't quite main-stream. To show that others are thinking on the subject - it is touted as one of the benefits of hybrid drives. I don't think we need fancy hardware to improve things today. I don't even think it needs much of a software change. I think all modern operating systems have write-behind caches. How about giving me a power saving option that does something like:
if no user activity in 5 minutes
if no program using significant constant amounts of CPU time
if write-behind cache is less than 100Mb (or possibly even 10Mb).
if battery power is not low (notebook or UPS)
then
  Turn off drive and cache writes in memory until one of the conditions above change.
It would have to be a power setting in the control panel. There are probably some systems out there where these background tasks are critical and the risk too high. I probably wouldn't use it on a desktop without a UPS. Given the low cost of a UPS these days I would probably buy one just to have my hard disk powered down for a large portion of the day.

Tuesday, September 20, 2005

JavaScript Events - Part 4 - Event Library Source

As promised, here it is. Refer to the earlier articles if you want to know the hows and whys.
/*
 * Created on 5/11/4
 *
 * Copyright 2004 Paul Marrington
 * All rights reserved - http://marringtons.com
 * PROPRIETARY/CONFIDENTIAL
 * Use is subject to license terms - paul@marrington.net
 */

/**
 * Add all the events in an event object
 * inside the group given.
 */
Events.addEvents = function( element, group)
  {
    for (var name in group.events)
      Events.add(
        element, name, group.events[name]);
  }

Events.addSameEvent = function(
  name, action, elements)
  {
    for (var i = 2;  i < arguments.length;  i++)
      Events.add( arguments[i], name, action);
  }

Events.add = function( element, name, action)
  {
    name = name.toLowerCase();
    var on = "on" + name;

    if (! element.events)
      element.events = new Object();
    
    var list = element.events[name];
    if (! list)
      {
        list = element.events[name] = new Array();
        if (element[on]) list.push( element[on]);
        /*
         * This is an interesting bit of obscure code.
         * If there were any other way, I would not do
         * it, but IE makes it essential. The event handler
         * is inline so that it keeps a handle to the surrounding
         * function/object. This way we can set event.owner
         * to the same as the calling element when add
         * function was called - even though the event
         * handler is called asynchronously later,
         * long after the add function is dead and buried.
         * Nasty. It also means we are hanging on to an
         * unknown amount of stuff. The W3C DOM uses
         * evt.currentTarget, but this is the ONLY way
         * we can get IE to know the owner. This is
         * because evt.target is the actual element
         * clicked on or whatever, while the owner 
         * of the event can be further up the DOM tree.
         */
        element[on] = function( evt)
          {
            /*
             * Normalise the event object between
             & browsers and retrieve the list
             * of actions to take.
             */
            evt = evt || event;
            if (! evt.target)
              evt.target = evt.srcElement;
            /*
             * We get element from the owner object.
             */
            evt.owner = element;

            var list = this.events[evt.type];
            /*
             * Run each event in the list until
             * one returns false. This breaks the chain.
             * Events are run from the most
             * recently added to the oldest.
             */
            for (var i = list.length - 1;  i >= 0;  i--)
              {
                evt.action = list[i];
                if (! evt.action( evt))
                  return false;
              }
            return true;
          }
      }
    
    list.push( action);
  }

Events.newList = function( name)
  {
    var newList = function( evt)
      {
        for (var j = evt.action.events.length - 1; 
          j >= 0;  j--)
            if (! evt.action.events[j]( evt))
              return false;
        return true;
      }
    newList.eventName = name;
    newList.events = new Array();
    newList.add = function( event)
      {
        this.events.push( event);
      }
    return newList;
  }

 

Tuesday, September 13, 2005

JavaScript Events - Part 3 - An Event Library

My first attempt at an event library allowed a single event to be attached to multiple elements, as well as multiple event methods to a single event on a single element. This is very useful when you want to track the mouse since you need to register the same even for multiple windows or frames. However, it did require the creation of a special event object and then to have that object attached. Consequently - except for the special mouse tracking case - most of my events were set in the time honoured way:
element.onclick = function( evt)
  {
    evt = evt || event;
    if (! evt.target)
      evt.target = evt.srcElement;
    alert( "clicking " + evt.target
  }

 

The problems grew:
  1. Every event function had to have the first two lines to ensure basic data conformity between browsers.
  2. If more than one piece of independent code wanted to set the same event, they had to know about each other or the event call was lost.
  3. The target field pointed to the element that triggered the event, not necessarily the element that owned the event. The latter isn't available from the event object for IE.
I wanted to create a library that:
  1. Could be called to set for an element event that already had others functions attached - either by HTML or due to earlier calls. Events needed to be called from most recent to oldest set.
  2. Would provide an event object already normalised for browser differences.
  3. Would provide an owner field that worked across all browsers.
  4. Could ease coding by setting multiple events for a single element, and multiple elements with the same event.

Usage

The core method is called, surprisingly enough, Events.add(). It requires a reference to the element, the event name and a function to run when the event is triggered.
  • The event name, as with the DOM equivalent, does not start with "on". In other words, use "click", not "onclick".
  • It will call multiple action methods for a single event.
  • If the HTML had an event set, this event will be added as well.
  • The action function has a normalised event object as its one parameter.
  • It includes valid target and owner fields.
Events.add( menu, "click",
  function( event)
    {
      Panel.menuHover = false;
      menu.style.display = "none";
      Panel.hideShield( menu.panel);
      return true;
    });

 

This can still cause a tedious amount of coding when a lot of events need to be set for a single element. I use an addEvents() method for these cases. It still sets events on a single element, but now it picks up these events from the methods of an object called events. This makes for some clear self-documenting code.
Events.addEvents( tabBarTextNode, Panel.tabActions);
...
Panel.tabActions = {events : {}};

Panel.tabActions.events.click =
  function( evt)
    {
      var panel
        = Elements.getPanel( evt.target).panel;
      Panel.setFocus( panel);
    }

Panel.tabActions.events.contextmenu = 
  function( evt)
  {
    var panel = Elements.getPanel(
                evt.target).panel;

    var menu = Panel._contextMenu(
                panel, Mouse.location( evt));

    menu.style.top =
      Panel.frame.height - menu.offsetHeight;
      
    return false;
  }

 

Less common - but just as important - are the situations where the same event and action need to be set on many elements. This following method has to use the parameters in a different order with the elements at the end, so that we can add as much as is needed.
Events.addSameEvent( "contextmenu",
  function() {alert('not allowed');},
  panel.titleBar, panel.resizeBox,
  panel.shadowRight, panel.shadowBottom);

 

This last method is going to take some describing - as is always the case when objects are used. It's used if you need to add an event to multiple elements that can be used to call multiple actions. None of the methods above will do the job. We can add multiple events to a single element, but we can't add a new event and have it be available to more than one element. The object newList resolves this deficiency: it creates a method that's also an object, so it can have state as well as function. The state is used to point to a list of events, rather than attach them to the element as we do above.
// Called once to create event function objects.
Mouse.events = {};
Mouse.events.mousemove
  = Events.newList( "mousemove");
  
Mouse.events.mouseup
  = Events.newList( "mouseup");

Mouse.events.blur
  = Events.newList( "blur");
...
// Called for each object we want
// to trigger the event list for.
// Note that the events added are
// function object, not just functions.
Mouse.initialise = function( w)
  { Events.addEvents( w, Mouse); }
...
// Elsewhere we add new events that will be
// called for each registered element
Mouse.events.mousemove.add( Panel.onmousemove);
Mouse.events.mouseup.add( Panel.onmouseup);

 

The mouse object here is the only occasion so far when I've used this more complex structure. Every window registers itself for Mouse events by calling Mouse.initialise(). Other modules that need to do something special on these actions register themselves with the action function objects using add(). To put it simply, Panel.onmousemove() will be called for each registered window.

Now, I suppose you want the library code. Well you can't have it yet. This article is already too long. I'll release it next week, so nyah.

Monday, September 05, 2005

JavaScript Events - Part 2 - The Event Object

I've no intention to describe the event object in detail. After all, the subject could easily fill a book (and it has). Given the depth to which I use this object, I will describe the browser differences I've found and how I've overcome them.

Retrieving The Event Object

In Mozilla the event object is passed to the event function as the one and only parameter. IE, however, has a nasty global variable called event. Does that make IE single threaded? Probably. I don't want to even think of the havoc multiple threads running the same Java code would cause. I think Mozilla must handle it. I can set a breakpoint in an event manager and have setTimeout() JavaScript trigger while still stopped in the debugger.

When an event is set from a HTML tag, Mozilla hides the difference between the two schemes.

<a id="test" href="" onclick="runOnClick(event)">
<script>
alert( document.getElementById( "test"));
</script>

 

generates different implicit JavaScript depending on the platform. Mozilla will create:
function( event) { runOnClick(event); }

 

While IE will generate:
function() { runOnClick(event); }

 

This is quite clever. This means that events set in HTML tags can all get the event in the same way.

Events set on an element by JavaScript is not so lucky. They have to account for the different platforms:

document.getElementById( "test").onclick = function( evt)
  {
    evt = evt || event;
    ...
  }

 

This is the simplest working method for retrieving the event object. We try and pass it in. If the event caller does not have an argument we use the global event object used by IE. For this reason we have to call the argument evt so as not to hide the global one called event.

The Event Target

The event target is the HTML element that captures the event. This isn't necessarily the element that event has set up. It's more often a child of that element. You can, for example, set an onclick event for a table. The target returned could be the TD cell element. IE does not have a target field in it's event object. It uses srcElement instead. The easiest way to normalise the object is to always have code like:
evt.target = evt.target || evt.srcElement;

 

The Event Owner

The owner is the element that has the event that was triggered as an attribute. This is more often of use than the target, because it is the element we most associated with the even in our code. The problem is that IE does not provide any direct way to get this item. In Mozilla this refers to it, as does the field currentTarget.

There are a number of ways around this. If the event is set in the HTML you can call a function giving the ID of the calling element. More general code could walk up from the target element until it finds an element with the correct event. The most general solution uses some of the more obtuse properties of the JavaScript OO system to set the owner. If the event is set functionally, you can have an inner function that refers to the element being set that is an argument to the outer function. This becomes a dirty circular reference - so that the event data is not released when the outer function returns - but it does the job. This last method isn't obvious, so perhaps an example will clear it up. The code calls an action method with a normalised event object. Note that the element argument for the add function is used inside a event function that can be called at any arbitrary time later. It's magic.

Events.add = function( element, name, action)
  {
    ...
    element["on"+name] = function( evt)
      {
        evt = evt || event;
        evt.target = evt.target || evt.srcElement;
        evt.owner = element;
        action( evt);
      }
  }

 

Monday, August 29, 2005

JavaScript Events - Part 1 - Setting Events

This is part 1 of a 3 part series.
  • Part 1: Setting Events
  • Part 2: The Event Object.
  • Part 3: A General Usable Event Manager. I've just refactored the JavaScript events system for Adept. I initially chose to implement the menu system by generating HTML rather than by building objects. Bad choice. It highlighted the operational inconsistencies between 3 completely different ways to add an event to an element.

    Three Event Addition Methods

    1. In HTML as in <a onclick="myfunc">.
    2. As an Attribute as in element.onclick = myfunc;
    3. Using the DOM Model as in element.addEventListener( "click", myfunc, false);.

    The HTML Event

    You can set an event for an element directly in the HTML using the event name preceded by "on". The code generates a function and compiles the attribute value text as the function body. This means that you are not running in the same context as when you programatically attach an event, since your code is running inside a method body. One browser inconsistency is overcome by this method: IE generates function(){yourcode} while Mozilla generates function(event){yourcode}. This allows both event types to access the event object as event even though it is passed in in Mozilla and is a global for IE. Warning: Don't use this to reference the element firing the event. It works for Mozilla, but not for IE.

    Event Attribute

    In the pre-DOM world, attributes were just fields on the HTML element. For backwards compatability they still are and always will be (for HTML anyway). So,
    element.onclick = function() { alert( "click"); }
    

     

    will work. The problem is that a single element can only have one event function per event type. This makes it difficult to produce library type code for JavaScript.

    DOM Events

    If events are set using the addEventListener() method they are stacked and all events added are fired. Perfect, except that IE (as of 6) does not support this part of the DOM.

    Summary

    So, what are the problems?
    1. Event setting that's portable across browsers does not allow more than one event per type per element.
    2. addEventListener() is not platform independent.