luci-base: move dropdown, combox and dynlist widget code to L.ui
Move the widget code to the ui class and replace it with compatibility shims in cbi.js Signed-off-by: Jo-Philipp Wich <jo@mein.io>
This commit is contained in:
parent
e4c29c6db4
commit
558f8c3b2a
2 changed files with 1083 additions and 830 deletions
|
@ -738,6 +738,11 @@ function cbi_d_update() {
|
||||||
function cbi_init() {
|
function cbi_init() {
|
||||||
var nodes;
|
var nodes;
|
||||||
|
|
||||||
|
document.querySelectorAll('.cbi-dropdown').forEach(function(node) {
|
||||||
|
cbi_dropdown_init(node);
|
||||||
|
node.addEventListener('cbi-dropdown-change', cbi_d_update);
|
||||||
|
});
|
||||||
|
|
||||||
nodes = document.querySelectorAll('[data-strings]');
|
nodes = document.querySelectorAll('[data-strings]');
|
||||||
|
|
||||||
for (var i = 0, node; (node = nodes[i]) !== undefined; i++) {
|
for (var i = 0, node; (node = nodes[i]) !== undefined; i++) {
|
||||||
|
@ -772,8 +777,8 @@ function cbi_init() {
|
||||||
nodes = document.querySelectorAll('[data-choices]');
|
nodes = document.querySelectorAll('[data-choices]');
|
||||||
|
|
||||||
for (var i = 0, node; (node = nodes[i]) !== undefined; i++) {
|
for (var i = 0, node; (node = nodes[i]) !== undefined; i++) {
|
||||||
var choices = JSON.parse(node.getAttribute('data-choices'));
|
var choices = JSON.parse(node.getAttribute('data-choices')),
|
||||||
var options = {};
|
options = {};
|
||||||
|
|
||||||
for (var j = 0; j < choices[0].length; j++)
|
for (var j = 0; j < choices[0].length; j++)
|
||||||
options[choices[0][j]] = choices[1][j];
|
options[choices[0][j]] = choices[1][j];
|
||||||
|
@ -781,15 +786,24 @@ function cbi_init() {
|
||||||
var def = (node.getAttribute('data-optional') === 'true')
|
var def = (node.getAttribute('data-optional') === 'true')
|
||||||
? node.placeholder || '' : null;
|
? node.placeholder || '' : null;
|
||||||
|
|
||||||
cbi_combobox_init(node, options, def,
|
var cb = new L.ui.Combobox(node.value, options, {
|
||||||
node.getAttribute('data-manual'));
|
name: node.getAttribute('name'),
|
||||||
|
sort: choices[0],
|
||||||
|
select_placeholder: def || _('-- Please choose --'),
|
||||||
|
custom_placeholder: node.getAttribute('data-manual') || _('-- custom --')
|
||||||
|
});
|
||||||
|
|
||||||
|
var n = cb.render();
|
||||||
|
n.addEventListener('cbi-dropdown-change', cbi_d_update);
|
||||||
|
node.parentNode.replaceChild(n, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes = document.querySelectorAll('[data-dynlist]');
|
nodes = document.querySelectorAll('[data-dynlist]');
|
||||||
|
|
||||||
for (var i = 0, node; (node = nodes[i]) !== undefined; i++) {
|
for (var i = 0, node; (node = nodes[i]) !== undefined; i++) {
|
||||||
var choices = JSON.parse(node.getAttribute('data-dynlist'));
|
var choices = JSON.parse(node.getAttribute('data-dynlist')),
|
||||||
var options = null;
|
values = JSON.parse(node.getAttribute('data-values') || '[]'),
|
||||||
|
options = null;
|
||||||
|
|
||||||
if (choices[0] && choices[0].length) {
|
if (choices[0] && choices[0].length) {
|
||||||
options = {};
|
options = {};
|
||||||
|
@ -798,8 +812,17 @@ function cbi_init() {
|
||||||
options[choices[0][j]] = choices[1][j];
|
options[choices[0][j]] = choices[1][j];
|
||||||
}
|
}
|
||||||
|
|
||||||
cbi_dynlist_init(node, choices[2], choices[3], options);
|
var dl = new L.ui.DynamicList(values, options, {
|
||||||
node.addEventListener('cbi-dynlist-change', cbi_d_update);
|
name: node.getAttribute('data-prefix'),
|
||||||
|
sort: choices[0],
|
||||||
|
datatype: choices[2],
|
||||||
|
optional: choices[3],
|
||||||
|
placeholder: node.getAttribute('data-placeholder')
|
||||||
|
});
|
||||||
|
|
||||||
|
var n = dl.render();
|
||||||
|
n.addEventListener('cbi-dynlist-change', cbi_d_update);
|
||||||
|
node.parentNode.replaceChild(n, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes = document.querySelectorAll('[data-type]');
|
nodes = document.querySelectorAll('[data-type]');
|
||||||
|
@ -809,11 +832,6 @@ function cbi_init() {
|
||||||
node.getAttribute('data-type'));
|
node.getAttribute('data-type'));
|
||||||
}
|
}
|
||||||
|
|
||||||
document.querySelectorAll('.cbi-dropdown').forEach(function(node) {
|
|
||||||
cbi_dropdown_init(node);
|
|
||||||
node.addEventListener('cbi-dropdown-change', cbi_d_update);
|
|
||||||
});
|
|
||||||
|
|
||||||
document.querySelectorAll('[data-browser]').forEach(cbi_browser_init);
|
document.querySelectorAll('[data-browser]').forEach(cbi_browser_init);
|
||||||
|
|
||||||
document.querySelectorAll('.cbi-tooltip:not(:empty)').forEach(function(s) {
|
document.querySelectorAll('.cbi-tooltip:not(:empty)').forEach(function(s) {
|
||||||
|
@ -835,46 +853,6 @@ function cbi_init() {
|
||||||
cbi_d_update();
|
cbi_d_update();
|
||||||
}
|
}
|
||||||
|
|
||||||
function cbi_combobox_init(id, values, def, man) {
|
|
||||||
var obj = (typeof(id) === 'string') ? document.getElementById(id) : id;
|
|
||||||
var sb = E('div', {
|
|
||||||
'name': obj.name,
|
|
||||||
'class': 'cbi-dropdown',
|
|
||||||
'display-items': 5,
|
|
||||||
'optional': obj.getAttribute('data-optional'),
|
|
||||||
'placeholder': _('-- Please choose --'),
|
|
||||||
'data-type': obj.getAttribute('data-type'),
|
|
||||||
'data-optional': obj.getAttribute('data-optional')
|
|
||||||
}, [ E('ul') ]);
|
|
||||||
|
|
||||||
if (!(obj.value in values) && obj.value.length) {
|
|
||||||
sb.lastElementChild.appendChild(E('li', {
|
|
||||||
'data-value': obj.value,
|
|
||||||
'selected': ''
|
|
||||||
}, obj.value.length ? obj.value : (def || _('-- Please choose --'))));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i in values) {
|
|
||||||
sb.lastElementChild.appendChild(E('li', {
|
|
||||||
'data-value': i,
|
|
||||||
'selected': (i == obj.value) ? '' : null
|
|
||||||
}, values[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.lastElementChild.appendChild(E('li', { 'data-value': '-' }, [
|
|
||||||
E('input', {
|
|
||||||
'type': 'text',
|
|
||||||
'class': 'create-item-input',
|
|
||||||
'data-type': obj.getAttribute('data-type'),
|
|
||||||
'data-optional': true,
|
|
||||||
'placeholder': (man || _('-- custom --'))
|
|
||||||
})
|
|
||||||
]));
|
|
||||||
|
|
||||||
sb.value = obj.value;
|
|
||||||
obj.parentNode.replaceChild(sb, obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
function cbi_filebrowser(id, defpath) {
|
function cbi_filebrowser(id, defpath) {
|
||||||
var field = L.dom.elem(id) ? id : document.getElementById(id);
|
var field = L.dom.elem(id) ? id : document.getElementById(id);
|
||||||
var browser = window.open(
|
var browser = window.open(
|
||||||
|
@ -898,171 +876,6 @@ function cbi_browser_init(field)
|
||||||
}), field.nextSibling);
|
}), field.nextSibling);
|
||||||
}
|
}
|
||||||
|
|
||||||
CBIDynamicList = {
|
|
||||||
addItem: function(dl, value, text, flash) {
|
|
||||||
var exists = false,
|
|
||||||
new_item = E('div', { 'class': flash ? 'item flash' : 'item', 'tabindex': 0 }, [
|
|
||||||
E('span', {}, text || value),
|
|
||||||
E('input', {
|
|
||||||
'type': 'hidden',
|
|
||||||
'name': dl.getAttribute('data-prefix'),
|
|
||||||
'value': value })]);
|
|
||||||
|
|
||||||
dl.querySelectorAll('.item, .add-item').forEach(function(item) {
|
|
||||||
if (exists)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var hidden = item.querySelector('input[type="hidden"]');
|
|
||||||
|
|
||||||
if (hidden && hidden.value === value)
|
|
||||||
exists = true;
|
|
||||||
else if (!hidden || hidden.value >= value)
|
|
||||||
exists = !!item.parentNode.insertBefore(new_item, item);
|
|
||||||
});
|
|
||||||
|
|
||||||
dl.dispatchEvent(new CustomEvent('cbi-dynlist-change', {
|
|
||||||
bubbles: true,
|
|
||||||
detail: {
|
|
||||||
instance: this,
|
|
||||||
element: dl,
|
|
||||||
value: value,
|
|
||||||
add: true
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
removeItem: function(dl, item) {
|
|
||||||
var value = item.querySelector('input[type="hidden"]').value;
|
|
||||||
var sb = dl.querySelector('.cbi-dropdown');
|
|
||||||
if (sb)
|
|
||||||
sb.querySelectorAll('ul > li').forEach(function(li) {
|
|
||||||
if (li.getAttribute('data-value') === value)
|
|
||||||
li.removeAttribute('unselectable');
|
|
||||||
});
|
|
||||||
|
|
||||||
item.parentNode.removeChild(item);
|
|
||||||
|
|
||||||
dl.dispatchEvent(new CustomEvent('cbi-dynlist-change', {
|
|
||||||
bubbles: true,
|
|
||||||
detail: {
|
|
||||||
instance: this,
|
|
||||||
element: dl,
|
|
||||||
value: value,
|
|
||||||
remove: true
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
handleClick: function(ev) {
|
|
||||||
var dl = ev.currentTarget,
|
|
||||||
item = findParent(ev.target, '.item');
|
|
||||||
|
|
||||||
if (item) {
|
|
||||||
this.removeItem(dl, item);
|
|
||||||
}
|
|
||||||
else if (matchesElem(ev.target, '.cbi-button-add')) {
|
|
||||||
var input = ev.target.previousElementSibling;
|
|
||||||
if (input.value.length && !input.classList.contains('cbi-input-invalid')) {
|
|
||||||
this.addItem(dl, input.value, null, true);
|
|
||||||
input.value = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
handleDropdownChange: function(ev) {
|
|
||||||
var dl = ev.currentTarget,
|
|
||||||
sbIn = ev.detail.instance,
|
|
||||||
sbEl = ev.detail.element,
|
|
||||||
sbVal = ev.detail.value;
|
|
||||||
|
|
||||||
if (sbVal === null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
sbIn.setValues(sbEl, null);
|
|
||||||
sbVal.element.setAttribute('unselectable', '');
|
|
||||||
|
|
||||||
this.addItem(dl, sbVal.value, sbVal.text, true);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleKeydown: function(ev) {
|
|
||||||
var dl = ev.currentTarget,
|
|
||||||
item = findParent(ev.target, '.item');
|
|
||||||
|
|
||||||
if (item) {
|
|
||||||
switch (ev.keyCode) {
|
|
||||||
case 8: /* backspace */
|
|
||||||
if (item.previousElementSibling)
|
|
||||||
item.previousElementSibling.focus();
|
|
||||||
|
|
||||||
this.removeItem(dl, item);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 46: /* delete */
|
|
||||||
if (item.nextElementSibling) {
|
|
||||||
if (item.nextElementSibling.classList.contains('item'))
|
|
||||||
item.nextElementSibling.focus();
|
|
||||||
else
|
|
||||||
item.nextElementSibling.firstElementChild.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.removeItem(dl, item);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (matchesElem(ev.target, '.cbi-input-text')) {
|
|
||||||
switch (ev.keyCode) {
|
|
||||||
case 13: /* enter */
|
|
||||||
if (ev.target.value.length && !ev.target.classList.contains('cbi-input-invalid')) {
|
|
||||||
this.addItem(dl, ev.target.value, null, true);
|
|
||||||
ev.target.value = '';
|
|
||||||
ev.target.blur();
|
|
||||||
ev.target.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
ev.preventDefault();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function cbi_dynlist_init(dl, datatype, optional, choices)
|
|
||||||
{
|
|
||||||
if (!(this instanceof cbi_dynlist_init))
|
|
||||||
return new cbi_dynlist_init(dl, datatype, optional, choices);
|
|
||||||
|
|
||||||
dl.classList.add('cbi-dynlist');
|
|
||||||
dl.appendChild(E('div', { 'class': 'add-item' }, E('input', {
|
|
||||||
'type': 'text',
|
|
||||||
'name': 'cbi.dynlist.' + dl.getAttribute('data-prefix'),
|
|
||||||
'class': 'cbi-input-text',
|
|
||||||
'placeholder': dl.getAttribute('data-placeholder'),
|
|
||||||
'data-type': datatype,
|
|
||||||
'data-optional': true
|
|
||||||
})));
|
|
||||||
|
|
||||||
if (choices)
|
|
||||||
cbi_combobox_init(dl.lastElementChild.lastElementChild, choices, '', _('-- custom --'));
|
|
||||||
else
|
|
||||||
dl.lastElementChild.appendChild(E('div', { 'class': 'cbi-button cbi-button-add' }, '+'));
|
|
||||||
|
|
||||||
dl.addEventListener('click', this.handleClick.bind(this));
|
|
||||||
dl.addEventListener('keydown', this.handleKeydown.bind(this));
|
|
||||||
dl.addEventListener('cbi-dropdown-change', this.handleDropdownChange.bind(this));
|
|
||||||
|
|
||||||
try {
|
|
||||||
var values = JSON.parse(dl.getAttribute('data-values') || '[]');
|
|
||||||
|
|
||||||
if (typeof(values) === 'object' && Array.isArray(values))
|
|
||||||
for (var i = 0; i < values.length; i++)
|
|
||||||
this.addItem(dl, values[i], choices ? choices[values[i]] : null);
|
|
||||||
}
|
|
||||||
catch (e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
cbi_dynlist_init.prototype = CBIDynamicList;
|
|
||||||
|
|
||||||
|
|
||||||
function cbi_validate_form(form, errmsg)
|
function cbi_validate_form(form, errmsg)
|
||||||
{
|
{
|
||||||
/* if triggered by a section removal or addition, don't validate */
|
/* if triggered by a section removal or addition, don't validate */
|
||||||
|
@ -1445,620 +1258,11 @@ if (typeof(window.CustomEvent) !== 'function') {
|
||||||
window.CustomEvent = CustomEvent;
|
window.CustomEvent = CustomEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
CBIDropdown = {
|
|
||||||
openDropdown: function(sb) {
|
|
||||||
var st = window.getComputedStyle(sb, null),
|
|
||||||
ul = sb.querySelector('ul'),
|
|
||||||
li = ul.querySelectorAll('li'),
|
|
||||||
fl = findParent(sb, '.cbi-value-field'),
|
|
||||||
sel = ul.querySelector('[selected]'),
|
|
||||||
rect = sb.getBoundingClientRect(),
|
|
||||||
items = Math.min(this.dropdown_items, li.length);
|
|
||||||
|
|
||||||
document.querySelectorAll('.cbi-dropdown[open]').forEach(function(s) {
|
|
||||||
s.dispatchEvent(new CustomEvent('cbi-dropdown-close', {}));
|
|
||||||
});
|
|
||||||
|
|
||||||
sb.setAttribute('open', '');
|
|
||||||
|
|
||||||
var pv = ul.cloneNode(true);
|
|
||||||
pv.classList.add('preview');
|
|
||||||
|
|
||||||
if (fl)
|
|
||||||
fl.classList.add('cbi-dropdown-open');
|
|
||||||
|
|
||||||
if ('ontouchstart' in window) {
|
|
||||||
var vpWidth = Math.max(document.documentElement.clientWidth, window.innerWidth || 0),
|
|
||||||
vpHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0),
|
|
||||||
scrollFrom = window.pageYOffset,
|
|
||||||
scrollTo = scrollFrom + rect.top - vpHeight * 0.5,
|
|
||||||
start = null;
|
|
||||||
|
|
||||||
ul.style.top = sb.offsetHeight + 'px';
|
|
||||||
ul.style.left = -rect.left + 'px';
|
|
||||||
ul.style.right = (rect.right - vpWidth) + 'px';
|
|
||||||
ul.style.maxHeight = (vpHeight * 0.5) + 'px';
|
|
||||||
ul.style.WebkitOverflowScrolling = 'touch';
|
|
||||||
|
|
||||||
var scrollStep = function(timestamp) {
|
|
||||||
if (!start) {
|
|
||||||
start = timestamp;
|
|
||||||
ul.scrollTop = sel ? Math.max(sel.offsetTop - sel.offsetHeight, 0) : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var duration = Math.max(timestamp - start, 1);
|
|
||||||
if (duration < 100) {
|
|
||||||
document.body.scrollTop = scrollFrom + (scrollTo - scrollFrom) * (duration / 100);
|
|
||||||
window.requestAnimationFrame(scrollStep);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
document.body.scrollTop = scrollTo;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.requestAnimationFrame(scrollStep);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ul.style.maxHeight = '1px';
|
|
||||||
ul.style.top = ul.style.bottom = '';
|
|
||||||
|
|
||||||
window.requestAnimationFrame(function() {
|
|
||||||
var height = items * li[Math.max(0, li.length - 2)].offsetHeight;
|
|
||||||
|
|
||||||
ul.scrollTop = sel ? Math.max(sel.offsetTop - sel.offsetHeight, 0) : 0;
|
|
||||||
ul.style[((rect.top + rect.height + height) > window.innerHeight) ? 'bottom' : 'top'] = rect.height + 'px';
|
|
||||||
ul.style.maxHeight = height + 'px';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.querySelectorAll('[selected] input[type="checkbox"]').forEach(function(c) {
|
|
||||||
c.checked = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
ul.classList.add('dropdown');
|
|
||||||
|
|
||||||
sb.insertBefore(pv, ul.nextElementSibling);
|
|
||||||
|
|
||||||
li.forEach(function(l) {
|
|
||||||
l.setAttribute('tabindex', 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
sb.lastElementChild.setAttribute('tabindex', 0);
|
|
||||||
|
|
||||||
this.setFocus(sb, sel || li[0], true);
|
|
||||||
},
|
|
||||||
|
|
||||||
closeDropdown: function(sb, no_focus) {
|
|
||||||
if (!sb.hasAttribute('open'))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var pv = sb.querySelector('ul.preview'),
|
|
||||||
ul = sb.querySelector('ul.dropdown'),
|
|
||||||
li = ul.querySelectorAll('li'),
|
|
||||||
fl = findParent(sb, '.cbi-value-field');
|
|
||||||
|
|
||||||
li.forEach(function(l) { l.removeAttribute('tabindex'); });
|
|
||||||
sb.lastElementChild.removeAttribute('tabindex');
|
|
||||||
|
|
||||||
sb.removeChild(pv);
|
|
||||||
sb.removeAttribute('open');
|
|
||||||
sb.style.width = sb.style.height = '';
|
|
||||||
|
|
||||||
ul.classList.remove('dropdown');
|
|
||||||
ul.style.top = ul.style.bottom = ul.style.maxHeight = '';
|
|
||||||
|
|
||||||
if (fl)
|
|
||||||
fl.classList.remove('cbi-dropdown-open');
|
|
||||||
|
|
||||||
if (!no_focus)
|
|
||||||
this.setFocus(sb, sb);
|
|
||||||
|
|
||||||
this.saveValues(sb, ul);
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleItem: function(sb, li, force_state) {
|
|
||||||
if (li.hasAttribute('unselectable'))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (this.multi) {
|
|
||||||
var cbox = li.querySelector('input[type="checkbox"]'),
|
|
||||||
items = li.parentNode.querySelectorAll('li'),
|
|
||||||
label = sb.querySelector('ul.preview'),
|
|
||||||
sel = li.parentNode.querySelectorAll('[selected]').length,
|
|
||||||
more = sb.querySelector('.more'),
|
|
||||||
ndisplay = this.display_items,
|
|
||||||
n = 0;
|
|
||||||
|
|
||||||
if (li.hasAttribute('selected')) {
|
|
||||||
if (force_state !== true) {
|
|
||||||
if (sel > 1 || this.optional) {
|
|
||||||
li.removeAttribute('selected');
|
|
||||||
cbox.checked = cbox.disabled = false;
|
|
||||||
sel--;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
cbox.disabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (force_state !== false) {
|
|
||||||
li.setAttribute('selected', '');
|
|
||||||
cbox.checked = true;
|
|
||||||
cbox.disabled = false;
|
|
||||||
sel++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (label.firstElementChild)
|
|
||||||
label.removeChild(label.firstElementChild);
|
|
||||||
|
|
||||||
for (var i = 0; i < items.length; i++) {
|
|
||||||
items[i].removeAttribute('display');
|
|
||||||
if (items[i].hasAttribute('selected')) {
|
|
||||||
if (ndisplay-- > 0) {
|
|
||||||
items[i].setAttribute('display', n++);
|
|
||||||
label.appendChild(items[i].cloneNode(true));
|
|
||||||
}
|
|
||||||
var c = items[i].querySelector('input[type="checkbox"]');
|
|
||||||
if (c)
|
|
||||||
c.disabled = (sel == 1 && !this.optional);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ndisplay < 0)
|
|
||||||
sb.setAttribute('more', '');
|
|
||||||
else
|
|
||||||
sb.removeAttribute('more');
|
|
||||||
|
|
||||||
if (ndisplay === this.display_items)
|
|
||||||
sb.setAttribute('empty', '');
|
|
||||||
else
|
|
||||||
sb.removeAttribute('empty');
|
|
||||||
|
|
||||||
more.innerHTML = (ndisplay === this.display_items) ? this.placeholder : '···';
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var sel = li.parentNode.querySelector('[selected]');
|
|
||||||
if (sel) {
|
|
||||||
sel.removeAttribute('display');
|
|
||||||
sel.removeAttribute('selected');
|
|
||||||
}
|
|
||||||
|
|
||||||
li.setAttribute('display', 0);
|
|
||||||
li.setAttribute('selected', '');
|
|
||||||
|
|
||||||
this.closeDropdown(sb, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.saveValues(sb, li.parentNode);
|
|
||||||
},
|
|
||||||
|
|
||||||
transformItem: function(sb, li) {
|
|
||||||
var cbox = E('form', {}, E('input', { type: 'checkbox', tabindex: -1, onclick: 'event.preventDefault()' })),
|
|
||||||
label = E('label');
|
|
||||||
|
|
||||||
while (li.firstChild)
|
|
||||||
label.appendChild(li.firstChild);
|
|
||||||
|
|
||||||
li.appendChild(cbox);
|
|
||||||
li.appendChild(label);
|
|
||||||
},
|
|
||||||
|
|
||||||
saveValues: function(sb, ul) {
|
|
||||||
var sel = ul.querySelectorAll('li[selected]'),
|
|
||||||
div = sb.lastElementChild,
|
|
||||||
strval = '',
|
|
||||||
values = [];
|
|
||||||
|
|
||||||
while (div.lastElementChild)
|
|
||||||
div.removeChild(div.lastElementChild);
|
|
||||||
|
|
||||||
sel.forEach(function (s) {
|
|
||||||
if (s.hasAttribute('placeholder'))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var v = {
|
|
||||||
text: s.innerText,
|
|
||||||
value: s.hasAttribute('data-value') ? s.getAttribute('data-value') : s.innerText,
|
|
||||||
element: s
|
|
||||||
};
|
|
||||||
|
|
||||||
div.appendChild(E('input', {
|
|
||||||
type: 'hidden',
|
|
||||||
name: s.hasAttribute('name') ? s.getAttribute('name') : (sb.getAttribute('name') || ''),
|
|
||||||
value: v.value
|
|
||||||
}));
|
|
||||||
|
|
||||||
values.push(v);
|
|
||||||
|
|
||||||
strval += strval.length ? ' ' + v.value : v.value;
|
|
||||||
});
|
|
||||||
|
|
||||||
var detail = {
|
|
||||||
instance: this,
|
|
||||||
element: sb
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.multi)
|
|
||||||
detail.values = values;
|
|
||||||
else
|
|
||||||
detail.value = values.length ? values[0] : null;
|
|
||||||
|
|
||||||
sb.value = strval;
|
|
||||||
|
|
||||||
sb.dispatchEvent(new CustomEvent('cbi-dropdown-change', {
|
|
||||||
bubbles: true,
|
|
||||||
detail: detail
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
setValues: function(sb, values) {
|
|
||||||
var ul = sb.querySelector('ul');
|
|
||||||
|
|
||||||
if (this.multi) {
|
|
||||||
ul.querySelectorAll('li[data-value]').forEach(function(li) {
|
|
||||||
if (values === null || !(li.getAttribute('data-value') in values))
|
|
||||||
this.toggleItem(sb, li, false);
|
|
||||||
else
|
|
||||||
this.toggleItem(sb, li, true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var ph = ul.querySelector('li[placeholder]');
|
|
||||||
if (ph)
|
|
||||||
this.toggleItem(sb, ph);
|
|
||||||
|
|
||||||
ul.querySelectorAll('li[data-value]').forEach(function(li) {
|
|
||||||
if (values !== null && (li.getAttribute('data-value') in values))
|
|
||||||
this.toggleItem(sb, li);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setFocus: function(sb, elem, scroll) {
|
|
||||||
if (sb && sb.hasAttribute && sb.hasAttribute('locked-in'))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (sb.target && findParent(sb.target, 'ul.dropdown'))
|
|
||||||
return;
|
|
||||||
|
|
||||||
document.querySelectorAll('.focus').forEach(function(e) {
|
|
||||||
if (!matchesElem(e, 'input')) {
|
|
||||||
e.classList.remove('focus');
|
|
||||||
e.blur();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (elem) {
|
|
||||||
elem.focus();
|
|
||||||
elem.classList.add('focus');
|
|
||||||
|
|
||||||
if (scroll)
|
|
||||||
elem.parentNode.scrollTop = elem.offsetTop - elem.parentNode.offsetTop;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
createItems: function(sb, value) {
|
|
||||||
var sbox = this,
|
|
||||||
val = (value || '').trim(),
|
|
||||||
ul = sb.querySelector('ul');
|
|
||||||
|
|
||||||
if (!sbox.multi)
|
|
||||||
val = val.length ? [ val ] : [];
|
|
||||||
else
|
|
||||||
val = val.length ? val.split(/\s+/) : [];
|
|
||||||
|
|
||||||
val.forEach(function(item) {
|
|
||||||
var new_item = null;
|
|
||||||
|
|
||||||
ul.childNodes.forEach(function(li) {
|
|
||||||
if (li.getAttribute && li.getAttribute('data-value') === item)
|
|
||||||
new_item = li;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!new_item) {
|
|
||||||
var markup,
|
|
||||||
tpl = sb.querySelector(sbox.template);
|
|
||||||
|
|
||||||
if (tpl)
|
|
||||||
markup = (tpl.textContent || tpl.innerHTML || tpl.firstChild.data).replace(/^<!--|-->$/, '').trim();
|
|
||||||
else
|
|
||||||
markup = '<li data-value="{{value}}">{{value}}</li>';
|
|
||||||
|
|
||||||
new_item = E(markup.replace(/{{value}}/g, item));
|
|
||||||
|
|
||||||
if (sbox.multi) {
|
|
||||||
sbox.transformItem(sb, new_item);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var old = ul.querySelector('li[created]');
|
|
||||||
if (old)
|
|
||||||
ul.removeChild(old);
|
|
||||||
|
|
||||||
new_item.setAttribute('created', '');
|
|
||||||
}
|
|
||||||
|
|
||||||
new_item = ul.insertBefore(new_item, ul.lastElementChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
sbox.toggleItem(sb, new_item, true);
|
|
||||||
sbox.setFocus(sb, new_item, true);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
closeAllDropdowns: function() {
|
|
||||||
document.querySelectorAll('.cbi-dropdown[open]').forEach(function(s) {
|
|
||||||
s.dispatchEvent(new CustomEvent('cbi-dropdown-close', {}));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
handleClick: function(ev) {
|
|
||||||
var sb = ev.currentTarget;
|
|
||||||
|
|
||||||
if (!sb.hasAttribute('open')) {
|
|
||||||
if (!matchesElem(ev.target, 'input'))
|
|
||||||
this.openDropdown(sb);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var li = findParent(ev.target, 'li');
|
|
||||||
if (li && li.parentNode.classList.contains('dropdown'))
|
|
||||||
this.toggleItem(sb, li);
|
|
||||||
else if (li && li.parentNode.classList.contains('preview'))
|
|
||||||
this.closeDropdown(sb);
|
|
||||||
}
|
|
||||||
|
|
||||||
ev.preventDefault();
|
|
||||||
ev.stopPropagation();
|
|
||||||
},
|
|
||||||
|
|
||||||
handleKeydown: function(ev) {
|
|
||||||
var sb = ev.currentTarget;
|
|
||||||
|
|
||||||
if (matchesElem(ev.target, 'input'))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!sb.hasAttribute('open')) {
|
|
||||||
switch (ev.keyCode) {
|
|
||||||
case 37:
|
|
||||||
case 38:
|
|
||||||
case 39:
|
|
||||||
case 40:
|
|
||||||
this.openDropdown(sb);
|
|
||||||
ev.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var active = findParent(document.activeElement, 'li');
|
|
||||||
|
|
||||||
switch (ev.keyCode) {
|
|
||||||
case 27:
|
|
||||||
this.closeDropdown(sb);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 13:
|
|
||||||
if (active) {
|
|
||||||
if (!active.hasAttribute('selected'))
|
|
||||||
this.toggleItem(sb, active);
|
|
||||||
this.closeDropdown(sb);
|
|
||||||
ev.preventDefault();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 32:
|
|
||||||
if (active) {
|
|
||||||
this.toggleItem(sb, active);
|
|
||||||
ev.preventDefault();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 38:
|
|
||||||
if (active && active.previousElementSibling) {
|
|
||||||
this.setFocus(sb, active.previousElementSibling);
|
|
||||||
ev.preventDefault();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 40:
|
|
||||||
if (active && active.nextElementSibling) {
|
|
||||||
this.setFocus(sb, active.nextElementSibling);
|
|
||||||
ev.preventDefault();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
handleDropdownClose: function(ev) {
|
|
||||||
var sb = ev.currentTarget;
|
|
||||||
|
|
||||||
this.closeDropdown(sb, true);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleDropdownSelect: function(ev) {
|
|
||||||
var sb = ev.currentTarget,
|
|
||||||
li = findParent(ev.target, 'li');
|
|
||||||
|
|
||||||
if (!li)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.toggleItem(sb, li);
|
|
||||||
this.closeDropdown(sb, true);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleMouseover: function(ev) {
|
|
||||||
var sb = ev.currentTarget;
|
|
||||||
|
|
||||||
if (!sb.hasAttribute('open'))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var li = findParent(ev.target, 'li');
|
|
||||||
|
|
||||||
if (li && li.parentNode.classList.contains('dropdown'))
|
|
||||||
this.setFocus(sb, li);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleFocus: function(ev) {
|
|
||||||
var sb = ev.currentTarget;
|
|
||||||
|
|
||||||
document.querySelectorAll('.cbi-dropdown[open]').forEach(function(s) {
|
|
||||||
if (s !== sb || sb.hasAttribute('open'))
|
|
||||||
s.dispatchEvent(new CustomEvent('cbi-dropdown-close', {}));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
handleCanaryFocus: function(ev) {
|
|
||||||
this.closeDropdown(ev.currentTarget.parentNode);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleCreateKeydown: function(ev) {
|
|
||||||
var input = ev.currentTarget,
|
|
||||||
sb = findParent(input, '.cbi-dropdown');
|
|
||||||
|
|
||||||
switch (ev.keyCode) {
|
|
||||||
case 13:
|
|
||||||
ev.preventDefault();
|
|
||||||
|
|
||||||
if (input.classList.contains('cbi-input-invalid'))
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.createItems(sb, input.value);
|
|
||||||
input.value = '';
|
|
||||||
input.blur();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
handleCreateFocus: function(ev) {
|
|
||||||
var input = ev.currentTarget,
|
|
||||||
cbox = findParent(input, 'li').querySelector('input[type="checkbox"]'),
|
|
||||||
sb = findParent(input, '.cbi-dropdown');
|
|
||||||
|
|
||||||
if (cbox)
|
|
||||||
cbox.checked = true;
|
|
||||||
|
|
||||||
sb.setAttribute('locked-in', '');
|
|
||||||
},
|
|
||||||
|
|
||||||
handleCreateBlur: function(ev) {
|
|
||||||
var input = ev.currentTarget,
|
|
||||||
cbox = findParent(input, 'li').querySelector('input[type="checkbox"]'),
|
|
||||||
sb = findParent(input, '.cbi-dropdown');
|
|
||||||
|
|
||||||
if (cbox)
|
|
||||||
cbox.checked = false;
|
|
||||||
|
|
||||||
sb.removeAttribute('locked-in');
|
|
||||||
},
|
|
||||||
|
|
||||||
handleCreateClick: function(ev) {
|
|
||||||
ev.currentTarget.querySelector(this.create).focus();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function cbi_dropdown_init(sb) {
|
function cbi_dropdown_init(sb) {
|
||||||
if (!(this instanceof cbi_dropdown_init))
|
var dl = new L.ui.Dropdown(sb, null, { name: sb.getAttribute('name') });
|
||||||
return new cbi_dropdown_init(sb);
|
return dl.bind(sb);
|
||||||
|
|
||||||
this.multi = sb.hasAttribute('multiple');
|
|
||||||
this.optional = sb.hasAttribute('optional');
|
|
||||||
this.placeholder = sb.getAttribute('placeholder') || '---';
|
|
||||||
this.display_items = parseInt(sb.getAttribute('display-items') || 3);
|
|
||||||
this.dropdown_items = parseInt(sb.getAttribute('dropdown-items') || 5);
|
|
||||||
this.create = sb.getAttribute('item-create') || '.create-item-input';
|
|
||||||
this.template = sb.getAttribute('item-template') || 'script[type="item-template"]';
|
|
||||||
|
|
||||||
var ul = sb.querySelector('ul'),
|
|
||||||
more = sb.appendChild(E('span', { class: 'more', tabindex: -1 }, '···')),
|
|
||||||
open = sb.appendChild(E('span', { class: 'open', tabindex: -1 }, '▾')),
|
|
||||||
canary = sb.appendChild(E('div')),
|
|
||||||
create = sb.querySelector(this.create),
|
|
||||||
ndisplay = this.display_items,
|
|
||||||
n = 0;
|
|
||||||
|
|
||||||
if (this.multi) {
|
|
||||||
var items = ul.querySelectorAll('li');
|
|
||||||
|
|
||||||
for (var i = 0; i < items.length; i++) {
|
|
||||||
this.transformItem(sb, items[i]);
|
|
||||||
|
|
||||||
if (items[i].hasAttribute('selected') && ndisplay-- > 0)
|
|
||||||
items[i].setAttribute('display', n++);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (this.optional && !ul.querySelector('li[data-value=""]')) {
|
|
||||||
var placeholder = E('li', { placeholder: '' }, this.placeholder);
|
|
||||||
ul.firstChild ? ul.insertBefore(placeholder, ul.firstChild) : ul.appendChild(placeholder);
|
|
||||||
}
|
|
||||||
|
|
||||||
var items = ul.querySelectorAll('li'),
|
|
||||||
sel = sb.querySelectorAll('[selected]');
|
|
||||||
|
|
||||||
sel.forEach(function(s) {
|
|
||||||
s.removeAttribute('selected');
|
|
||||||
});
|
|
||||||
|
|
||||||
var s = sel[0] || items[0];
|
|
||||||
if (s) {
|
|
||||||
s.setAttribute('selected', '');
|
|
||||||
s.setAttribute('display', n++);
|
|
||||||
}
|
|
||||||
|
|
||||||
ndisplay--;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.saveValues(sb, ul);
|
|
||||||
|
|
||||||
ul.setAttribute('tabindex', -1);
|
|
||||||
sb.setAttribute('tabindex', 0);
|
|
||||||
|
|
||||||
if (ndisplay < 0)
|
|
||||||
sb.setAttribute('more', '')
|
|
||||||
else
|
|
||||||
sb.removeAttribute('more');
|
|
||||||
|
|
||||||
if (ndisplay === this.display_items)
|
|
||||||
sb.setAttribute('empty', '')
|
|
||||||
else
|
|
||||||
sb.removeAttribute('empty');
|
|
||||||
|
|
||||||
more.innerHTML = (ndisplay === this.display_items) ? this.placeholder : '···';
|
|
||||||
|
|
||||||
|
|
||||||
sb.addEventListener('click', this.handleClick.bind(this));
|
|
||||||
sb.addEventListener('keydown', this.handleKeydown.bind(this));
|
|
||||||
sb.addEventListener('cbi-dropdown-close', this.handleDropdownClose.bind(this));
|
|
||||||
sb.addEventListener('cbi-dropdown-select', this.handleDropdownSelect.bind(this));
|
|
||||||
|
|
||||||
if ('ontouchstart' in window) {
|
|
||||||
sb.addEventListener('touchstart', function(ev) { ev.stopPropagation(); });
|
|
||||||
window.addEventListener('touchstart', this.closeAllDropdowns);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
sb.addEventListener('mouseover', this.handleMouseover.bind(this));
|
|
||||||
sb.addEventListener('focus', this.handleFocus.bind(this));
|
|
||||||
|
|
||||||
canary.addEventListener('focus', this.handleCanaryFocus.bind(this));
|
|
||||||
|
|
||||||
window.addEventListener('mouseover', this.setFocus);
|
|
||||||
window.addEventListener('click', this.closeAllDropdowns);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (create) {
|
|
||||||
create.addEventListener('keydown', this.handleCreateKeydown.bind(this));
|
|
||||||
create.addEventListener('focus', this.handleCreateFocus.bind(this));
|
|
||||||
create.addEventListener('blur', this.handleCreateBlur.bind(this));
|
|
||||||
|
|
||||||
var li = findParent(create, 'li');
|
|
||||||
|
|
||||||
li.setAttribute('unselectable', '');
|
|
||||||
li.addEventListener('click', this.handleCreateClick.bind(this));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cbi_dropdown_init.prototype = CBIDropdown;
|
|
||||||
|
|
||||||
function cbi_update_table(table, data, placeholder) {
|
function cbi_update_table(table, data, placeholder) {
|
||||||
var target = isElem(table) ? table : document.querySelector(table);
|
var target = isElem(table) ? table : document.querySelector(table);
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue