/* global define, console, $ */
/**
* @module Utils
*/
/**
* Creates a Checkbox group bound from the supplied JArray of input nodes. Optionally, the group is bound to the supplied master input node
* which acts as a global toggle for the group.
*
*
* @class CheckboxGroup
* @constructor
* @uses dojo/Evented
* @uses dojo/_base/declare
* @uses dojo/_base/lang
* @uses dojo/_base/array
* @uses Checkbox
*
* @param {JArray} nodes a jQuery object representing the checkboxes to be grouped
* @param {Object} [options] Additional options
* @param {String} [options.nodeIdAttr] Name of the "data-*" attribute set on the checkbox node to be treated as the checkbox id. If no appropriate "data-*" attribute found,
* `nodeIdAttr` is used directly, failing that, regular `id` is used.
* @param {Object} [options.cssClass] `active`, `focus`, and `check` CSS class to be applied to the Checkbox correspondingly.
* @param {Object} [options.cssClass.active] CSS class to be set when the Checkbox is `active`.
* @param {Object} [options.cssClass.focus] CSS class to be set when the Checkbox is `focused`.
* @param {Object} [options.cssClass.check] CSS class to be set when the Checkbox is `checked`.
* @param {Object} [options.label] `check` and `uncheck` label texts to be applied to the Checkbox labels.
* @param {Object} [options.label.check] A text to be set as a label when the Checkbox is `checked`
* @param {Object} [options.label.uncheck] A text to be set as a label when the Checkbox is `unchecked`
* @param {Function} [options.onChnage] A function to be called when the state of the Checkbox changes.
* @param {Object} [options.master] Additional options applied to the master Checkbox.
* @param {Object} [options.master.node] An `input` node to serve as the master Checkbox for the group.
* @param {Object} [options.master.cssClass] `active`, `focus`, and `check` CSS class to be applied to the master Checkbox correspondingly.
* @param {Object} [options.master.cssClass.active] CSS class to be set when the Checkbox is `active`.
* @param {Object} [options.master.cssClass.focus] CSS class to be set when the master Checkbox is `focused`.
* @param {Object} [options.master.cssClass.check] CSS class to be set when the master Checkbox is `checked`.
* @param {Object} [options.master.label] `check` and `uncheck` label texts to be applied to the master Checkbox labels.
* @param {Object} [options.master.label.check] A text to be set as a label when the master Checkbox is `checked`
* @param {Object} [options.master.label.uncheck] A text to be set as a label when the master Checkbox is `unchecked`
* @param {Function} [options.master.onChnage] A function to be called when the state of the master Checkbox changes.
*
* @return {CheckboxGroup} A control objects allowing to toggle individual checkboxes in a group as well as the group as a whole.
*/
define(["dojo/Evented", "dojo/_base/declare", "dojo/_base/lang", "dojo/_base/array",
"utils/checkbox", "utils/array"],
function (Evented, declare, lang, dojoArray,
Checkbox, Array) {
"use strict";
var CheckboxGroup;
CheckboxGroup = declare([Evented], {
constructor: function (nodes, options) {
var that = this,
masterCheckboxOptions;
// declare individual properties inside the constructor: http://dojotoolkit.org/reference-guide/1.9/dojo/_base/declare.html#id6
lang.mixin(this,
{
/**
* Nodes of the checkbox nodes originally supplied to the CheckboxGroup.
*
* @property nodes
* @type JArray
* @default []
*/
nodes: [],
/**
* Name of the "data-*" attribute set on the checkbox node to be treated as the checkbox id.
*
* @property nodeIdAttr
* @type String
* @default "id"
*/
nodeIdAttr: "id",
/**
* An array of the Checkbox object belonging to the body of the group.
*
* @property nodes
* @type Array
* @default []
*/
checkboxes: [],
/**
* `active`, `focus`, and `check` CSS class to be applied to the Checkbox correspondingly.
*
* @property cssClass
* @type {Object}
* @default
* @example
* cssClass: {
* active: "active",
* focus: "focused",
* check: "checked"
* }
*/
cssClass: {
active: "active",
focus: "focused",
check: "checked"
},
/**
* `check` and `uncheck` label texts to be applied to the Checkbox labels.
*
* @property label
* @type {Object}
* @default
* @example
* label: {
* check: "check",
* uncheck: "unchecked"
* }
*/
label: {
check: "checked",
uncheck: "unchecked"
},
/**
* A function to be called when the state of the Checkbox changes.
*
* @property onChnage
* @type Function
* @default
* @example function () { }
*/
onChange: function () { },
/**
* Options for the master Checkbox.
*
* @property master
* @type Object
* @default
* @example
* master: {
* node: null,
* checkbox: null,
* nodeIdAttr: null,
*
* cssClass: {
* active: "active",
* focus: "focused",
* check: "checked"
* },
*
* label: {
* check: "checked",
* uncheck: "unchecked"
* },
*
* onChange: function () { }
* }
*
*/
master: {
node: null,
checkbox: null,
nodeIdAttr: null,
cssClass: {
active: "active",
focus: "focused",
check: "checked"
},
label: {
check: "checked",
uncheck: "unchecked"
},
onChange: function () { }
}
},
options,
{
nodes: nodes
}
);
masterCheckboxOptions = {
nodeIdAttr: this.nodeIdAttr,
cssClass: this.cssClass,
label: this.label,
onChange: this.onChange
};
if (this.master.node) {
this.master.checkbox = new Checkbox(
this.master.node,
lang.mixin(masterCheckboxOptions, this.master)
);
this.master.checkbox.on(Checkbox.event.TOGGLE, function (evt) {
// re-emit individual checkbox's toggle event as groups;
console.log("CheckboxGroup Master ->", evt.checkbox.id, "set by", evt.agency, "to", evt.checkbox.state);
that.emit(CheckboxGroup.event.MASTER_TOGGLE, evt);
if (evt.agency === Checkbox.agency.USER) {
that.setState(evt.checkbox.state);
}
});
} else {
this.master = null;
}
this.addCheckbox(this.nodes);
},
addCheckbox: function (nodes) {
var that = this,
checkbox,
checkboxOptions = {
nodeIdAttr: this.nodeIdAttr,
cssClass: this.cssClass,
label: this.label,
onChange: this.onChange
},
cbIndex;
// Create individual Checkboxes
nodes.each(function (index, node) {
node = $(node);
cbIndex = Array.indexOf(that.checkboxes, function (cb) {
return cb.node.is(node);
});
if (cbIndex === -1) {
checkbox = new Checkbox(node, checkboxOptions);
that.checkboxes.push(checkbox);
checkbox.on(Checkbox.event.TOGGLE, function (evt) {
// re-emit individual checkbox's toggle event as groups;
//console.log("CheckboxGroup ->", evt.checkbox.id, "set by", evt.agency, "to", evt.checkbox.state);
that.emit(CheckboxGroup.event.MEMBER_TOGGLE, evt);
if (evt.agency === Checkbox.agency.USER) {
that._checkMaster();
}
});
} else {
// reset the checkbox, just in case
that.checkboxes[cbIndex].reset();
}
});
this._checkMaster();
},
/**
* Synchronizes the state of the master Checkbox with the state of the group.
* All group members checked -> master checked
* Any of the group members unchecked -> master unchecked
*
* @method _checkMaster
* @private
*/
_checkMaster: function () {
var allChecked;
allChecked = dojoArray.every(this.checkboxes, function (checkbox) {
// "INVALID" state is parsed as "true" so it's counted in, but is ignored otherwise
return checkbox
.validate()
.state;
});
// set state to the master checkbox only if you have one
if (this.master) {
this.master.checkbox.setState(allChecked);
}
},
/**
* Toggles the state of the specified Checkbox. If checkboxId is not supplied, toggles the whole group.
*
* @method setState
* @param {Boolean} state Specifies the state of the checkbox: true, false
* @param {String} [checkboxId] Specifies the checkbox to toggle.
* @return CheckboxGroup
* @chainable
*/
setState: function (state, checkboxId) {
var checkbox,
masterCheckboxId = this.master.checkbox ? this.master.checkbox.id : undefined;
if (!checkboxId || masterCheckboxId === checkboxId) {
this.master.checkbox.setState(state);
this.checkboxes.forEach(function (checkbox) {
checkbox.setState(state);
});
} else {
// use for loop because you can't break out of forEach loop
for (var i = 0; i < this.checkboxes.length; i++) {
checkbox = this.checkboxes[i];
if (checkbox.id === checkboxId) {
break;
}
}
checkbox.setState(state);
this._checkMaster();
}
return this;
},
/**
* Toggle all the checkboxes based on the return value of the given function.
*
* @param {Function} fcn a function that takes a checkbox as an argument and returns
* true if the given checkbox should be toggled on, false if it should be toggled off
* @method setEachState
* @chainable
*/
setEachState: function (fcn) {
this.checkboxes.forEach(function (checkbox) {
checkbox.setState(fcn(checkbox));
});
this._checkMaster();
return this;
},
_purgeInvalid: function () {
var i,
checkbox;
for (i = this.checkboxes.length - 1; i >= 0; i--) {
checkbox = this.checkboxes[i];
if (checkbox.state === Checkbox.state.INVALID) {
Array.remove(this.checkboxes, i);
}
}
}
});
lang.mixin(CheckboxGroup,
{
/**
* Event names published by the Checkbox
*
* @private
* @property event
* @type Object
* @default null
* @example
* {
* MEMBER_TOGGLE: "checkbox/toggled",
* MASTER_TOGGLE: "checkbox/toggled"
* }
*/
event: {
/**
* This event is not published by CheckboxGroup. __Ignore this.__
*
* @event TOGGLE
* @private
*/
/**
* Published whenever a Checkbox get toggled.
*
* @event MEMBER_TOGGLE
* @param event {Object}
* @param event.checkbox {Checkbox} Checkbox object that has been toggled
* @param event.agency {String} Agency that toggled the Checkbox
*/
MEMBER_TOGGLE: "checkbox/member-toggle",
/**
* Published whenever the master Checkbox get toggled.
*
* @event MASTER_TOGGLE
* @param event {Object}
* @param event.checkbox {Checkbox} master Checkbox object that has been toggled
* @param event.agency {String} Agency that toggled the Checkbox
*/
MASTER_TOGGLE: "checkbox/master-toggle"
}
}
);
return CheckboxGroup;
});