2
\$\begingroup\$

I sat down to write a demo HTML5-ish page that lets a user to perform following operations on a canvas - draw lines, draw a filled rectangle, and reset the canvas. Each operation is represented by a button, clicking on which the operation is carried out.

My initial attempt ended up as a mingled HTML and JavaScript code. So, after a bit of reading, I used the addEventListener() method to add the events to the buttons, thus eliminating the onclick code in HTML.

Now, I followed two approaches while writing the JavaScript code.

The first, simpler approach:

// Instead of forcing all event handlers to know the canvasId by themselves, I chose to hardwire it in window.onload() function,           inside which I also add the event handlers to their respective buttons.

// I need to pass canvasId to the event handlers, thus forcing me to write anonymous functions.
// I won't have a future reference to the event handlers unless, of course, I store them in variables, which I find clumsy.

window.onload = function()
{
    var canvasId = "DrawingBoard";

    var rectangleButton = document.getElementById("rectangleButton");
    rectangleButton.addEventListener("click", function() { drawFilledRectangle(canvasId); }, false);

    var linesButton = document.getElementById("linesButton");
    linesButton.addEventListener("click", function() { drawLines(canvasId); }, false);

    var resetButton = document.getElementById("resetButton");
    resetButton.addEventListener("click", function() { resetCanvas(canvasId); }, false);
}

function drawFilledRectangle(canvasId)
{
    resetCanvas(canvasId);

    var canvas = document.getElementById(canvasId);
    var context = canvas.getContext("2d");
    context.fillStyle = "#eee";
    context.fillRect(50, 25, 150, 120); 
}

function drawLines(canvasId)
{
    resetCanvas(canvasId);

    var canvas = document.getElementById(canvasId);
    var context = canvas.getContext("2d");

    for (var x = 0.5; x < canvas.width; x += 10)
    {
        context.moveTo(x, 0);
        context.lineTo(x, canvas.height - 1);
    }

    context.strokeStyle = "#eee";
    context.stroke();
}

function resetCanvas(canvasId)
{
    var canvas = document.getElementById(canvasId);
    var context = canvas.getContext("2d");
    context.clearRect(0, 0, canvas.width, canvas.height);
}

The second approach:

// I don't need to pass canvasId to each event handler.
// This way, I don't have to create anonymous functions, meaning that I always have a reference to the function - the function name.

// The structure still makes sense; this way, I have grouped the canvas operations along with the canvas id in a single namespace.
// Whatever you need to do with canvas, you can just add a function to the object literal CanvasOperations.

window.onload = function()
{   
    var rectangleButton = document.getElementById("rectangleButton");
    rectangleButton.addEventListener("click", CanvasOperations.drawFilledRectangle, false);

    var linesButton = document.getElementById("linesButton");
    linesButton.addEventListener("click", CanvasOperations.drawLines, false);

    var resetButton = document.getElementById("resetButton");
    resetButton.addEventListener("click", CanvasOperations.resetCanvas, false);
}

var CanvasOperations = 
{
    canvasId : "DrawingBoard",

    resetCanvas : function() 
    {
        var canvas = document.getElementById(CanvasOperations.canvasId);
        var context = canvas.getContext("2d");
        context.clearRect(0, 0, canvas.width, canvas.height);
    },

    drawLines : function()
    {
        CanvasOperations.resetCanvas(CanvasOperations.canvasId);

        var canvas = document.getElementById(CanvasOperations.canvasId);
        var context = canvas.getContext("2d");

        for (var x = 0.5; x < canvas.width; x += 10)
        {
            context.moveTo(x, 0);
            context.lineTo(x, canvas.height - 1);
        }

        context.strokeStyle = "#eee";
        context.stroke();
    },

    drawFilledRectangle : function()
    {
        CanvasOperations.resetCanvas(CanvasOperations.canvasId);

        var canvas = document.getElementById(CanvasOperations.canvasId);
        var context = canvas.getContext("2d");
        context.fillStyle = "#eee";
        context.fillRect(50, 25, 150, 120); 
    }
};

Note that I have provided my reasoning of both the approaches in their respective comments.

  1. Which approach is better?

  2. Are my reasons to lean towards the second one right?

  3. What approach should I take while using attributes like id or name of HTML elements in JavaScript code?

I think hardwiring should be as less as possible. Is there anything you can point me to for learning? Or is it just intuition thing, to be decided by me? I did Google on this, but couldn't find a right solution; or I might have failed to frame a proper Google search.

\$\endgroup\$
2
\$\begingroup\$

The second one is indeed "better", in my opinion. Although, "better" is a bit hard to define when both versions work. "More maintainable" might be a more precise way of putting it.

I might take it a step further, and add a constructor (i.e. class) that wraps the canvas element:

function DrawingCanvas(elementId) {
  this.element = document.getElementById(elementId);
  this.context = this.element.getContext("2d");
}

DrawingCanvas.prototype = {
  reset: function () {
    this.context.clearRect(0, 0, this.element.width, this.element.height);
  },

  drawLines: function () { ... draw stuff ... },
  drawFilledRectangle: function () { ... draw different stuff ... },
};

I'd also use addEventListener for the onload event:

window.addEventListener("load", function () {
  var canvas = new DrawingCanvas("DrawingBoard");
  document.getElementById("rectangleButton").addEventListener("click", canvas.drawFilledRectangle, false);
  document.getElementById("linesButton").addEventListener("click", canvas.drawLines, false);
  document.getElementById("resetButton").addEventListener("click", canvas.reset, false);
}, false);

Finally, a word of general advice: Don't use the "curly bracket on new line"-style in JavaScript.
JavaScript has some dumb parts to it, and one of them is that it'll automatically insert a semi-colon at the end of a line, if it thinks it's missing.

So, if you for instance have a function that returns an object literal, you can get into trouble:

function getObj() {
  return
    {
      x: 42
    }
}

will be interpreted as

function getObj() {
  return; // <- auto semi-colon
}
\$\endgroup\$
  • \$\begingroup\$ Yeah. I had wanted to imply "more maintainable" by "better"; the right word just hadn't come to the sleepy brain. :-) You are right about addEventListener() for window's onload, too; it's just that my main focus was the Canvas object. I will certainly read more about the constructor function; I bet that will be useful in future. \$\endgroup\$ – Jayesh Bhoot Oct 7 '12 at 8:57
  • \$\begingroup\$ I wasn't aware of the parsing style of JavaScript. I read a bit on it after reading your braces issue. It seems I will have to drop my all-time favorite brace-on-new-line style. \$\endgroup\$ – Jayesh Bhoot Oct 7 '12 at 8:58
  • \$\begingroup\$ @MrBhoot Brace-on-new-line has always been evil and wrong! Just kidding :) - you could also look into CoffeeScript, which compiles to JS, but where you almost never use curlies, and there are structures such as class that translate to JS's constructors/prototypes. But first, read Douglas Crockford's "JavaScript: The Good Parts" if you want to dig deeper. \$\endgroup\$ – Flambino Oct 7 '12 at 10:41
  • \$\begingroup\$ As you directed, I had a look at CoffeeScript; impressive, indeed! But I will concentrate on pure JavaScript for now, along with a library like jQuery. CoffeeScript - maybe some time later. :-) \$\endgroup\$ – Jayesh Bhoot Oct 8 '12 at 5:48
  • \$\begingroup\$ @MrBhoot Excellent choice! While you can use jQuery or anything other JS library, and simply write your own code in CoffeeScript (it is JS after all, just a different syntax), there's no substitute for really learning pure JS first. Depending on what you're used it, JS has some strange, strange stuff in it (again, I can recommend googling for Douglas Crockford; you can find videos of his talks on the subject). Once you feel you "get it", CoffeeScript is a great next step. \$\endgroup\$ – Flambino Oct 8 '12 at 8:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.