/* --------------------------------------------------------------
checkbox.js 2016-06-01
Gambio GmbH
http://www.gambio.de
Copyright (c) 2016 Gambio GmbH
Released under the GNU General Public License (Version 2)
[http://www.gnu.org/licenses/gpl-2.0.html]
--------------------------------------------------------------
*/
/**
* ## Checkbox Widget
*
* This extension can serve multiple types of checkboxes (simple switchers, text switchers and gambio-styled
* checkboxes, radio-button switcher). Apply the widget in a parent container and it will search and convert
* all the instances into fine checkboxes
*
* ### Options
*
* **Filter | `data-checkbox-filter` | String | Optional**
*
* Provide a jQuery selector string for filtering the children elements of the parent container.
*
* **Checked State URL | `data-checkbox-on_url` | String | Optional**
*
* If provided the user will be navigated to the given URL once he clicks a checked instance of the widget.
*
* **Unchecked State URL | `dat-acheckbox-off_url` | String | Optional**
*
* If provided the user will be navigated ot the given URL once he clicks an unchecked instance of the widget.
*
* **Checked State Text | `data-checkbox-on_text` | String | Optional**
*
* If provided it will be displayed instead of the normal check icon.
*
* **Unchecked State Text | `data-checkbox-off_text` | String | Optional**
*
* If provided it will be displayed instead of the normal X icon.
*
* **Custom Checkbox Class | `data-checkbox-class` | String | Optional**
*
* Provide additional custom classes to the checkbox element.
*
* **Check Status | `data-checkbox-check` | Boolean | Optional**
*
* Defines whether the checkbox is checked or not. Use this option to override the original checkbox state.
*
* ### Examples
*
* **Single Checkbox Example**
*
* A single checkbox is just a better styled checkbox that can be used for seamless integration into the
* Gambio Admin pages.
*
* ```html
* <label for="my-checkbox">Single Checkbox (checked)</label>
* <input type="checkbox" id="my-checkbox" title="Single Checkbox" data-single_checkbox checked />
* ```
*
* **Switcher Checkbox**
*
* Displays a nice mobile-like switcher that is bound on the original checkbox. That means that any change done
* on the switcher will affect the original checkbox element.
*
* ```html
* <label for="my-checkbox">Receive Notifications</label>
* <input type="checkbox" id="my-checkbox" title="Receive Notifications" />
* ```
*
* **Radio Checkbox**
*
* The checkbox widget can also serve cases with two radio buttons that define a yes or no use case. Consider
* the following example where the first radio element contains the "activate" and the second "deactivate" status.
*
* ```html
* <input type="radio" name="status" value="1" title="Activated" checked />
* <input type="radio" name="status" value="0" title="Deactivated" />
* ```
*
* **URL Switcher**
*
* If you need to change the status of something by navigating the user to a specific url use the "on_url"
* and "off_url" options which will forward the user to the required URL.
*
* ```html
* <div data-gx-widget="checkbox"
* data-checkbox-checked="true"
* data-checkbox-on_url="#installed"
* data-checkbox-off_url="#uninstalled"
* data-checkbox-on_label="Installed"
* data-checkbox-off_label="Uninstalled"
* data-checkbox-class="labeled"></div>
* ```
*
* **Notice:** This widget was highly modified for use in compatibility pages. It's complexity and performance
* are not optimal anymore. Use the single_checkbox and switcher widgets instead.
*
* @module Admin/Widgets/checkbox
*/
gx.widgets.module(
'checkbox',
['fallback'],
function (data) {
'use strict';
// ------------------------------------------------------------------------
// VARIABLE DEFINITION
// ------------------------------------------------------------------------
var
/**
* Widget Reference
*
* @type {object}
*/
$this = $(this),
/**
* Default Options for Widget
*
* @type {object}
*/
defaults = {
'filter': '', // Only select checkboxes with the following selector
// Url Switcher Options:
'on_url': '', // Open url when switcher is turned on
'off_url': '', // Open url when switcher is turned off
'on_label': '', // Text shown on the switcher when turned on
'off_label': '', // Text shown on the switcher when turned off
'on_text': '', // Text shown next to the switcher when turned on
'off_text': '', // Text shown next to the switcher when turned off
'class': '', // Add class(es) to the on and off switcher
'checked': false // Initial status of the switcher: true = on, false = off
},
/**
* Status of mouse down event
*
* @type {boolean}
*/
mouseDown = false,
/**
* Final Widget Options
*
* @type {object}
*/
options = $.extend(true, {}, defaults, data),
/**
* Module Object
*
* @type {object}
*/
module = {};
// ------------------------------------------------------------------------
// EVENT HANDLERS
// ------------------------------------------------------------------------
/**
* Change the styling of the new switcher depending on the original checkbox/radio box setting
* Additionally set the new state of the original checkbox/radio box and trigger the change event on it.
*
* @private
*/
var _switcherChangeHandler = function (event) {
if ($(this).hasClass('disabled')) {
return false;
}
var $self = $(this),
$checkbox = $self.find('input:checkbox'),
$onElement = $self.find('input:radio').first(),
$offElement = $self.find('input:radio').last(),
$select = $self.find('select').first(),
dataset = $self.parent().data('checkbox');
$self.toggleClass('checked');
$self.find('.state-description').show().fadeOut('slow');
$checkbox
.prop('checked', $self.hasClass('checked')).trigger('checkbox:change');
$onElement
.prop('checked', $self.hasClass('checked'));
$offElement
.prop('checked', !$self.hasClass('checked'));
$select
.find('option')
.removeAttr('selected');
var selectOptionToSelect = $self.hasClass('checked') ? 1 : 0;
$select
.find('option[value="' + selectOptionToSelect + '"]')
.attr('selected', true);
if (options.on_url !== '' && options.off_url !== '') {
event.preventDefault();
event.stopPropagation();
if (options.checked) {
window.location.href = options.off_url;
options.checked = false;
return false;
}
window.location.href = options.on_url;
options.checked = true;
}
};
/**
* Change the styling of the new checkbox depending on the original checkbox setting
* Additionally set the new state of the original checkbox and trigger the change event on it.
*
* @private
*/
var _checkboxChangeHandler = function () {
if ($(this).hasClass('disabled')) {
return false;
}
mouseDown = true;
$(this).find('input:checkbox').focus();
};
/**
* Imitate mouse up behaviour of the checkbox
*
* @private
*/
var _checkboxMouseUpHandler = function () {
if ($(this).hasClass('disabled')) {
return false;
}
$(this).toggleClass('checked');
$(this).find('input:checkbox').focus();
$(this).find('input:checkbox').trigger('click');
mouseDown = false;
};
// ------------------------------------------------------------------------
// INITIALISATION FUNCTIONS
// ------------------------------------------------------------------------
/**
* Wrap the checkboxes and generate markup for the new checkbox style.
*
* @private
*/
var _initCheckboxes = function ($target) {
var $container = $target || $this;
$container
.find('input:checkbox')
.filter(options.filter || '*')
.each(function () {
var $self = $(this),
dataset = jse.libs.fallback._data($self, 'checkbox'),
className = dataset.className || '',
title = $self.prop('title'),
isChecked = ($self.prop('checked')) ? 'checked' : '',
isDisabled = ($self.prop('disabled')) ? 'disabled' : '';
if (typeof $self.data('single_checkbox') !== 'undefined') {
$self
.css({
'position': 'absolute',
'left': '-100000px'
})
.wrap('<span class="single-checkbox ' + isChecked + ' ' + isDisabled + '" title="' +
title + '"></span>')
.parent()
.append('<i class="fa fa-check"></i>');
$self.on('focus', function () {
$('.single_checkbox').removeClass('focused');
$(this).parent().addClass('focused');
});
$self.on('blur', function () {
$(this).parent().removeClass('focused');
});
$self.on('change', function () {
if (mouseDown === false) {
$(this).parent().toggleClass('checked');
}
});
} else {
var onText = ($self.attr('data-checkbox-on_text')) ? $self.attr('data-checkbox-on_text') :
'<span class="fa fa-check"></span>';
var offText = ($self.attr('data-checkbox-on_text')) ? $self.attr('data-checkbox-off_text') :
'<span class="fa fa-times"></span>';
$self
.wrap('<div class="switcher ' + isChecked + ' ' + isDisabled + '" title="' + title +
'"></div>')
.parent()
.data('checkbox', dataset)
.addClass(className)
.append('<div class="switcher-toggler"></div>' + '<div class="switcher-inner">' +
'<div class="switcher-state-on">' + onText + '</div>' +
'<div class="switcher-state-off">' + offText + '</div>' + '</div>' +
'<div class="switcher-text-on">' + options.on_text + '</div>' +
'<div class="switcher-text-off">' + options.off_text +
'</div>'
);
}
});
};
/**
* Wrap the radio boxes and generate markup for the new checkbox style.
*
* @private
*/
var _initRadioOptions = function () {
if ($this.find('input:radio').filter(options.filter || '*').length === 2) {
var $onElement = $this.find('input:radio').filter(options.filter || '*').first(),
onTitle = $onElement.prop('title'),
$offElement = $this.find('input:radio').filter(options.filter || '*').last(),
offTitle = $offElement.prop('title'),
onLabel = (options.on_label !== '') ? ' data-checkbox-label="' + options.on_label + '"' : '',
offLabel = (options.off_label !== '') ? ' data-checkbox-label="' + options.off_label + '"' :
'',
dataset = options,
isChecked = ($onElement.prop('checked')) ? 'checked' : '',
isDisabled = ($onElement.prop('disabled')) ? 'disabled' : '';
var $switcher = $('<div class="switcher ' + isChecked + ' ' + isDisabled + '"></div>');
$onElement.after($switcher);
$onElement.appendTo($switcher);
$offElement.appendTo($switcher);
$switcher
.data('checkbox', dataset)
.addClass(options.class)
.append('<div class="switcher-toggler"></div>' + '<div class="switcher-inner">' +
'<div class="switcher-state-on" title="' + onTitle + '"' + onLabel +
'><span class="fa fa-check"></span></div>' +
'<div class="switcher-state-off" title="' + offTitle + '"' + offLabel +
'><span class="fa fa-times"></span></div>' + '<div class="switcher-text-on">'
+ options.on_text +
'</div>' +
'<div class="switcher-text-off">' + options.off_text + '</div>' + '</div>'
);
// toggle switcher if hidden radio option status changes (there is no default case for that)
$onElement.on('change', function () {
$(this).parent().toggleClass('checked');
});
// toggle switcher if hidden radio option status changes (there is no default case for that)
$offElement.on('change', function () {
$(this).parent().toggleClass('checked');
});
}
};
/**
* build markup for the url switcher
*
* @private
*/
var _initUrlSwitcher = function () {
if (options.on_url !== '' && options.off_url !== '') {
var dataset = jse.libs.fallback._data($this, 'checkbox'),
onLabel = (options.on_label !== '') ? ' data-checkbox-label="' + options.on_label + '"' : '',
offLabel = (options.off_label !== '') ? ' data-checkbox-label="' + options.off_label + '"' :
'',
isChecked = (options.checked) ? 'checked' : '';
$this
.data('checkbox', dataset)
.addClass('switcher')
.addClass(isChecked)
.addClass(options.class)
.append('<div class="switcher-toggler"></div>' + '<div class="switcher-inner">' +
'<div class="switcher-state-on" title="' + options.off_url + '"' + onLabel +
'><span class="fa fa-check"></span></div>' + '<div class="switcher-state-off" title="' +
options.on_url + '"' +
offLabel + '><span class="fa fa-times"></span></div>' + '</div>'
)
.on('click', _switcherChangeHandler);
}
};
/**
* Bind events that change the checkbox or switcher
*
* @private
*/
var _initEventHandlers = function () {
$this.on('click', '.switcher', _switcherChangeHandler);
$this.off('mousedown', '.single-checkbox');
$this.on('mousedown', '.single-checkbox', _checkboxChangeHandler);
$this.off('mouseup', '.single-checkbox');
$this.on('mouseup', '.single-checkbox', _checkboxMouseUpHandler);
$this.on('mousedown', 'label', function () {
mouseDown = true;
});
$this.on('mouseup', 'label', function () {
mouseDown = false;
});
$this.on('FORM_UPDATE', function (e) {
var $target = $(e.target);
$target
.find('input:checkbox')
.each(function () {
var $self = $(this),
$wrapper = $self.closest('.switcher');
if ($wrapper.length) {
$wrapper
.find('div')
.remove();
$self.unwrap();
}
});
_initCheckboxes($target);
});
};
var _initSelects = function () {
// Iterate over select fields
$this.find('[data-convert-checkbox]').each(function (index, element) {
// Shortcuts
var $optionTrue = $(element).find('option[value="1"]'),
$optionFalse = $(element).find('option[value="0"]');
// States
var isChecked = $optionTrue.is(':selected') ? 'checked' : '',
isDisabled = $(element).is(':disabled') ? 'disabled' : '';
// Switcher Template
var $switcher = $('<div class="switcher ' + isChecked + ' ' + isDisabled + '"></div>');
$switcher
.addClass($(element).data('newClass'))
.data('checkbox', options)
.append('<div class="switcher-toggler"></div>' + '<div class="switcher-inner">' +
'<div class="switcher-state-on"><span class="fa fa-check"></span></div>' +
'<div class="switcher-state-off"><span class="fa fa-times"></span></div>' + '</div>'
);
$(element)
.after($switcher)
.appendTo($switcher)
.hide();
});
};
// ------------------------------------------------------------------------
// INITIALIZATION
// ------------------------------------------------------------------------
/**
* Initialize method of the widget, called by the engine.
*/
module.init = function (done) {
// sanitize url preventing cross site scripting
options.on_url = options.on_url.replace('"', '');
options.off_url = options.off_url.replace('"', '');
_initCheckboxes();
_initRadioOptions();
_initSelects();
_initUrlSwitcher();
_initEventHandlers();
done();
};
// Return data to module engine.
return module;
});