jquery-qubit
Version:
Provides the semantics for a nested list of tri-state checkboxes, using the HTML5 "indeterminate" property
101 lines (99 loc) • 3.17 kB
JavaScript
(function(factory){
if (typeof define === 'function' && define.amd) {
define(['jquery'], factory);
} else if (typeof exports === 'object') {
factory(require('jquery'));
} else {
factory(jQuery);
}
}(function($) {
$.fn.qubit = function(options) {
return this.each(function() {
new Qubit(this, options);
});
};
var Qubit = function(el) {
var self = this;
this.scope = $(el);
var handler = function(e) {
if (!self.suspendListeners) {
self.process(e.target);
}
};
this.scope.on('change', 'input[type=checkbox]', handler);
// workaround for IE<10
if (document.documentMode && document.documentMode <= 9) {
this.scope.on('click', 'input[type=checkbox]:indeterminate', handler);
}
this.processParents();
};
Qubit.prototype = {
itemSelector: 'li',
process: function(checkbox) {
checkbox = $(checkbox);
var parentItems = checkbox.parentsUntil(this.scope, this.itemSelector);
var self = this;
try {
this.suspendListeners = true;
// all children inherit my state
parentItems.eq(0).find('input[type=checkbox]').not(':disabled')
.filter(checkbox.prop('checked') ? ':not(:checked)' : ':checked')
.each(function() {
if (!$(this).parent().hasClass('hidden')) {
self.setChecked($(this), checkbox.prop('checked'));
}
});
this.processParents();
} finally {
this.suspendListeners = false;
}
},
processParents: function() {
var self = this, changed = false;
this.scope.find('input[type=checkbox]').not(':disabled').each(function() {
var $this = $(this);
var parent = $this.closest(self.itemSelector);
var children = parent.find('input[type=checkbox]').not(':disabled').not($this);
var numChecked = children.filter(function() {
return $(this).prop('checked') || $(this).prop('indeterminate');
}).length;
if (children.length) {
if (numChecked === 0) {
self.setChecked($this, false) && (changed = true);
}
else if (numChecked == children.length) {
self.setChecked($this, true) && (changed = true);
}
else {
self.setIndeterminate($this, true) && (changed = true);
}
}
else {
self.setIndeterminate($this, false) && (changed = true);
}
});
if (changed) this.processParents();
},
setChecked: function(checkbox, value, event) {
var changed = false;
if (checkbox.prop('indeterminate')) {
checkbox.prop('indeterminate', false);
changed = true;
}
if (checkbox.prop('checked') != value) {
checkbox.prop('checked', value).trigger('change');
changed = true;
}
return changed;
},
setIndeterminate: function(checkbox, value) {
if (value) {
checkbox.prop('checked', false);
}
if (checkbox.prop('indeterminate') != value) {
checkbox.prop('indeterminate', value);
return true;
}
}
};
}));