Commit 0a857a08 authored by David Stutz's avatar David Stutz

Fixed #109, #113.

Moved CSS declarations into separate LESS file. Filter will now be
rebuild, too.
parent 29f1ab30
This source diff could not be displayed because it is too large. You can view the blob instead.
.multiselect-container{position:absolute;list-style-type:none;margin:0;padding:0}.multiselect-container input[type="text"]{width:70%}.multiselect-container .input-prepend{padding:3px}.multiselect-container>li{padding:0}.multiselect-container>li>label{margin:0;padding:3px 20px 3px 20px;height:100%;cursor:pointer}.multiselect-container>li>label.multiselect-header{margin:0;padding:3px 20px 3px 20px;height:100%}.multiselect-container>li>label>input[type="checkbox"]{margin-bottom:5px}
\ No newline at end of file
......@@ -7,6 +7,7 @@
<meta name="copyright" content="David Stutz" />
<link rel="stylesheet" href="css/bootstrap-2.3.2.min.css" type="text/css">
<link rel="stylesheet" href="css/bootstrap-multiselect.css" type="text/css">
<link rel="stylesheet" href="css/prettify.css" type="text/css">
<script type="text/javascript" src="js/jquery-2.0.1.min.js"></script>
......@@ -587,7 +588,8 @@
});
$('#example12').multiselect({
buttonContainer: '<span />'
buttonContainer: '<span />',
enableFiltering: true
});
$('#example12-rebuild').on('click', function() {
$('#example12').multiselect('rebuild');
......@@ -1297,7 +1299,7 @@
<div class="add-styling">
<table class="table table-striped">
<tr>
<td>
<td width="30%">
<select id="example15" multiple="multiple">
<optgroup label="Mathematics">
<option value="analysis">Analysis</option>
......@@ -1314,10 +1316,10 @@
</optgroup>
</select>
</td>
<td>
<td width="30%">
Text alignment combined with fixed width and bold, underlined text for option group headers.
</td>
<td>
<td width="40%">
<pre class="prettyprint linenums">
.multiselect {
text-align: left;
......
......@@ -16,19 +16,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
!function ($) {
!function($) {"use strict";// jshint ;_;
"use strict"; // jshint ;_;
if(typeof ko != 'undefined' && ko.bindingHandlers && !ko.bindingHandlers.multiselect){
if ( typeof ko != 'undefined' && ko.bindingHandlers && !ko.bindingHandlers.multiselect) {
ko.bindingHandlers.multiselect = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {},
update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
init : function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
},
update : function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var ms = $(element).data('multiselect');
if (!ms) {
$(element).multiselect(ko.utils.unwrapObservable(valueAccessor()));
}
else if (allBindingsAccessor().options && allBindingsAccessor().options().length !== ms.originalOptions.length) {
else
if (allBindingsAccessor().options && allBindingsAccessor().options().length !== ms.originalOptions.length) {
ms.updateOriginalOptions();
$(element).multiselect('rebuild');
}
......@@ -40,101 +40,55 @@
this.options = this.getOptions(options);
this.$select = $(select);
this.originalOptions = this.$select.clone()[0].options; //we have to clone to create a new reference
this.originalOptions = this.$select.clone()[0].options;
//we have to clone to create a new reference
this.query = '';
this.searchTimeout = null;
this.options.multiple = this.$select.attr('multiple') == "multiple";
this.$container = $(this.options.buttonContainer)
.append('<button type="button" class="multiselect dropdown-toggle ' + this.options.buttonClass + '" data-toggle="dropdown">' + this.options.buttonText(this.getSelected(), this.$select) + '</button>')
.append('<ul class="multiselect-container dropdown-menu' + (this.options.dropRight ? ' pull-right' : '') + '" style="position:absolute; list-style-type: none;margin:0;padding:0;"></ul>');
this.$container = $(this.options.buttonContainer).append('<button type="button" class="multiselect dropdown-toggle ' + this.options.buttonClass + '" data-toggle="dropdown">' + this.options.buttonText(this.getSelected(), this.$select) + '</button>')
.append('<ul class="multiselect-container dropdown-menu' + (this.options.dropRight ? ' pull-right' : '') + '"></ul>');
if (this.options.buttonWidth) {
$('button', this.$container).css({
'width': this.options.buttonWidth
'width' : this.options.buttonWidth
});
}
// Set max height of dropdown menu to activate auto scrollbar.
if (this.options.maxHeight) {
// TODO: Add a class for this option to move the css declarations.
$('.multiselect-container', this.$container).css({
'max-height': this.options.maxHeight + 'px',
'overflow-y': 'auto',
'overflow-x': 'hidden'
'max-height' : this.options.maxHeight + 'px',
'overflow-y' : 'auto',
'overflow-x' : 'hidden'
});
$('input[type="text"]', this.$container).width('75%');
}
// Enable filtering.
if (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering) {
$('.multiselect-container', this.$container).prepend('<div class="input-prepend" style="padding:3px;"><span class="add-on"><i class="icon-search"></i></span><input class="multiselect-search" type="text" placeholder="' + this.options.filterPlaceholder + '"></div>');
$('.multiselect-search', this.$container).val(this.query).on('click', function (event) {
event.stopPropagation();
}).on('keydown', $.proxy(function (event) {
// This is useful to catch "keydown" events after the browser has updated the control.
clearTimeout(this.searchTimeout);
this.searchTimeout = this.asyncFunction($.proxy(function () {
if (this.query != event.target.value) {
this.query = event.target.value;
$.each($('.multiselect-container li', this.$container), $.proxy(function(index, element) {
var value = $('input', element).val();
if (value != this.options.selectAllValue) {
var text = $('label', element).text();
var value = $('input', element).val();
if (value && text && value != this.options.selectAllValue ) {
// by default lets assume that element is not interesting for this search
var showElement = false;
var filterCandidate = '';
if ( (this.options.filterBehavior == 'text' || this.options.filterBehavior == 'both')) {
filterCandidate = text;
}
if ( (this.options.filterBehavior == 'value' || this.options.filterBehavior == 'both')) {
filterCandidate = value;
}
if(this.options.enableCaseInsensitiveFiltering && filterCandidate.toLowerCase().indexOf(this.query.toLowerCase()) > -1) {
showElement = true;
} else if(filterCandidate.indexOf(this.query) > -1) {
showElement = true;
}
if (showElement) {
$(element).show();
} else {
$(element).hide();
}
}
}
}, this));
}
}, this), 300, this);
}, this));
this.buildFilter();
}
this.buildDropdown();
this.updateButtonText();
this.$select
.hide()
.after(this.$container);
this.$select.hide().after(this.$container);
};
Multiselect.prototype = {
defaults: {
// Default text function will either print 'None selected' in case no option is selected,
// or a list of the selected options up to a length of 3 selected options.
defaults : {
// Default text function will either print 'None selected' in case no
// option is selected, or a list of the selected options up to a length of 3 selected options.
// If more than 3 options are selected, the number of selected options is printed.
buttonText: function(options, select) {
buttonText : function(options, select) {
if (options.length == 0) {
return 'None selected <b class="caret"></b>';
}
else if (options.length > 3) {
else
if (options.length > 3) {
return options.length + ' selected <b class="caret"></b>';
}
else {
......@@ -144,34 +98,35 @@
selected += label + ', ';
});
return selected.substr(0, selected.length -2) + ' <b class="caret"></b>';
return selected.substr(0, selected.length - 2) + ' <b class="caret"></b>';
}
},
// Is triggered on change of the selected options.
onChange: function(option, checked) {
onChange : function(option, checked) {
},
buttonClass: 'btn',
dropRight: false,
selectedClass: 'active',
buttonWidth: 'auto',
buttonContainer: '<div class="btn-group" />',
buttonClass : 'btn',
dropRight : false,
selectedClass : 'active',
buttonWidth : 'auto',
buttonContainer : '<div class="btn-group" />',
// Maximum height of the dropdown menu.
// If maximum height is exceeded a scrollbar will be displayed.
maxHeight: false,
includeSelectAllOption: false,
selectAllText: ' Select all',
selectAllValue: 'multiselect-all',
enableFiltering: false,
enableCaseInsensitiveFiltering: false,
filterPlaceholder: 'Search',
filterBehavior: 'text' // possible options: 'text', 'value', 'both'
maxHeight : false,
includeSelectAllOption : false,
selectAllText : ' Select all',
selectAllValue : 'multiselect-all',
enableFiltering : false,
enableCaseInsensitiveFiltering : false,
filterPlaceholder : 'Search',
// possible options: 'text', 'value', 'both'
filterBehavior : 'text'
},
constructor: Multiselect,
constructor : Multiselect,
// Will build an dropdown element for the given option.
createOptionValue: function(element) {
createOptionValue : function(element) {
if ($(element).is(':selected')) {
$(element).attr('selected', 'selected').prop('selected', true);
}
......@@ -181,7 +136,7 @@
var value = $(element).val();
var inputType = this.options.multiple ? "checkbox" : "radio";
var $li = $('<li><a href="javascript:void(0);" style="padding:0;"><label style="margin:0;padding:3px 20px 3px 20px;height:100%;cursor:pointer;"><input style="margin-bottom:5px;" type="' + inputType + '" /></label></a></li>');
var $li = $('<li><a href="javascript:void(0);"><label><input type="' + inputType + '" /></label></a></li>');
var selected = $(element).prop('selected') || false;
var $checkbox = $('input', $li);
......@@ -206,7 +161,7 @@
}
},
toggleActiveState: function (shouldBeActive) {
toggleActiveState : function(shouldBeActive) {
if (this.$select.attr('disabled') == undefined) {
$('button.multiselect.dropdown-toggle', this.$container).removeClass('disabled');
}
......@@ -216,17 +171,18 @@
},
// Build the dropdown and bind event handling.
buildDropdown: function () {
buildDropdown : function() {
var alreadyHasSelectAll = this.$select[0][0] ? this.$select[0][0].value == this.options.selectAllValue : false;
// If options.includeSelectAllOption === true, add the include all checkbox.
// If options.includeSelectAllOption === true, add the include all
// checkbox.
if (this.options.includeSelectAllOption && this.options.multiple && !alreadyHasSelectAll) {
this.$select.prepend('<option value="' + this.options.selectAllValue + '">' + this.options.selectAllText + '</option>');
}
this.toggleActiveState();
this.$select.children().each($.proxy(function (index, element) {
this.$select.children().each($.proxy(function(index, element) {
// Support optgroups and options without a group simultaneously.
var tag = $(element).prop('tagName').toLowerCase();
if (tag == 'optgroup') {
......@@ -234,16 +190,17 @@
var groupName = $(group).prop('label');
// Add a header for the group.
var $li = $('<li><label style="margin:0;padding:3px 20px 3px 20px;height:100%;" class="multiselect-group"></label></li>');
var $li = $('<li><label class="multiselect-group"></label></li>');
$('label', $li).text(groupName);
$('.multiselect-container', this.$container).append($li);
// Add the options of the group.
$('option', group).each($.proxy(function (index, element) {
$('option', group).each($.proxy(function(index, element) {
this.createOptionValue(element);
}, this));
}
else if (tag == 'option') {
else
if (tag == 'option') {
this.createOptionValue(element);
}
else {
......@@ -252,7 +209,7 @@
}, this));
// Bind the change event on the dropdown elements.
$('.multiselect-container li input', this.$container).on('change', $.proxy(function (event) {
$('.multiselect-container li input', this.$container).on('change', $.proxy(function(event) {
var checked = $(event.target).prop('checked') || false;
var isSelectAllOption = $(event.target).val() == this.options.selectAllValue;
......@@ -275,7 +232,9 @@
// Toggle all options if the select all option was changed.
if (isSelectAllOption) {
$checkboxesNotThis.filter(function () { return $(this).is(':checked') != checked; }).trigger('click');
$checkboxesNotThis.filter(function() {
return $(this).is(':checked') != checked;
}).trigger('click');
}
if (checked) {
......@@ -313,14 +272,15 @@
this.$select.change();
}, this));
$('.multiselect-container li a', this.$container).on('touchstart click', function (event) {
$('.multiselect-container li a', this.$container).on('touchstart click', function(event) {
event.stopPropagation();
$(event.target).blur();
});
// Keyboard support.
this.$container.on('keydown', $.proxy(function (event) {
if ($('input[type="text"]', this.$container).is(':focus')) return;
this.$container.on('keydown', $.proxy(function(event) {
if ($('input[type="text"]', this.$container).is(':focus'))
return;
if ((event.keyCode == 9 || event.keyCode == 27) && this.$container.hasClass('open')) {
// Close on tab or escape.
$(this.$container).find(".multiselect.dropdown-toggle").click();
......@@ -339,10 +299,12 @@
index--;
}
// Navigate down.
else if (event.keyCode == 40 && index < $items.length - 1) {
else
if (event.keyCode == 40 && index < $items.length - 1) {
index++;
}
else if (!~index) {
else
if (!~index) {
index = 0;
}
......@@ -369,16 +331,71 @@
}, this));
},
// Build and bind filter.
buildFilter: function() {
$('.multiselect-container', this.$container).prepend('<div class="input-prepend"><span class="add-on"><i class="icon-search"></i></span><input class="multiselect-search" type="text" placeholder="' + this.options.filterPlaceholder + '"></div>');
$('.multiselect-search', this.$container).val(this.query).on('click', function(event) {
event.stopPropagation();
}).on('keydown', $.proxy(function(event) {
// This is useful to catch "keydown" events after the browser has
// updated the control.
clearTimeout(this.searchTimeout);
this.searchTimeout = this.asyncFunction($.proxy(function() {
if (this.query != event.target.value) {
this.query = event.target.value;
$.each($('.multiselect-container li', this.$container), $.proxy(function(index, element) {
var value = $('input', element).val();
if (value != this.options.selectAllValue) {
var text = $('label', element).text();
var value = $('input', element).val();
if (value && text && value != this.options.selectAllValue) {
// by default lets assume that element is not
// interesting for this search
var showElement = false;
var filterCandidate = '';
if ((this.options.filterBehavior == 'text' || this.options.filterBehavior == 'both')) {
filterCandidate = text;
}
if ((this.options.filterBehavior == 'value' || this.options.filterBehavior == 'both')) {
filterCandidate = value;
}
if (this.options.enableCaseInsensitiveFiltering && filterCandidate.toLowerCase().indexOf(this.query.toLowerCase()) > -1) {
showElement = true;
}
else if (filterCandidate.indexOf(this.query) > -1) {
showElement = true;
}
if (showElement) {
$(element).show();
}
else {
$(element).hide();
}
}
}
}, this));
}
}, this), 300, this);
}, this));
},
// Destroy - unbind - the plugin.
destroy: function() {
destroy : function() {
this.$container.remove();
this.$select.show();
},
// Refreshs the checked options based on the current state of the select.
refresh: function() {
refresh : function() {
$('option', this.$select).each($.proxy(function(index, element) {
var $input = $('.multiselect-container li input', this.$container).filter(function () {
var $input = $('.multiselect-container li input', this.$container).filter(function() {
return $(this).val() == $(element).val();
});
......@@ -409,11 +426,11 @@
},
// Select an option by its value.
select: function(value) {
var $option = $('option', this.$select).filter(function () {
select : function(value) {
var $option = $('option', this.$select).filter(function() {
return $(this).val() == value;
});
var $checkbox = $('.multiselect-container li input', this.$container).filter(function () {
var $checkbox = $('.multiselect-container li input', this.$container).filter(function() {
return $(this).val() == value;
});
......@@ -430,11 +447,11 @@
},
// Deselect an option by its value.
deselect: function(value) {
var $option = $('option', this.$select).filter(function () {
deselect : function(value) {
var $option = $('option', this.$select).filter(function() {
return $(this).val() == value;
});
var $checkbox = $('.multiselect-container li input', this.$container).filter(function () {
var $checkbox = $('.multiselect-container li input', this.$container).filter(function() {
return $(this).val() == value;
});
......@@ -450,34 +467,39 @@
},
// Rebuild the whole dropdown menu.
rebuild: function() {
rebuild : function() {
$('.multiselect-container', this.$container).html('');
this.buildDropdown(this.$select, this.options);
this.updateButtonText();
// Enable filtering.
if (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering) {
this.buildFilter();
}
},
// Get options by merging defaults and given options.
getOptions: function(options) {
getOptions : function(options) {
return $.extend({}, this.defaults, options);
},
updateButtonText: function() {
updateButtonText : function() {
var options = this.getSelected();
$('button', this.$container).html(this.options.buttonText(options, this.$select));
},
// Get all selected options.
getSelected: function () {
getSelected : function() {
return $('option:selected[value!="' + this.options.selectAllValue + '"]', this.$select);
},
updateOriginalOptions: function() {
updateOriginalOptions : function() {
this.originalOptions = this.$select.clone()[0].options;
},
asyncFunction: function (callback, timeout, self) {
asyncFunction : function(callback, timeout, self) {
var args = Array.prototype.slice.call(arguments, 3);
return setTimeout(function () {
return setTimeout(function() {
callback.apply(self || window, args);
}, timeout);
}
......@@ -485,16 +507,15 @@
$.fn.multiselect = function(option, parameter) {
return this.each(function() {
var data = $(this).data('multiselect'),
options = typeof option == 'object' && option;
var data = $(this).data('multiselect'), options = typeof option == 'object' && option;
// Initialize the multiselect.
if (!data) {
$(this).data('multiselect', (data = new Multiselect(this, options)));
$(this).data('multiselect', ( data = new Multiselect(this, options)));
}
// Call multiselect method.
if (typeof option == 'string') {
if ( typeof option == 'string') {
data[option](parameter);
}
});
......
......@@ -7,6 +7,7 @@
<meta name="copyright" content="David Stutz" />
<link rel="stylesheet" href="css/bootstrap-3.3.2.min.css" type="text/css">
<link rel="stylesheet" href="css/bootstrap-multiselect.css" type="text/css">
<link rel="stylesheet" href="css/prettify.css" type="text/css">
<script type="text/javascript" src="js/jquery-2.0.1.min.js"></script>
......
.multiselect-container {
position: absolute;
list-style-type: none;
margin: 0;
padding: 0;
input[type="text"] {
width: 70%;
}
.input-prepend {
padding: 3px;
}
> li {
padding: 0;
> label {
margin: 0;
padding: 3px 20px 3px 20px;
height: 100%;
cursor: pointer;
&.multiselect-header {
margin: 0;
padding: 3px 20px 3px 20px;
height: 100%;
}
> input[type="checkbox"] {
margin-bottom:5px;
}
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment