0

I am not great with anything beyond basic javascript so please forgive the simple question.

I am using the jsDraw2D library. This library has a graphics object that looks something like the following:

function jsGraphics(canvasDivElement) {
  var canvasDiv;
  this.drawLine = drawLine;
  function drawLine(point1, point2) {
    // do things with canvasDiv
  }
}

You use it like this:

var gr = new jsGraphics(document.getElementById('canvas'))
gr.drawLine(new jsPoint(0,0), new jsPoint(10,10))

I would like to add a function to jsGraphics so that I can call

gr.getCanvasElement()

Is there a way to do this without editing the library itself?

I have tried

jsGraphics.prototype.getCanvasElement = function() { return canvasDiv }

but this doesn't seem to work. I have an intuitive feeling that its something with that new keyword but if you could explain why exactly it doesn't that would be helpful too.

2
  • Are you passing document.getElementById('canvas') to jsGraphics just like that? If you are you can just call document.getElementById('canvas') to get the same thing canvasDiv holds. Commented Oct 29, 2009 at 15:01
  • Well what I want isn't really the canvasDiv, but the width/height of it that will be used in some simplifying facade methods that I plan to add. The page itself will have many jsGraphics objects however and I will need to work with each one in turn. Commented Oct 29, 2009 at 15:06

3 Answers 3

4

Nope, this isn't using the normal JavaScript prototype-based inheritance, it's adding a separate drawLine function to every instance of jsGraphics, each with a closure around its own canvasDiv variable.

Once function jsGraphics() { is closed } there is no further way to access the canvasDiv variable at all, unless one of the functions inside provides access to it. This is often done deliberately to make private variables, explicitly to stop you getting at canvasDiv.

Sign up to request clarification or add additional context in comments.

2 Comments

Really? I thought just about anything was possible in javascript...Technically I want to treat it as a protected variable used from inside some methods I will add in the future if that makes it any easier.
Yeah, I kind of prefer the debugging-friendly Python approach where you would set it as this._canvasDiv and woe betide anyone who uses a variable starting with an underline unless they're a member function (or they really know what they're doing). This would require you to change the constructor function, though.
3

You can't just get to the canvasDiv element necessarily, because if it is never assigned to the object in the constructor using the this keyword, the reference to that object exists in a closure created by the constructor function itself.

You can however wrap the constructor in a new constructor and then set the prototypes equal:

function myJsGraphics(canvasDivElement) {
  this.canvasDiv = canvasDivElement;
  jsGraphics.call(this, cavasDivElement);
}

myJsGraphics.prototype = jsGraphics.prototype;

Now you should be able to access the element using your new constructor:

var obj = new myJsGraphics(document.getElementById('blah-elem'));
elem = obj.canvasDiv;

The whole closure thing is a little weird if you're not used to it, but the gist is that functions defined in a certain scope but available elsewhere can refer to variables in the scope in which they were defined at all times. The easiest example is when you have a function that returns a function:

function makeIncrementer(start) {
  return function () { return start++; };
}

var inc = makeIncrementer(0);
var inc2 = makeIncrementer(0);
inc(); // => 0
inc(); // => 1
inc(); // => 2
inc2(); // => 0

That reference to the "start" variable is "closed over" when the function is returned from the makeIncrementer function. It cannot be accessed directly. The same thing happens in an object's constructor, where local variables are "closed" into the member functions of the object, and they act as private variables. Unless there was a method or variable reference to a private member defined in the constructor, you just can't get access to it.

1 Comment

jsGraphics.call(this); should be jsGraphics.call(this, canvasDivElement);
2

This "private state" technique has become more and more idiomatic in the last few years. Personally I've found it oddly limiting when trying to quickly debug something from the console or override behavior in a 3rd party library. It's one of the few times I think "Damn it, why can't I do this with the language". I've exploited this bug in Firefox 2 to good effect when I've really needed to debug a "private variable" quickly.

I'd be curious to know when others use this idiom or when they avoid it. (@bobince I'm looking at you).

Anyway @bobince has pretty much answered your question (nutshell: No, you can't access the canvasDiv variable from the scope you are in).

However, there is one thing you can do that is a tradeoff between a hack or editing the 3rd-party library (I always go for the hack ;): you can augment the object to hold a reference you know you will need later.

Hack 1: if you control the object instantiations yourself:

var canvas = document.getElementById('canvas');
var gr = new jsGraphics(canvas);
gr._canvasDiv = canvas; // Augment because we'll need this later

// Sometime later...

gr._canvasDiv; // do something with the element

If the library supports some concept akin to a destructor (fired on unload of the page or something), be sure to null out or delete your property there too, to avoid memory leaks in IE:

delete gr._canvasDiv;

OR Hack 2: Overwrite the constructor just after including the library:

// run after including the library, and before
// any code that instantiates jsGraphics objects
jsGraphics = (function(fn) {
    return function(canvas) {
        this._canvasDiv = canvas;
        return fn.apply(this, arguments)
    }
}(jsGraphics))

Then access the element (now public) as gr._canvasDiv. Same note about deleting it on page unload applies.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.