Skip to content

Instantly share code, notes, and snippets.

@gu-stav
Last active January 7, 2020 21:57
Show Gist options
  • Save gu-stav/9c03a8763cb0e88aeb13 to your computer and use it in GitHub Desktop.
Save gu-stav/9c03a8763cb0e88aeb13 to your computer and use it in GitHub Desktop.
An accessible HTML-Stepper Component.

#Accessible HTML-Stepper#

It will provide some label-magic through aria-label, so that the buttons actually have a meaning, even if you can't see them.

Styling is not part of it, so you can use whatever you want. The plugin itself doesn't know anything about responsiveness. It does not depend on any plugins.

TODOs/ Ideas

  • Option for limiting the total & minimal Amount
  • Provide Web-Component
  • Let users pass in a set of elements
  • Possibility to load via AMD-Loaders

##Usage##

Minimal HTML-Setup:

<div class="stepper"
     data-stepper-label-up="Add {{steps}} bed to {{sum}}."
     data-stepper-label-down="Remove {{steps}} bed from {{sum}}.">

  <label for="stepper-1-ipt">Amount of booked beds:</label>

  <button data-stepper-button="down">-</button>
  <input type="text" id="stepper-1-ipt" data-stepper-target />
  <button data-stepper-button="up">+</button>
</div>

Initialize the Javascript:

new Stepper( document.querySelectorAll( '.stepper' )[0] )
  .init();

Passing options:

<div class="stepper"
      data-stepper-steps-up="2"
      data-stepper-steps-down="2"
      data-stepper-label-up="Add {{steps}} bed to {{sum}}."
      data-stepper-label-down="Remove {{steps}} bed from {{sum}}." />

The same is also possible by passing options in javascript.

var stepper = new Stepper( document.querySelectorAll( '.stepper' )[0] );

/* prefix used for data attributes */
stepper.options.prefix = 'stepper';

stepper.options.steps.up = 2;
stepper.options.steps.down = 2;

stepper.options.label.up = "Add {{steps}} bed to {{sum}}.";
stepper.options.label.down = "Remove {{steps}} bed from {{sum}}.";

/* called, when step > 1 */
stepper.pluralize = function( singular ) {
  /* this instanceof Stepper === true */
  return singular.replace( 'bed', 'beds' );
};

stepper.init();

Pluralisation can also be done through data attributes:

<div class="stepper"
     data-stepper-label-up="Add {{steps}} bed to {{sum}}."
     data-stepper-label-up-plural="Add {{steps}} beds to {{sum}}."
     data-stepper-label-down="Remove {{steps}} bed from {{sum}}."
     data-stepper-label-down-plural="Remove {{steps}} beds of {{sum}}." />
<!doctype />
<html>
<body>
<div class="stepper"
data-stepper-label-up="Add {{steps}} bed to {{sum}}."
data-stepper-label-down="Remove {{steps}} bed of {{sum}}.">
<label for="stepper-1-ipt">Amount of booked beds:</label>
<button data-stepper-button="down">-</button>
<input type="text" id="stepper-1-ipt" data-stepper-target />
<button data-stepper-button="up">+</button>
</div>
<script type="text/javascript"
src="stepper.js"></script>
<script type="text/javascript">
var stepper = new Stepper( document.querySelectorAll( '.stepper' )[0] );
stepper.pluralize = function( singular ) {
return singular.replace( 'bed', 'beds' );
};
stepper.init();
</script>
</body>
</html>
(function( window, document ) {
'use strict';
/**
* Initializes a new stepper
* @param {HtmlElement} the Stepper-wrapper element
*/
var Stepper = function( wrapper ) {
this.wrap = wrapper;
this.options = {
prefix: 'stepper',
defaultStart: 0,
};
this.options.steps = {
up: this._getData( this.wrap, 'StepsUp' ) || 1,
down: this._getData( this.wrap, 'StepsDown' ) || 1,
};
this.options.label = {
up: this._getData( this.wrap, 'LabelUp' ) || '',
down: this._getData( this.wrap, 'LabelDown' ) || '',
};
[ 'button', 'target' ].forEach(function( entry ) {
this[ entry ] = this.wrap
.querySelectorAll( '[data-' +
this.options.prefix + '-' +
entry + ']' );
}.bind( this ));
/* work only on a single element for the moment */
this.target = this.target[0];
return this;
};
/**
* Adds the bindings & initializes the labels.
*/
Stepper.prototype.init = function() {
var initButton = function( button ) {
var callback = function( e ) {
e.preventDefault();
var target = e.target,
direction = this._getData( target, 'Button');
this.updateLabel( target, direction );
this[ direction ].call( this, this.options.steps[ direction ] );
}.bind( this );
this.updateLabel( button );
button.addEventListener( 'click', callback );
}.bind( this ),
buttons = Array.prototype.slice.call( this.button, 0 );
/* initializes the buttons with bindings & initial label */
buttons.forEach( initButton );
return this;
};
/**
* Add steps
* @param {Integer} The amount, to move the value up.
*/
Stepper.prototype.up = function( steps ) {
this._changeValue( 'up', steps );
};
/**
* Remove steps
* @param {Integer} The amount, to lower the value.
*/
Stepper.prototype.down = function( steps ) {
this._changeValue( 'down', steps );
};
/**
* Update the label a single button
* @param {HtmlElement} Button which should be updated.
*/
Stepper.prototype.updateLabel = function( btnInstance, direction ) {
var capitalize = function( str ) {
return str.charAt(0).toUpperCase() + str.slice(1)
};
var direction = direction || this._getData( btnInstance, 'Button' ),
steps = this.options.steps[ direction ],
label = this.options.label[ direction ],
pluralIndex = 'label' + capitalize( direction ) + 'Plural';
if( steps !== 1 ) {
if( this.pluralize ) {
label = this.pluralize.call( this, label );
} else {
label = this._getData( this.wrap, pluralIndex ) || label;
}
}
label = label.replace( '{{steps}}', steps );
label = label.replace( '{{sum}}', this._getInputValue() );
btnInstance.setAttribute( 'aria-label', label );
};
/**
* Get the current input value
* @return {Integer} Current value.
*/
Stepper.prototype._getInputValue = function() {
return this.target.value ?
Number( this.target.value ) :
Number( this.options.defaultStart );
};
/**
* change the inpute value.
* @param {String} Direction, in which the value should change. ["up", "down"]
* @param {Integer} Amount of the change.
*/
Stepper.prototype._changeValue = function( direction, steps ) {
var val = this._getInputValue(),
steps = typeof steps === 'number' ? steps : Number( steps ),
res = ( direction === 'up' ? ( val + steps ) : ( val - steps ) );
this.target.value = res;
};
/**
* Returns data, while respecting the prefix, of a given element.
* @param {HtmlElement} The element.
* @param {String} The data index, without the prefix.
* @return {String} Data value.
*/
Stepper.prototype._getData = function( el, index ) {
return el.dataset[ this.options.prefix + index ];
};
window.Stepper = Stepper;
}( window, document ));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment