2

I have a conceptual question about how one would approach a particular problem from an object oriented perspective (note: for those interested in the namespacing here, I am using Google Closure). I am fairly new to the OOP JS game so any and all insight is helpful!

Imagine you want to create an object that initiates a carousel for each DOM element on the page that matches a classname .carouselClassName.

Something like this

/*
 * Carousel constructor
 * 
 * @param {className}: Class name to match DOM elements against to
 * attach event listeners and CSS animations for the carousel.
 */
var carousel = function(className) {
  this.className = className;

  //get all DOM elements matching className
  this.carouselElements = goog.dom.getElementsByClass(this.className);
}

carousel.prototype.animate = function() {
  //animation methods here
}

carousel.prototype.startCarousel = function(val, index, array) {
  //attach event listeners and delegate to other methods 
  //note, this doesn't work because this.animate is undefined (why?)
  goog.events.listen(val, goog.events.EventType.CLICK, this.animate);
}

//initalize the carousel for each
carousel.prototype.init = function() {
  //foreach carousel element found on the page, run startCarousel
  //note: this works fine, even calling this.startCarousel which is a method. Why?
  goog.dom.array.foreach(this.className, this.startCarousel, carousel);
}

//create a new instance and initialize
var newCarousel = new carousel('carouselClassName');
newCarousel.init();

Just playing around with OOP in JS for the first time, I've made a few observations:

  1. A property defined in my constructor object (this.classname) is available by the this operation in other prototype objects.
  2. A method defined in my constructor object or in the prototype is not available using this.methodName (see comments above).

Any additional comments on my approach to this are definitely welcome :)

gen_Eric
  • 223,194
  • 41
  • 299
  • 337
djreed
  • 2,673
  • 4
  • 22
  • 21

2 Answers2

2

I would suggest that you do not have your Carousel object represent all the carousels on the page. Each one should be a new instance of Carousel.

The problem that you are running into with this not being assigned properly can be fixed by "binding" those methods to this in your constructor.

E.g.

function Carousel(node) {
    this.node = node;

    // "bind" each method that will act as a callback or event handler
    this.bind('animate');

    this.init();
}
Carousel.prototype = {
    // an example of a "bind" function...
    bind: function(method) {
        var fn = this[method],
            self = this;
        this[method] = function() {
            return fn.apply(self, arguments);
        };
        return this;
    },

    init: function() {},

    animate: function() {}
};

var nodes = goog.dom.getElementsByClass(this.className),
    carousels = [];

// Instantiate each carousel individually
for(var i = 0; i < nodes.length; i++) carousels.push(new Carousel(nodes[i]));
Prestaul
  • 83,552
  • 10
  • 84
  • 84
  • Thank you. That was the next question I was going to ask actually - and I agree it seems more appropriate to instantiate each carousel individually. Thanks for demonstrating! One follow-up question re: binding these methods to 'this'. I found this (http://stackoverflow.com/questions/6527192/google-closure-bind-resolve-issues-with-the-this-keyword). The answer states that you should normally be able to bypass using goog.bind (Closure handles it for you). So, how might I structure this differently to avoid binding events? – djreed Apr 19 '12 at 21:54
1

Have a look at the reference for the this keyword. If you call one of the methods from your newCarousel object (for example: newCarousel.init();), this in the init method will point to the object. If you'd call a method on that, it's the same.

You always can retrieve properties from a object reference. If these properties are functions, and won't get executed in the right context (e.g. from an event handler), their this won't point to the newCarousel any more. Use bind() to get around that (forEach seems to take your third parameter as the context of each call).

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 1
    I think that third argument to the forEach might take care of that... I don't use Closure though, so I'm not certain... – Prestaul Apr 19 '12 at 17:30
  • Ah, surely. OP said it works - although I don't understand why because I don't know the lib. Updated answer. – Bergi Apr 19 '12 at 17:35