18 December 2008

A Simple Javascript Slider

HTML5 might well have a native slider element (as part of Webforms 2.0); jQuery and MooTools have slider components, and the StackOverflow slider page points to these and other implementations.

In the spirit of NIH, I wrote my own. Rolling your own is one way to learn to appreciate a language; afterwards you get bored and start trusting the libraries. Here's a screenshot:

This slider invokes a callback function (onchange) when necessary, with the percentage value of the slider. It's up to you to translate that into dates, numbers, colours, or whatever it is you're sliding. Use like this:

<table>
  <tr>
    <th>
      Slider
    </th>

    <td id="foo_slider_min">
      <img src='/images/slider_min.png'>
    </td>
    
    <td valign="top" style="padding-top:5px;">
      <div id="foo_slider_bar" style="width:128px;background-image:url('/images/slider-bar.png');background-repeat:repeat-x;position:relative;">
         
        <div id="foo_slider_button" style="position:absolute;top:0;left:95%;">
          <img src="/images/slider-button.png" alt="editor pixel size controller"/>
        </div>
      </div>
    </td>

    <td id="foo_slider_max">
      <img src='/images/slider_max.png'>
    </td>

  </tr>
</table>

Initialise this with

var onFooChange = function(percent) {
    // update foo so it's at percent%
  }
  var sliderControl = new Slider("foo_slider", onFooChange);

If your slider's name (the 1st argument to new Slider()) is "foo_slider", it expects the following DOM elements to exist:

  • foo_slider_bar - the object whose width is considered a value of 100%
  • foo_slider_button - the object whose position relative to foo_slider_bar determines the value of the component
  • foo_slider_min - an object which sets the slider to 0% when clicked
  • foo_slider_max - an object which sets the slider to 100% when clicked

Here's the actual Slider code, use as you please -

function Slider(name, onchange) {
  var sliderBar = $(name + '_bar');
  var sliderButton = $(name + '_button');
  var sliderMin = $(name + '_min');
  var sliderMax = $(name + '_max');
  var min = 0;
  var self = this;

  var max = function() {
    return parseInt(sliderBar.style.width) - 2;
  };

  var setButtonPosition = function(px) {
    if (px > max()) {
      px = max();
    } else if (px < min) {
      px = min;
    }
    sliderButton.style.left = "" + (px - 8) + "px";
    onchange(px / max());
  };

  sliderBar.onmousedown = function(event) {
    document.onmousedown = returnFalse;

    offsets(event, sliderBar, function(y, x) {
      setButtonPosition(x);
    });

    document.onmousemove = function(event2) {
      offsets(event2, sliderBar, function(y, x) {
        setButtonPosition(x);
      });
    }

    document.onmouseup = cancelMouse;
  };

  sliderMin.onclick = function() {
    self.resetTo(0);
  };

  sliderMax.onclick = function() {
    self.resetTo(1);
  };

  this.resetTo = function(proportion) {
    setButtonPosition(proportion * max());
  }
}

10 December 2008

Objects in Javascript

As a consequence of building iconfu.com, I've used more javascript in the past four months than in all the previous ten years combined. Javascript is not difficult, but its superficial similarities with java can be misleading for someone, such as I, coming from that language. Javascript is also, surprisingly, a respectable language, tarnished only by browser incompatibilities and buggy implementations (I'm thinking of one browser implementation in particular).

Here are the three worst stumbling blocks I faced on the slow path of figuring out how objects work in javascript: (1) what does "this" refer to? (2) how to write a constructor, and (3) how to define functions on objects.

If you're just scripting mouse events on a web page, you won't need much of this, but it's handy if you're doing anything serious with javascript (like, writing an image editor, ahem).

1. "this" refers to the object on which the function was called, not necessarily the place where the function was defined

Unlike java, javascript lets you copy methods about from object to object. So, suppose you have

var Truck = {
  fuel : 100,

  drive: function(km) {
    this.fuel = this.fuel - km;
  }
}

You can legitimately write

var Car = {
  fuel: 100
};

Car.drive = Truck.drive;

And then,

Car.drive(10);
alert(Car.fuel); // alerts 90

The "this" reference in the drive function references the callee's object, in this case Car, even though "this" originally referred to Truck.

"this" can be a source of much grief when setting up event listeners on objects. Suppose you want to install an onclick handler on a DOM element that allows your user interact with your vehicle, thus:

<a id='drive_truck'>drive the truck!</a>

and

var Truck = {
  // same as before, plus

  init : function() {
    document.getElementById('drive_truck').onclick = function(event) {
      // this.drive();  // FAIL: "this" is the span element, not the Truck object
      Truck.drive(); // this works, but it's ugly!
      someOtherStuff();
    };
  }
}

Truck.init();

the moral of the story: this inside an event handler function refers to the DOM element on which the event was fired - NOT the place where the function was defined. Fortunately, there are better ways to declare objects so that this ugliness can be avoided.

2. Two ways of creating objects: direct declaration (hash), or via constructor

The Truck example above shows a simple, direct way of creating objects, and it's common to do this all over the place in javascript. Mostly, it's the right thing to do, if you want something that's similar to a HashMap in java.

But, javascript also has constructors, and the funny thing about a javascript constructor is that it looks just like an ordinary function.

function Truck() {
  this.fuel = 100;

  this.drive = function(km) {
    this.fuel = this.fuel - km;
  }

  var self = this;

  document.getElementById('drive_truck').onclick = function(event) {
    // this.drive();  // FAIL: "this" is the span element, not the Truck object
    self.drive(); // this works, but you need the "self" local variable
    someOtherStuff();
  };
}

var myTruck = new Truck();
myTruck.drive(60);
alert(myTruck.fuel); // alerts 40

The Truck() function is, in fact, the Truck constructor. When you call new Truck(), instead of just calling Truck(), the "this" keyword within the function references a new object, for which this instanceof Truck returns true.

The difference between this approach and the previous one is that now you have a typed object, and you can have multiple instances of the same type of object.

3. Two ways of defining functions on objects: in constructor, or via prototype

So there are a few ways of associating functions with objects: you can declare them in the constructor, as we did above, or you can just add them later, as we did with the "Car" example. A problem arises with the constructor method if you define functions on your objects within the constructor: each object instance you create will have its own unique instance of each function as well. This can potentially introduce memory issues if you're creating a lot of objects.

The prototype approach eliminates this issue. Every object has an associated "prototype" object, to which you can add properties. Truck.prototype is just another object, but one treated with special respect by all Truck objects.

function Truck() {
  this.fuel = 100;

  var self = this;

  document.getElementById('drive_truck').onclick = function(event) {
    // this.drive();  // FAIL: "this" is the span element, not the Truck object
    self.drive(); // this works, but you need the "self" local variable
    someOtherStuff();
  };
}

Truck.prototype.drive = function(km) {
  this.fuel = this.fuel - km;
}

var myTruck = new Truck();
myTruck.drive(60);
alert(myTruck.fuel); // alerts 40, same as before

When the javascript interpreter searches a Truck instance for a "drive" property and finds nothing, it will consult "Truck.prototype" and see if there's anything appropriate there. If there is, the interpreter behaves as if that property were originally defined on the Truck instance itself. So the "this" reference remains intact for all properties inherited from the object's prototype.

Don't confuse the "prototype" concept with the "prototype.js" library. Javascript is a "prototype-based" object-oriented language, unlike java, which is a "class-based" object-oriented language. The prototype.js library relies heavily on the prototype feature of javascript, that's all.

HTH, let me know how it works for you ...

08 December 2008

Do You Really Want Analytics?

Kontra's article on Google, linking Android and Youtube via video search, speech recognition and phoneme harvesting, made a lot of sense, and I realised there's even more cleverness in that whole google product suite. I have Google Analytics on this site, and enjoy watching my bounce rate bounce and my visitors jump up and down and pages per visit and time on site and all this statistical goodness generally so much that I risk carpal tunnel from continuously hitting the refresh button on my permanently-open multiple analytics tabs in firefox.

I don't know why Analytics is free, but here's a possible reason: when google sends a visitor to your site from a search result, the only way they can judge the quality of that result (the relevance of your site to the search query), without analytics, is whether and how soon the user clicks on another result for the same search. Now that you have analytics installed, they have much richer and deeper information: how long does the user stay on your site, how many and which pages to they visit? Having this information allows them decide whether that particular result was in fact a "good" hit.

I have no idea if google correlates search data with analytics data. But the point is, they have the data. And if I were google, I would do it. Wouldn't you? It's not necessarily a Bad Thing, but it's nice to know what you're paying for when it's free.