Skip to content

Commit

Permalink
Merge pull request #139 from alphagov/add-document-level-events-to-se…
Browse files Browse the repository at this point in the history
…lection-buttons

Add document level events to selection buttons
  • Loading branch information
dsingleton committed Oct 17, 2014
2 parents 54146ca + a490c81 commit bc2b6f1
Show file tree
Hide file tree
Showing 3 changed files with 770 additions and 340 deletions.
46 changes: 40 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -862,29 +862,63 @@ and stop the elements before they get to the bottom.

## Selection buttons

Script to support a specific design of radio buttons and checkboxes wrapped in `<label>` tags:
Script to support a design of radio buttons and checkboxes requiring them to be wrapped in `<label>` tags:

<label>
<input type="radio" name="size" value="medium" />
</label>

When the input is focused or its `checked` attribute is set, classes are added to their parent labels so their styling can show this.

To apply this behaviour to elements with the above HTML pattern, call the `GOVUK.selectionButtons` function with their inputs:
### Usage

#### GOVUK.SelectionButtons

To apply this behaviour to elements with the above HTML pattern, call the `GOVUK.SelectionButtons` constructor with their inputs:

```
var $buttons = $("label input[type='radio'], label input[type='checkbox']");
GOVUK.selectionButtons($buttons);
var selectionButtons = new GOVUK.SelectionButtons($buttons);
```

You can also call `GOVUK.SelectionButtons` with a selector:

```
var selectionButtons = new GOVUK.SelectionButtons("label input[type='radio'], label input[type='checkbox']");
```

This will bind all events to the document, meaning any changes to content (for example, by AJAX) will not effect the button's behaviour.

The classes that get added can be passed in as options:
The classes that get added to the `<label>` tags can be passed in as options:

```
var $buttons = $("label input[type='radio'], label input[type='checkbox']");
GOVUK.selectionButtons($buttons, { focusedClass : 'selectable-focused', selectedClass : 'selectable-selected' });
var selectionButtons = new GOVUK.SelectionButtons($buttons, { focusedClass : 'selectable-focused', selectedClass : 'selectable-selected' });
var selectionButtons = new GOVUK.SelectionButtons("label input[type='radio'], label input[type='checkbox']", { focusedClass : 'selectable-focused', selectedClass : 'selectable-selected' });
```

#### destroy method

The returned instance object includes a `destroy` method to remove all events bound to either the elements or the document.

Using any of the `selectionButtons` objects created above, it can be called like so:

```
selectionButtons.destroy();
```

### Deprecated functionality

The previous method of calling selection buttons is now deprecated. If you need to call them using this method, you will need to define this function:

```
GOVUK.selectionButtons = function (elms, opts) {
new GOVUK.SelectionButtons(elms, opts);
};
```

Note that `GOVUK.selectionButtons` and the constructors it wraps, `GOVUK.RadioButtons` and `GOVUK.CheckboxButtons` use the `bind.js` polyfill.
This method will mean the `destroy` method is not available to call.

## Licence

Expand Down
168 changes: 71 additions & 97 deletions javascripts/govuk/selection-buttons.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,133 +5,107 @@

if (typeof GOVUK === 'undefined') { root.GOVUK = {}; }

var BaseButtons = function ($elms, opts) {
this.$elms = $elms;
var SelectionButtons = function (elmsOrSelector, opts) {
var $elms;

this.selectedClass = 'selected';
this.focusedClass = 'focused';
if (opts !== undefined) {
$.each(opts, function (optionName, optionObj) {
this[optionName] = optionObj;
}.bind(this));
}
this.setEventNames();
this.getSelections();
this.bindEvents();
if (typeof elmsOrSelector === 'string') {
$elms = $(elmsOrSelector);
this.selector = elmsOrSelector;
this.setInitialState($(this.selector));
} else {
this.$elms = elmsOrSelector;
this.setInitialState(this.$elms);
}
this.addEvents();
};
BaseButtons.prototype.setEventNames = function () {
this.selectionEvents = 'click';
this.focusEvents = 'focus blur';
SelectionButtons.prototype.addEvents = function () {
if (typeof this.$elms !== 'undefined') {
this.addElementLevelEvents();
} else {
this.addDocumentLevelEvents();
}
};
BaseButtons.prototype.markFocused = function ($elm, state) {
var elmId = $elm.attr('id');
SelectionButtons.prototype.setInitialState = function ($elms) {
$elms.each(function (idx, elm) {
var $elm = $(elm);

if ($elm.is(':checked')) {
this.markSelected($elm);
}
}.bind(this));
};
SelectionButtons.prototype.markFocused = function ($elm, state) {
if (state === 'focused') {
$elm.parent('label').addClass(this.focusedClass);
} else {
$elm.parent('label').removeClass(this.focusedClass);
}
};
BaseButtons.prototype.bindEvents = function () {
var selectionEventHandler = this.markSelected.bind(this),
focusEventHandler = this.markFocused.bind(this);
SelectionButtons.prototype.markSelected = function ($elm) {
var radioName;

this.$elms
.on(this.selectionEvents, function (e) {
selectionEventHandler($(e.target));
})
.on(this.focusEvents, function (e) {
var state = (e.type === 'focus') ? 'focused' : 'blurred';

focusEventHandler($(e.target), state);
});
};

var RadioButtons = function ($elms, opts) {
BaseButtons.apply(this, arguments);
};
RadioButtons.prototype.setEventNames = function () {
// some browsers fire the 'click' when the selected radio changes by keyboard
this.selectionEvents = 'click change';
this.focusEvents = 'focus blur';
};
RadioButtons.prototype.getSelections = function () {
var selectionEventHandler = this.markSelected.bind(this);

this.selections = {};
$.each(this.$elms, function (index, elm) {
var $elm = $(elm),
radioName = $elm.attr('name');

if (typeof this.selections[radioName] === 'undefined') {
this.selections[radioName] = false;
}
if ($elm.attr('type') === 'radio') {
radioName = $elm.attr('name'),
$($elm[0].form).find('input[name="' + radioName + '"]')
.parent('label')
.removeClass(this.selectedClass);
$elm.parent('label').addClass(this.selectedClass);
} else { // checkbox
if ($elm.is(':checked')) {
selectionEventHandler($elm);
$elm.parent('label').addClass(this.selectedClass);
} else {
$elm.parent('label').removeClass(this.selectedClass);
}
}.bind(this));
};
RadioButtons.prototype.bindEvents = function () {
BaseButtons.prototype.bindEvents.call(this);
};
RadioButtons.prototype.markSelected = function ($elm) {
var radioName = $elm.attr('name'),
$previousSelection = this.selections[radioName];

if ($previousSelection) {
$previousSelection.parent('label').removeClass(this.selectedClass);
}
$elm.parent('label').addClass(this.selectedClass);
this.selections[radioName] = $elm;
};
RadioButtons.prototype.markFocused = function ($elm) {
BaseButtons.prototype.markFocused.apply(this, arguments);
SelectionButtons.prototype.addElementLevelEvents = function () {
this.clickHandler = this.getClickHandler();
this.focusHandler = this.getFocusHandler({ 'level' : 'element' });

this.$elms
.on('click', this.clickHandler)
.on('focus blur', this.focusHandler);
};
SelectionButtons.prototype.addDocumentLevelEvents = function () {
this.clickHandler = this.getClickHandler();
this.focusHandler = this.getFocusHandler({ 'level' : 'document' });

var CheckboxButtons = function ($elms, opts) {
BaseButtons.apply(this, arguments);
$(document)
.on('click', this.selector, this.clickHandler)
.on('focus blur', this.selector, this.focusHandler);
};
CheckboxButtons.prototype.setEventNames = function () {
BaseButtons.prototype.setEventNames.call(this);
SelectionButtons.prototype.getClickHandler = function () {
return function (e) {
this.markSelected($(e.target));
}.bind(this);
};
CheckboxButtons.prototype.getSelections = function () {
var selectionEventHandler = this.markSelected.bind(this);
SelectionButtons.prototype.getFocusHandler = function (opts) {
var focusEvent = (opts.level === 'document') ? 'focusin' : 'focus'

this.$elms.each(function (idx, elm) {
var $elm = $(elm);
return function (e) {
var state = (e.type === focusEvent) ? 'focused' : 'blurred';

if ($elm.is(':checked')) {
selectionEventHandler($elm);
}
});
this.markFocused($(e.target), state);
}.bind(this);
};
CheckboxButtons.prototype.bindEvents = function () {
BaseButtons.prototype.bindEvents.call(this);
};
CheckboxButtons.prototype.markSelected = function ($elm) {
if ($elm.is(':checked')) {
$elm.parent('label').addClass(this.selectedClass);
SelectionButtons.prototype.destroy = function () {
if (typeof this.selector !== 'undefined') {
$(document)
.off('click', this.selector, this.clickHandler)
.off('focus blur', this.selector, this.focusHandler);
} else {
$elm.parent('label').removeClass(this.selectedClass);
}
};
CheckboxButtons.prototype.markFocused = function ($elm) {
BaseButtons.prototype.markFocused.apply(this, arguments);
};

root.GOVUK.RadioButtons = RadioButtons;
root.GOVUK.CheckboxButtons = CheckboxButtons;

var selectionButtons = function ($elms, opts) {
var $radios = $elms.filter('[type=radio]'),
$checkboxes = $elms.filter('[type=checkbox]');

if ($radios) {
new GOVUK.RadioButtons($radios, opts);
}
if ($checkboxes) {
new GOVUK.CheckboxButtons($checkboxes, opts);
this.$elms
.off('click', this.clickHandler)
.off('focus blur', this.focusHandler);
}
};

root.GOVUK.selectionButtons = selectionButtons;
root.GOVUK.SelectionButtons = SelectionButtons;
}).call(this);
Loading

0 comments on commit bc2b6f1

Please sign in to comment.