Replace Chosen jQuery plugin by custom UI component

This commit is contained in:
Frederic Guillot 2016-12-11 15:46:54 -05:00
parent a2b44371e0
commit ffb3926178
21 changed files with 385 additions and 1795 deletions

View File

@ -5,7 +5,8 @@ New features:
Improvements:
* Rewrite ui component that change user/group roles
* Replace Chosen jQuery plugin by custom UI component
* Rewrite UI component that change user/group roles
Bug fixes:

View File

@ -9,7 +9,11 @@
<?= $this->form->hidden('group_id', $values) ?>
<?= $this->form->label(t('User'), 'user_id') ?>
<?= $this->form->select('user_id', $users, $values, $errors, array('required'), 'chosen-select') ?>
<?= $this->app->component('select-dropdown-autocomplete', array(
'name' => 'user_id',
'items' => $users,
'defaultValue' => isset($values['user_id']) ? $values['user_id'] : null,
)) ?>
<div class="form-actions">
<button type="submit" class="btn btn-blue"><?= t('Save') ?></button>

View File

@ -1,13 +1,13 @@
<select id="board-selector"
class="chosen-select select-auto-redirect"
tabindex="-1"
data-search-threshold="0"
data-notfound="<?= t('No results match:') ?>"
data-placeholder="<?= t('Display another project') ?>"
data-redirect-regex="PROJECT_ID"
data-redirect-url="<?= $this->url->href('BoardViewController', 'show', array('project_id' => 'PROJECT_ID')) ?>">
<option value=""></option>
<?php foreach ($board_selector as $board_id => $board_name): ?>
<option value="<?= $board_id ?>"><?= $this->text->e($board_name) ?></option>
<?php endforeach ?>
</select>
<?= $this->app->component('select-dropdown-autocomplete', array(
'name' => 'boardId',
'placeholder' => t('Display another project'),
'items' => $board_selector,
'redirect' => array(
'regex' => 'PROJECT_ID',
'url' => $this->url->to('BoardViewController', 'show', array('project_id' => 'PROJECT_ID')),
),
'onFocus' => array(
'board.selector.open',
)
)) ?>

View File

@ -1,14 +1,17 @@
<div class="sidebar">
<?= $this->form->select(
'user_id',
$users,
$filter,
array(),
array('data-redirect-url="'.$this->url->href('ProjectUserOverviewController', $this->app->getRouterAction(), array('user_id' => 'USER_ID')).'"', 'data-redirect-regex="USER_ID"'),
'chosen-select select-auto-redirect'
) ?>
<?= $this->app->component('select-dropdown-autocomplete', array(
'name' => 'user_id',
'items' => $users,
'defaultValue' => $filter['user_id'],
'sortByKeys' => true,
'redirect' => array(
'regex' => 'USER_ID',
'url' => $this->url->to('ProjectUserOverviewController', $this->app->getRouterAction(), array('user_id' => 'USER_ID')),
),
)) ?>
<br>
<br><br>
<ul>
<li <?= $this->app->checkMenuSelection('ProjectUserOverviewController', 'managers') ?>>
<?= $this->url->link(t('Project managers'), 'ProjectUserOverviewController', 'managers', $filter) ?>

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,4 @@
// Open board selector: "b"
KB.onKey(98, function () {
KB.trigger('board.selector.open');
});

View 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(); });
});
}
};
});

View File

@ -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) {

View File

@ -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);
});

View File

@ -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

File diff suppressed because one or more lines are too long

View File

@ -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

View File

@ -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

View 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

View File

@ -12,6 +12,7 @@
@import tooltip
@import dropdown
@import accordion
@import select_dropdown
@import suggest_menu
@import dialog_box
@import popover

View File

@ -10,7 +10,6 @@
"dependencies": {
"jquery": "^2.2.3",
"fullcalendar": "^3.0.1",
"chosen": "^1.5.1",
"c3": "^0.4.11",
"jquery-ui": "^1.11.4",
"jqueryui-touch-punch": "*",

View File

@ -24,7 +24,6 @@ var vendor = {
css: [
'bower_components/jquery-ui/themes/base/jquery-ui.min.css',
'bower_components/jqueryui-timepicker-addon/dist/jquery-ui-timepicker-addon.min.css',
'bower_components/chosen/chosen.css',
'bower_components/select2/dist/css/select2.min.css',
'bower_components/fullcalendar/dist/fullcalendar.min.css',
'bower_components/font-awesome/css/font-awesome.min.css',
@ -45,7 +44,6 @@ var vendor = {
'bower_components/jqueryui-timepicker-addon/dist/jquery-ui-timepicker-addon.min.js',
'bower_components/jqueryui-timepicker-addon/dist/i18n/jquery-ui-timepicker-addon-i18n.min.js',
'bower_components/jqueryui-touch-punch/jquery.ui.touch-punch.min.js',
'bower_components/chosen/chosen.jquery.js',
'bower_components/select2/dist/js/select2.min.js',
'bower_components/moment/min/moment.min.js',
'bower_components/fullcalendar/dist/fullcalendar.min.js',
@ -86,9 +84,6 @@ gulp.task('vendor', function() {
gulp.src('bower_components/jquery-ui/themes/base/images/*')
.pipe(gulp.dest(dist.css + 'images/'));
gulp.src('bower_components/chosen/*.png')
.pipe(gulp.dest(dist.css + ''));
});
gulp.task('js', function() {