Files
MaxScale/Documentation/Design-Documents/assets/js/jquery.bonsai.js
2015-02-02 11:47:06 +00:00

248 lines
8.1 KiB
JavaScript

(function($){
$.fn.bonsai = function(options) {
var args = arguments;
return this.each(function() {
var bonsai = $(this).data('bonsai');
if (!bonsai) {
bonsai = new Bonsai(this, options);
$(this).data('bonsai', bonsai);
}
if (typeof options == 'string') {
var method = options;
bonsai[method].apply(bonsai, [].slice.call(args, 1));
}
});
};
$.bonsai = {};
$.bonsai.defaults = {
expandAll: false, // boolean expands all items
expand: null, // function to expand an item
collapse: null, // function to collapse an item
checkboxes: false, // requires jquery.qubit
// createCheckboxes: creates checkboxes for each list item.
//
// The name and value for the checkboxes can be declared in the
// markup using `data-name` and `data-value`.
//
// The name is inherited from parent items if not specified.
//
// Checked state can be indicated using `data-checked`.
createCheckboxes: false,
// handleDuplicateCheckboxes: adds onChange bindings to update
// any other checkboxes that have the same value.
handleDuplicateCheckboxes: false,
selectAllExclude: null
};
var Bonsai = function(el, options) {
var self = this;
options = options || {};
this.options = $.extend({}, $.bonsai.defaults, options);
this.el = $(el).addClass('bonsai').data('bonsai', this);
this.update();
if (this.isRootNode()) {
if (this.options.handleDuplicateCheckboxes) this.handleDuplicates();
if (this.options.checkboxes) this.el.qubit(this.options);
if (this.options.addExpandAll) this.addExpandAllLink();
if (this.options.addSelectAll) this.addSelectAllLink();
this.el.on('click', '.thumb', function(ev) {
self.toggle($(ev.currentTarget).closest('li'));
});
}
if (this.options.expandAll) this.expandAll();
};
Bonsai.prototype = {
isRootNode: function() {
return this.options.scope == this.el;
},
toggle: function(listItem) {
if (!$(listItem).hasClass('expanded')) {
this.expand(listItem);
}
else {
this.collapse(listItem);
}
},
expand: function(listItem) {
this.setExpanded(listItem, true);
},
collapse: function(listItem) {
this.setExpanded(listItem, false);
},
setExpanded: function(listItem, expanded) {
listItem = $(listItem);
if (listItem.length > 1) {
var self = this;
listItem.each(function() {
self.setExpanded(this, expanded);
});
return;
}
if (expanded) {
if (!listItem.data('subList')) return;
listItem = $(listItem).addClass('expanded')
.removeClass('collapsed');
$(listItem.data('subList')).css('height', 'auto');
}
else {
listItem = $(listItem).addClass('collapsed')
.removeClass('expanded');
$(listItem.data('subList')).height(0);
}
},
expandAll: function() {
this.expand(this.el.find('li'));
},
collapseAll: function() {
this.collapse(this.el.find('li'));
},
update: function() {
var self = this;
// store the scope in the options for child nodes
if (!this.options.scope) {
this.options.scope = this.el;
}
// look for a nested list (if any)
this.el.children().each(function() {
var item = $(this);
if (self.options.createCheckboxes) self.insertCheckbox(item);
// insert a thumb if it doesn't already exist
if (item.children().filter('.thumb').length == 0) {
var thumb = $('<div class="thumb"></div>');
item.prepend(thumb);
}
var subLists = item.children().filter('ol, ul');
item.toggleClass('has-children', subLists.find('li').length > 0);
// if there is a child list
subLists.each(function() {
// that's not empty
if ($('li', this).length == 0) {
return;
}
// then this el has children
item.data('subList', this);
// collapse the nested list
if (item.hasClass('expanded')) {
self.expand(item);
}
else {
self.collapse(item);
}
// handle any deeper nested lists
var exists = !!$(this).data('bonsai');
$(this).bonsai(exists ? 'update' : self.options);
});
});
this.expand = this.options.expand || this.expand;
this.collapse = this.options.collapse || this.collapse;
},
insertCheckbox: function(listItem) {
if (listItem.find('> input[type=checkbox]').length) return;
var id = this.generateId(listItem),
checkbox = $('<input type="checkbox" name="'
+ this.getCheckboxName(listItem) + '" id="' + id + '" /> '
),
children = listItem.children(),
// get the first text node for the label
text = listItem.contents().filter(function() {
return this.nodeType == 3;
}).first();
checkbox.val(listItem.data('value'));
checkbox.prop('checked', listItem.data('checked'))
children.remove();
listItem.append(checkbox)
.append(
$('<label for="' + id + '">').append(text ? text : children.first())
)
.append(text ? children : children.slice(1));
},
handleDuplicates: function() {
var self = this;
self.el.on('change', 'input[type=checkbox]', function(ev) {
var checkbox = $(ev.target);
if (!checkbox.val()) return;
// select all duplicate checkboxes that need to be updated
var selector = 'input[type=checkbox]'
+ '[value="' + checkbox.val() + '"]'
+ '[name="' + checkbox.attr('name') + '"]'
+ (checkbox.prop('checked') ? ':not(:checked)' : ':checked');
self.el.find(selector).prop({
checked: checkbox.prop('checked'),
indeterminate: checkbox.prop('indeterminate')
}).trigger('change');
});
},
idPrefix: 'checkbox-',
generateId: function(listItem) {
do {
var id = this.idPrefix + Bonsai.uniqueId++;
}
while($('#' + id).length > 0);
return id;
},
getCheckboxName: function(listItem) {
return listItem.data('name')
|| listItem.parents().filter('[data-name]').data('name');
},
addExpandAllLink: function() {
var self = this;
$('<div class="expand-all">')
.append($('<a class="all">Expand all</a>')
.on('click', function() {
self.expandAll();
})
)
.append('<i class="separator"></i>')
.append($('<a class="none">Collapse all</a>')
.on('click', function() {
self.collapseAll();
})
)
.insertBefore(this.el);
},
addSelectAllLink: function() {
var scope = this.options.scope,
self = this;
function getCheckboxes() {
// return all checkboxes that are not in hidden list items
return scope.find('li')
.filter(self.options.selectAllExclude || function() {
return $(this).css('display') != 'none';
})
.find('> input[type=checkbox]');
}
$('<div class="check-all">')
.append($('<a class="all">Select all</a>')
.css('cursor', 'pointer')
.on('click', function() {
getCheckboxes().prop({
checked: true,
indeterminate: false
});
})
)
.append('<i class="separator"></i>')
.append($('<a class="none">Select none</a>')
.css('cursor', 'pointer')
.on('click', function() {
getCheckboxes().prop({
checked: false,
indeterminate: false
});
})
)
.insertAfter(this.el);
},
setCheckedValues: function(values) {
var all = this.options.scope.find('input[type=checkbox]');
$.each(values, function(key, value) {
all.filter('[value="' + value + '"]')
.prop('checked', true)
.trigger('change');
});
}
};
$.extend(Bonsai, {
uniqueId: 0
});
}(jQuery));