Replace Chosen jQuery plugin by custom UI component
This commit is contained in:
2
assets/css/app.min.css
vendored
2
assets/css/app.min.css
vendored
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 538 B |
Binary file not shown.
|
Before Width: | Height: | Size: 738 B |
452
assets/css/vendor.min.css
vendored
452
assets/css/vendor.min.css
vendored
File diff suppressed because one or more lines are too long
4
assets/js/app.min.js
vendored
4
assets/js/app.min.js
vendored
File diff suppressed because one or more lines are too long
4
assets/js/components/keyboard-shortcuts.js
Normal file
4
assets/js/components/keyboard-shortcuts.js
Normal file
@@ -0,0 +1,4 @@
|
||||
// Open board selector: "b"
|
||||
KB.onKey(98, function () {
|
||||
KB.trigger('board.selector.open');
|
||||
});
|
||||
240
assets/js/components/select-dropdown-autocomplete.js
Normal file
240
assets/js/components/select-dropdown-autocomplete.js
Normal file
@@ -0,0 +1,240 @@
|
||||
KB.component('select-dropdown-autocomplete', function(containerElement, options) {
|
||||
var componentElement, inputElement, inputHiddenElement;
|
||||
|
||||
function onKeyDown(e) {
|
||||
switch (e.keyCode) {
|
||||
case 27:
|
||||
inputElement.value = '';
|
||||
destroyDropdownMenu();
|
||||
break;
|
||||
case 38:
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
moveUp();
|
||||
break;
|
||||
case 40:
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
moveDown();
|
||||
break;
|
||||
case 13:
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
insertSelectedItem();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function onInputChanged() {
|
||||
destroyDropdownMenu();
|
||||
renderDropdownMenu();
|
||||
}
|
||||
|
||||
function onItemMouseOver(element) {
|
||||
if (KB.dom(element).hasClass('select-dropdown-menu-item')) {
|
||||
KB.find('.select-dropdown-menu-item.active').removeClass('active');
|
||||
KB.dom(element).addClass('active');
|
||||
}
|
||||
}
|
||||
|
||||
function onItemClick() {
|
||||
insertSelectedItem();
|
||||
}
|
||||
|
||||
function onDocumentClick(e) {
|
||||
if (! containerElement.contains(e.target)) {
|
||||
inputElement.value = '';
|
||||
destroyDropdownMenu();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleDropdownMenu() {
|
||||
var menuElement = KB.find('#select-dropdown-menu');
|
||||
|
||||
if (menuElement === null) {
|
||||
renderDropdownMenu();
|
||||
} else {
|
||||
destroyDropdownMenu();
|
||||
}
|
||||
}
|
||||
|
||||
function insertSelectedItem() {
|
||||
var element = KB.find('.select-dropdown-menu-item.active');
|
||||
var value = element.data('value');
|
||||
inputHiddenElement.value = value;
|
||||
inputElement.value = options.items[value];
|
||||
destroyDropdownMenu();
|
||||
|
||||
if (options.redirect) {
|
||||
var regex = new RegExp(options.redirect.regex, 'g');
|
||||
window.location = options.redirect.url.replace(regex, value);
|
||||
}
|
||||
}
|
||||
|
||||
function resetSelection() {
|
||||
var elements = document.querySelectorAll('.select-dropdown-menu-item');
|
||||
|
||||
for (var i = 0; i < elements.length; i++) {
|
||||
if (KB.dom(elements[i]).hasClass('active')) {
|
||||
KB.dom(elements[i]).removeClass('active');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {items: elements, index: i};
|
||||
}
|
||||
|
||||
function moveUp() {
|
||||
var result = resetSelection();
|
||||
|
||||
if (result.index > 0) {
|
||||
result.index = result.index - 1;
|
||||
}
|
||||
|
||||
KB.dom(result.items[result.index]).addClass('active');
|
||||
}
|
||||
|
||||
function moveDown() {
|
||||
var result = resetSelection();
|
||||
|
||||
if (result.index < result.items.length - 1) {
|
||||
result.index++;
|
||||
}
|
||||
|
||||
KB.dom(result.items[result.index]).addClass('active');
|
||||
}
|
||||
|
||||
function buildItems(items) {
|
||||
var elements = [];
|
||||
var keys = Object.keys(items);
|
||||
|
||||
if (options.sortByKeys) {
|
||||
keys.sort();
|
||||
}
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
elements.push({
|
||||
'class': 'select-dropdown-menu-item',
|
||||
'text': items[keys[i]],
|
||||
'data-label': items[keys[i]],
|
||||
'data-value': keys[i]
|
||||
});
|
||||
}
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
function filterItems(text, items) {
|
||||
var filteredItems = [];
|
||||
var hasActiveItem = false;
|
||||
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
if (text.length === 0 || items[i]['data-label'].toLowerCase().indexOf(text.toLowerCase()) === 0) {
|
||||
var item = items[i];
|
||||
|
||||
if (typeof options.defaultValue !== 'undefined' && String(options.defaultValue) === item['data-value']) {
|
||||
item.class += ' active';
|
||||
hasActiveItem = true;
|
||||
}
|
||||
|
||||
filteredItems.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (! hasActiveItem && filteredItems.length > 0) {
|
||||
filteredItems[0].class += ' active';
|
||||
}
|
||||
|
||||
return filteredItems;
|
||||
}
|
||||
|
||||
function buildDropdownMenu() {
|
||||
var itemElements = filterItems(inputElement.value, buildItems(options.items));
|
||||
var componentPosition = componentElement.getBoundingClientRect();
|
||||
|
||||
if (itemElements.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return KB.dom('ul')
|
||||
.attr('id', 'select-dropdown-menu')
|
||||
.style('top', componentPosition.bottom + 'px')
|
||||
.style('left', componentPosition.left + 'px')
|
||||
.style('width', componentPosition.width + 'px')
|
||||
.mouseover(onItemMouseOver)
|
||||
.click(onItemClick)
|
||||
.for('li', itemElements)
|
||||
.build();
|
||||
}
|
||||
|
||||
function destroyDropdownMenu() {
|
||||
var menuElement = KB.find('#select-dropdown-menu');
|
||||
|
||||
if (menuElement !== null) {
|
||||
menuElement.remove();
|
||||
}
|
||||
|
||||
document.removeEventListener('keydown', onKeyDown, false);
|
||||
document.removeEventListener('click', onDocumentClick, false);
|
||||
}
|
||||
|
||||
function renderDropdownMenu() {
|
||||
var element = buildDropdownMenu();
|
||||
|
||||
if (element !== null) {
|
||||
document.body.appendChild(element);
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', onKeyDown, false);
|
||||
document.addEventListener('click', onDocumentClick, false);
|
||||
}
|
||||
|
||||
function getPlaceholderValue() {
|
||||
if (options.defaultValue && options.defaultValue in options.items) {
|
||||
return options.items[options.defaultValue];
|
||||
}
|
||||
|
||||
if (options.placeholder) {
|
||||
return options.placeholder;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
this.render = function () {
|
||||
var dropdownIconElement = KB.dom('i')
|
||||
.attr('class', 'fa fa-chevron-down select-dropdown-chevron')
|
||||
.click(toggleDropdownMenu)
|
||||
.build();
|
||||
|
||||
inputHiddenElement = KB.dom('input')
|
||||
.attr('type', 'hidden')
|
||||
.attr('name', options.name)
|
||||
.attr('value', options.defaultValue || '')
|
||||
.build();
|
||||
|
||||
inputElement = KB.dom('input')
|
||||
.attr('type', 'text')
|
||||
.attr('placeholder', getPlaceholderValue())
|
||||
.addClass('select-dropdown-input')
|
||||
.style('width', (containerElement.offsetWidth - 30) + 'px')
|
||||
.on('focus', toggleDropdownMenu)
|
||||
.on('input', onInputChanged, true)
|
||||
.build();
|
||||
|
||||
componentElement = KB.dom('div')
|
||||
.addClass('select-dropdown-input-container')
|
||||
.add(inputHiddenElement)
|
||||
.add(inputElement)
|
||||
.add(dropdownIconElement)
|
||||
.build();
|
||||
|
||||
containerElement.appendChild(componentElement);
|
||||
|
||||
if (options.onFocus) {
|
||||
options.onFocus.forEach(function (eventName) {
|
||||
KB.on(eventName, function() { inputElement.focus(); });
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -6,6 +6,7 @@ var KB = {
|
||||
listeners: {
|
||||
clicks: {},
|
||||
changes: {},
|
||||
keys: {},
|
||||
internals: {}
|
||||
}
|
||||
};
|
||||
@@ -36,6 +37,10 @@ KB.onChange = function (selector, callback) {
|
||||
this.listeners.changes[selector] = callback;
|
||||
};
|
||||
|
||||
KB.onKey = function (key, callback) {
|
||||
this.listeners.keys[key] = callback;
|
||||
};
|
||||
|
||||
KB.listen = function () {
|
||||
var self = this;
|
||||
|
||||
@@ -56,8 +61,28 @@ KB.listen = function () {
|
||||
}
|
||||
}
|
||||
|
||||
function onKeypress(e) {
|
||||
var key = (typeof e.which === 'number') ? e.which : e.keyCode;
|
||||
var element = e.target;
|
||||
|
||||
if (element.tagName === 'INPUT' ||
|
||||
element.tagName === 'SELECT' ||
|
||||
element.tagName === 'TEXTAREA' ||
|
||||
element.isContentEditable) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var keyMap in self.listeners.keys) {
|
||||
if (self.listeners.keys.hasOwnProperty(keyMap) && key === parseInt(keyMap)) {
|
||||
e.preventDefault();
|
||||
self.listeners.keys[key](e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('click', onClick, false);
|
||||
document.addEventListener('change', onChange, false);
|
||||
document.addEventListener('keypress', onKeypress, false);
|
||||
};
|
||||
|
||||
KB.component = function (name, object) {
|
||||
|
||||
@@ -43,9 +43,11 @@ KB.dom = function (tag) {
|
||||
return this;
|
||||
};
|
||||
|
||||
this.on = function (eventName, callback) {
|
||||
this.on = function (eventName, callback, ignorePrevent) {
|
||||
element.addEventListener(eventName, function (e) {
|
||||
e.preventDefault();
|
||||
if (! ignorePrevent) {
|
||||
e.preventDefault();
|
||||
}
|
||||
callback(e.target);
|
||||
});
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ Kanboard.App.prototype.execute = function() {
|
||||
}
|
||||
|
||||
this.focus();
|
||||
this.chosen();
|
||||
this.keyboardShortcuts();
|
||||
this.datePicker();
|
||||
this.autoComplete();
|
||||
@@ -56,12 +55,6 @@ Kanboard.App.prototype.keyboardShortcuts = function() {
|
||||
}
|
||||
});
|
||||
|
||||
// Open board selector
|
||||
Mousetrap.bind("b", function(e) {
|
||||
e.preventDefault();
|
||||
$('#board-selector').trigger('chosen:open');
|
||||
});
|
||||
|
||||
// Close popover and dropdown
|
||||
Mousetrap.bindGlobal("esc", function() {
|
||||
if (! document.getElementById('suggest-menu')) {
|
||||
@@ -88,27 +81,6 @@ Kanboard.App.prototype.focus = function() {
|
||||
});
|
||||
};
|
||||
|
||||
Kanboard.App.prototype.chosen = function() {
|
||||
$(".chosen-select").each(function() {
|
||||
var searchThreshold = $(this).data("search-threshold");
|
||||
|
||||
if (searchThreshold === undefined) {
|
||||
searchThreshold = 10;
|
||||
}
|
||||
|
||||
$(this).chosen({
|
||||
width: "180px",
|
||||
no_results_text: $(this).data("notfound"),
|
||||
disable_search_threshold: searchThreshold
|
||||
});
|
||||
});
|
||||
|
||||
$(".select-auto-redirect").change(function() {
|
||||
var regex = new RegExp($(this).data('redirect-regex'), 'g');
|
||||
window.location = $(this).data('redirect-url').replace(regex, $(this).val());
|
||||
});
|
||||
};
|
||||
|
||||
Kanboard.App.prototype.datePicker = function() {
|
||||
var bodyElement = $("body");
|
||||
var dateFormat = bodyElement.data("js-date-format");
|
||||
|
||||
1283
assets/js/vendor.min.js
vendored
1283
assets/js/vendor.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -12,22 +12,20 @@ header
|
||||
@include md-device
|
||||
@include grid_width(15/100)
|
||||
@include sm-device
|
||||
@include grid_width(65/100)
|
||||
@include grid_width(30/100)
|
||||
order: 2
|
||||
|
||||
.board-selector-container
|
||||
@include grid_width(15/100)
|
||||
@include grid_width(25/100)
|
||||
@include md-device
|
||||
@include grid_width(20/100)
|
||||
@include sm-device
|
||||
@include grid_width(35/100)
|
||||
@include grid_width(70/100)
|
||||
order: 1
|
||||
margin-bottom: 5px
|
||||
|
||||
.title-container
|
||||
@include grid_width(75/100)
|
||||
@include md-device
|
||||
@include grid_width(65/100)
|
||||
@include grid_width(65/100)
|
||||
@include sm-device
|
||||
@include grid_width(1)
|
||||
order: 3
|
||||
|
||||
@@ -10,12 +10,24 @@
|
||||
margin: 0
|
||||
+appearance
|
||||
|
||||
&:first-child
|
||||
border-radius: 5px 0 0 5px
|
||||
|
||||
&:last-child
|
||||
border-radius: 0 5px 5px 0
|
||||
|
||||
.input-addon-item
|
||||
background-color: rgba(147, 128, 108, 0.1)
|
||||
color: #666
|
||||
font: inherit
|
||||
font-weight: normal
|
||||
|
||||
&:first-child
|
||||
border-radius: 5px 0 0 5px
|
||||
|
||||
&:last-child
|
||||
border-radius: 0 5px 5px 0
|
||||
|
||||
@include xs-device
|
||||
.dropdown
|
||||
.fa-caret-down
|
||||
@@ -27,9 +39,3 @@
|
||||
|
||||
&:not(:first-child)
|
||||
border-left: 0
|
||||
|
||||
.input-addon-field:first-child, .input-addon-item:first-child
|
||||
border-radius: 5px 0 0 5px
|
||||
|
||||
.input-addon-field:last-child, .input-addon-item:last-child
|
||||
border-radius: 0 5px 5px 0
|
||||
|
||||
45
assets/sass/_select_dropdown.sass
Normal file
45
assets/sass/_select_dropdown.sass
Normal file
@@ -0,0 +1,45 @@
|
||||
@import variables
|
||||
|
||||
#select-dropdown-menu
|
||||
position: absolute
|
||||
display: block
|
||||
z-index: 1000
|
||||
min-width: 160px
|
||||
padding: 5px 0
|
||||
background: #fff
|
||||
list-style: none
|
||||
border: 1px solid #ccc
|
||||
border-radius: 3px
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, .175)
|
||||
|
||||
.select-dropdown-menu-item
|
||||
white-space: nowrap
|
||||
overflow: hidden
|
||||
padding: 3px 10px
|
||||
color: color('medium')
|
||||
cursor: pointer
|
||||
border-bottom: 1px solid #f8f8f8
|
||||
line-height: 1.5em
|
||||
font-weight: 400
|
||||
&.active
|
||||
color: #fff
|
||||
background: #428bca
|
||||
&:last-child
|
||||
border: none
|
||||
|
||||
.select-dropdown-input-container
|
||||
position: relative
|
||||
border: 1px solid #ccc
|
||||
border-radius: 5px
|
||||
input.select-dropdown-input
|
||||
margin: 0 0 0 5px
|
||||
border: none
|
||||
&:focus
|
||||
border: none
|
||||
box-shadow: none
|
||||
.select-dropdown-chevron
|
||||
color: color('medium')
|
||||
position: absolute
|
||||
top: 4px
|
||||
right: 5px
|
||||
cursor: pointer
|
||||
@@ -12,6 +12,7 @@
|
||||
@import tooltip
|
||||
@import dropdown
|
||||
@import accordion
|
||||
@import select_dropdown
|
||||
@import suggest_menu
|
||||
@import dialog_box
|
||||
@import popover
|
||||
|
||||
Reference in New Issue
Block a user