patternjavascriptMinor
Object-oriented widget for reuse
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
```
$(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
Reference
There are a couple of things regarding reusability going on in here:
-
-
Event binding is handled internally via
-
Ideally the widget would manage its own application state logic via
-
You can call public methods from other places via the automatic interface the widget provides, e.g.
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.