Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add document level events to selection buttons #139

Merged
merged 9 commits into from
Oct 17, 2014
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));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.setInitialState($(this.selector)); can be this.setInitialState($elms); as you have already created the jQuery object.

} 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