HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

Object-oriented widget for reuse

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
forreuseorientedobjectwidget

Problem

I have been using a pattern similar to this basic structure below. Can anyone please let me know if this is a correct way to code an object-oriented widget for reuse? I am looking to make code than can also be extended easily and modular.

```
$(function(){
// wrap in jquery .fn wrapper so that may be called on a
function $lider(autoplay, velocity, controls){
var $lider = [
// props n methods, accessible through data-slider-* attributes
{
settings : {
envokeBecause : $('[data-widget~="slider"]'),
autoplay : autoplay,
speed : velocity,
showControls : controls,
slideSize : '100%',// 25% will show 4 slides if the parent container is 100% width and so on
},
bindings : {
slideRail : $('[data-function~="slide-rail"]'),
slide : $('[data-function~="slide"]'),
nextButton : $('[data-function~="next"]'),
prevButton : $('[data-function~="prev"]'),
playButton : $('[data-function~="play"]'),
pauseButton : $('[data-function~="pause"]'),
stopButton : $('[data-function~="stop"]')
// attach this functionality to the DOM
},
methods : {
slideNext : function(){ slideRail.animate({left: '-=100%'}, velocity) },
slidePrev : function(){ slideRail.animate({left: '+=100%'}, velocity) },
slideRestart : function(){ slideRail.animate({left: '0%'}, velocity) },
slideStart : function(){ window.$liderTimer = setInterval(slideNext, velocity) },
slidesCount : function(){ slideRail.children().size()
}
}
}
]
$.each($lider, function(){
// iterate through all of the slider objects properties
window.SliderProps = this;
// set slider to be accessible to the global scope
});
// slider props stored as vars
var $liderHook = SliderProps.settings.envokeBecause;
var slideRail = SliderProps.bindings.slideRail;
var slide = SliderProps.bindings.slide;
var play = SliderProps.bindi

Solution

I'd suggest taking a look at jquery-boilerplate, especially the jqueryUI widget portion.

I started refactoring your code in a true $widget manner. It's not completely functional but should point you in the right direction.

;(function ( $, window, document, undefined ) {
  $.widget("yourCustomNamespace.slider", {

    /*
     * Options to be used as defaults:
     */

    options: {
      autoplay: true,
      controls: true,
      duration: 750,
      slideSize: '100%',
      selectors: {
        slideRail: '[data-function~="slide-rail"]',
        slide: '[data-function~="slide"]',
        next: '[data-function~="next"]',
        prev: '[data-function~="prev"]',
        play: '[data-function~="play"]',
        pause: '[data-function~="pause"]',
        stop: '[data-function~="stop"]'
      }
    },

    /*
     * Private Methods:
     */

    _create: function() {
      this._setOption("current", 0);
      this._setOption("total", this.element.children().length);
      this._on(this.element, {
        "hover " + this.options.selectors.slideRail: $.proxy(this.stop, this),
        "click " + this.options.selectors.next: $.proxy(this.next, this),
        "click " + this.options.selectors.prev: $.proxy(this.prev, this),
        "click " + this.options.selectors.stop: $.proxy(this.stop, this),
        "click " + this.options.selectors.pause: $.proxy(this.pause, this),
        "click " + this.options.selectors.play: $.proxy(this.play, this)
      });
      if(this.options.autoplay) {
        this.start();
      }
    },
    _destroy: function() {
      this._off(this.element, "click hover");
    },
    _setOption: function(key, value) {
      switch (key) {
        case "current":
          // Update internal object ...
          this.options[key] = value;
          // Update DOM ...
          // this.element.find(this.options.selectors.slideRail.animate({...}, this.options.duration) ...
          break;
        default:
          this.options[key] = value;
          break;
      }
      return this._super("_setOption", key, value);
    },

    /*
     * Public Methods:
     */

    next: function() {
      // TODO: Account for overflow, this.options.total
      this._setOption("current", this.options.current + 1);
    },
    prev: function() {
      // TODO: Account for underflow, this.options.total
      this._setOption("current", this.options.current -1);
    },
    restart: function() {
      this._setOption("current", 0);
    },
    start: function() {
      this.interval = window.setInterval($.proxy(this.next, this), this.options.duration);
    },
    stop: function() {
      window.clearInterval(this.interval);
    },
    getCount: function() {
      return this.options.total;
    }
  });
})( jQuery, window, document );


Reference

There are a couple of things regarding reusability going on in here:

-
options are your default parameters that can be overwritten on initializing a new widget instance, e.g. $('[data-widget~="slider"]').slider({autoplay: false}); would overwrite the default of autoplay: true

-
Event binding is handled internally via this._on()

-
Ideally the widget would manage its own application state logic via this._setOption(). That way you can distinguish between logic and DOM interaction. Whenever a value changes, you update the DOM accordingly.

-
You can call public methods from other places via the automatic interface the widget provides, e.g. $('[data-widget~="slider"]').slider("next")

Code Snippets

;(function ( $, window, document, undefined ) {
  $.widget("yourCustomNamespace.slider", {

    /*
     * Options to be used as defaults:
     */

    options: {
      autoplay: true,
      controls: true,
      duration: 750,
      slideSize: '100%',
      selectors: {
        slideRail: '[data-function~="slide-rail"]',
        slide: '[data-function~="slide"]',
        next: '[data-function~="next"]',
        prev: '[data-function~="prev"]',
        play: '[data-function~="play"]',
        pause: '[data-function~="pause"]',
        stop: '[data-function~="stop"]'
      }
    },

    /*
     * Private Methods:
     */

    _create: function() {
      this._setOption("current", 0);
      this._setOption("total", this.element.children().length);
      this._on(this.element, {
        "hover " + this.options.selectors.slideRail: $.proxy(this.stop, this),
        "click " + this.options.selectors.next: $.proxy(this.next, this),
        "click " + this.options.selectors.prev: $.proxy(this.prev, this),
        "click " + this.options.selectors.stop: $.proxy(this.stop, this),
        "click " + this.options.selectors.pause: $.proxy(this.pause, this),
        "click " + this.options.selectors.play: $.proxy(this.play, this)
      });
      if(this.options.autoplay) {
        this.start();
      }
    },
    _destroy: function() {
      this._off(this.element, "click hover");
    },
    _setOption: function(key, value) {
      switch (key) {
        case "current":
          // Update internal object ...
          this.options[key] = value;
          // Update DOM ...
          // this.element.find(this.options.selectors.slideRail.animate({...}, this.options.duration) ...
          break;
        default:
          this.options[key] = value;
          break;
      }
      return this._super("_setOption", key, value);
    },

    /*
     * Public Methods:
     */

    next: function() {
      // TODO: Account for overflow, this.options.total
      this._setOption("current", this.options.current + 1);
    },
    prev: function() {
      // TODO: Account for underflow, this.options.total
      this._setOption("current", this.options.current -1);
    },
    restart: function() {
      this._setOption("current", 0);
    },
    start: function() {
      this.interval = window.setInterval($.proxy(this.next, this), this.options.duration);
    },
    stop: function() {
      window.clearInterval(this.interval);
    },
    getCount: function() {
      return this.options.total;
    }
  });
})( jQuery, window, document );

Context

StackExchange Code Review Q#59453, answer score: 3

Revisions (0)

No revisions yet.