Commit 75462cba authored by Jacob Schatz's avatar Jacob Schatz

Merge branch 'update-droplab-to-webpack-version' into 'master'

Update droplab

See merge request !9444
parents 780a8968 dab60475
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
"plugins": [ "plugins": [
["istanbul", { ["istanbul", {
"exclude": [ "exclude": [
"app/assets/javascripts/droplab/**/*",
"spec/javascripts/**/*" "spec/javascripts/**/*"
] ]
}], }],
......
const DATA_TRIGGER = 'data-dropdown-trigger';
const DATA_DROPDOWN = 'data-dropdown';
const SELECTED_CLASS = 'droplab-item-selected';
const ACTIVE_CLASS = 'droplab-item-active';
export {
DATA_TRIGGER,
DATA_DROPDOWN,
SELECTED_CLASS,
ACTIVE_CLASS,
};
/* eslint-disable */
import utils from './utils';
import { SELECTED_CLASS } from './constants';
var DropDown = function(list) {
this.currentIndex = 0;
this.hidden = true;
this.list = typeof list === 'string' ? document.querySelector(list) : list;
this.items = [];
this.eventWrapper = {};
this.getItems();
this.initTemplateString();
this.addEvents();
this.initialState = list.innerHTML;
};
Object.assign(DropDown.prototype, {
getItems: function() {
this.items = [].slice.call(this.list.querySelectorAll('li'));
return this.items;
},
initTemplateString: function() {
var items = this.items || this.getItems();
var templateString = '';
if (items.length > 0) templateString = items[items.length - 1].outerHTML;
this.templateString = templateString;
return this.templateString;
},
clickEvent: function(e) {
var selected = utils.closest(e.target, 'LI');
if (!selected) return;
this.addSelectedClass(selected);
e.preventDefault();
this.hide();
var listEvent = new CustomEvent('click.dl', {
detail: {
list: this,
selected: selected,
data: e.target.dataset,
},
});
this.list.dispatchEvent(listEvent);
},
addSelectedClass: function (selected) {
this.removeSelectedClasses();
selected.classList.add(SELECTED_CLASS);
},
removeSelectedClasses: function () {
const items = this.items || this.getItems();
items.forEach(item => item.classList.remove(SELECTED_CLASS));
},
addEvents: function() {
this.eventWrapper.clickEvent = this.clickEvent.bind(this)
this.list.addEventListener('click', this.eventWrapper.clickEvent);
},
toggle: function() {
this.hidden ? this.show() : this.hide();
},
setData: function(data) {
this.data = data;
this.render(data);
},
addData: function(data) {
this.data = (this.data || []).concat(data);
this.render(this.data);
},
render: function(data) {
const children = data ? data.map(this.renderChildren.bind(this)) : [];
const renderableList = this.list.querySelector('ul[data-dynamic]') || this.list;
renderableList.innerHTML = children.join('');
},
renderChildren: function(data) {
var html = utils.t(this.templateString, data);
var template = document.createElement('div');
template.innerHTML = html;
this.setImagesSrc(template);
template.firstChild.style.display = data.droplab_hidden ? 'none' : 'block';
return template.firstChild.outerHTML;
},
setImagesSrc: function(template) {
const images = [].slice.call(template.querySelectorAll('img[data-src]'));
images.forEach((image) => {
image.src = image.getAttribute('data-src');
image.removeAttribute('data-src');
});
},
show: function() {
if (!this.hidden) return;
this.list.style.display = 'block';
this.currentIndex = 0;
this.hidden = false;
},
hide: function() {
if (this.hidden) return;
this.list.style.display = 'none';
this.currentIndex = 0;
this.hidden = true;
},
toggle: function () {
this.hidden ? this.show() : this.hide();
},
destroy: function() {
this.hide();
this.list.removeEventListener('click', this.eventWrapper.clickEvent);
}
});
export default DropDown;
/* eslint-disable */
import HookButton from './hook_button';
import HookInput from './hook_input';
import utils from './utils';
import Keyboard from './keyboard';
import { DATA_TRIGGER } from './constants';
var DropLab = function() {
this.ready = false;
this.hooks = [];
this.queuedData = [];
this.config = {};
this.eventWrapper = {};
};
Object.assign(DropLab.prototype, {
loadStatic: function(){
var dropdownTriggers = [].slice.apply(document.querySelectorAll(`[${DATA_TRIGGER}]`));
this.addHooks(dropdownTriggers);
},
addData: function () {
var args = [].slice.apply(arguments);
this.applyArgs(args, '_addData');
},
setData: function() {
var args = [].slice.apply(arguments);
this.applyArgs(args, '_setData');
},
destroy: function() {
this.hooks.forEach(hook => hook.destroy());
this.hooks = [];
this.removeEvents();
},
applyArgs: function(args, methodName) {
if (this.ready) return this[methodName].apply(this, args);
this.queuedData = this.queuedData || [];
this.queuedData.push(args);
},
_addData: function(trigger, data) {
this._processData(trigger, data, 'addData');
},
_setData: function(trigger, data) {
this._processData(trigger, data, 'setData');
},
_processData: function(trigger, data, methodName) {
this.hooks.forEach((hook) => {
if (Array.isArray(trigger)) hook.list[methodName](trigger);
if (hook.trigger.id === trigger) hook.list[methodName](data);
});
},
addEvents: function() {
this.eventWrapper.documentClicked = this.documentClicked.bind(this)
document.addEventListener('click', this.eventWrapper.documentClicked);
},
documentClicked: function(e) {
let thisTag = e.target;
if (thisTag.tagName !== 'UL') thisTag = utils.closest(thisTag, 'UL');
if (utils.isDropDownParts(thisTag, this.hooks) || utils.isDropDownParts(e.target, this.hooks)) return;
this.hooks.forEach(hook => hook.list.hide());
},
removeEvents: function(){
document.removeEventListener('click', this.eventWrapper.documentClicked);
},
changeHookList: function(trigger, list, plugins, config) {
const availableTrigger = typeof trigger === 'string' ? document.getElementById(trigger) : trigger;
this.hooks.forEach((hook, i) => {
hook.list.list.dataset.dropdownActive = false;
if (hook.trigger !== availableTrigger) return;
hook.destroy();
this.hooks.splice(i, 1);
this.addHook(availableTrigger, list, plugins, config);
});
},
addHook: function(hook, list, plugins, config) {
const availableHook = typeof hook === 'string' ? document.querySelector(hook) : hook;
let availableList;
if (typeof list === 'string') {
availableList = document.querySelector(list);
} else if (list instanceof Element) {
availableList = list;
} else {
availableList = document.querySelector(hook.dataset[utils.toCamelCase(DATA_TRIGGER)]);
}
availableList.dataset.dropdownActive = true;
const HookObject = availableHook.tagName === 'INPUT' ? HookInput : HookButton;
this.hooks.push(new HookObject(availableHook, availableList, plugins, config));
return this;
},
addHooks: function(hooks, plugins, config) {
hooks.forEach(hook => this.addHook(hook, null, plugins, config));
return this;
},
setConfig: function(obj){
this.config = obj;
},
fireReady: function() {
const readyEvent = new CustomEvent('ready.dl', {
detail: {
dropdown: this,
},
});
document.dispatchEvent(readyEvent);
this.ready = true;
},
init: function (hook, list, plugins, config) {
hook ? this.addHook(hook, list, plugins, config) : this.loadStatic();
this.addEvents();
Keyboard();
this.fireReady();
this.queuedData.forEach(data => this.addData(data));
this.queuedData = [];
return this;
},
});
export default DropLab;
This diff is collapsed.
/* eslint-disable */
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g=(g.droplab||(g.droplab = {}));g=(g.ajax||(g.ajax = {}));g=(g.datasource||(g.datasource = {}));g.js = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/* global droplab */
require('../window')(function(w){
function droplabAjaxException(message) {
this.message = message;
}
w.droplabAjax = {
_loadUrlData: function _loadUrlData(url) {
var self = this;
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest;
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if(xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
self.cache[url] = data;
return resolve(data);
} else {
return reject([xhr.responseText, xhr.status]);
}
}
};
xhr.send();
});
},
_loadData: function _loadData(data, config, self) {
if (config.loadingTemplate) {
var dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]');
if (dataLoadingTemplate) {
dataLoadingTemplate.outerHTML = self.listTemplate;
}
}
if (!self.destroyed) {
self.hook.list[config.method].call(self.hook.list, data);
}
},
init: function init(hook) {
var self = this;
self.destroyed = false;
self.cache = self.cache || {};
var config = hook.config.droplabAjax;
this.hook = hook;
if (!config || !config.endpoint || !config.method) {
return;
}
if (config.method !== 'setData' && config.method !== 'addData') {
return;
}
if (config.loadingTemplate) {
var dynamicList = hook.list.list.querySelector('[data-dynamic]');
var loadingTemplate = document.createElement('div');
loadingTemplate.innerHTML = config.loadingTemplate;
loadingTemplate.setAttribute('data-loading-template', '');
this.listTemplate = dynamicList.outerHTML;
dynamicList.outerHTML = loadingTemplate.outerHTML;
}
if (self.cache[config.endpoint]) {
self._loadData(self.cache[config.endpoint], config, self);
} else {
this._loadUrlData(config.endpoint)
.then(function(d) {
self._loadData(d, config, self);
}, function(xhrError) {
// TODO: properly handle errors due to XHR cancellation
return;
}).catch(function(e) {
throw new droplabAjaxException(e.message || e);
});
}
},
destroy: function() {
var dynamicList = this.hook.list.list.querySelector('[data-dynamic]');
this.destroyed = true;
if (this.listTemplate && dynamicList) {
dynamicList.outerHTML = this.listTemplate;
}
}
};
});
},{"../window":2}],2:[function(require,module,exports){
module.exports = function(callback) {
return (function() {
callback(this);
}).call(null);
};
},{}]},{},[1])(1)
});
/* eslint-disable */
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g=(g.droplab||(g.droplab = {}));g=(g.ajax||(g.ajax = {}));g=(g.datasource||(g.datasource = {}));g.js = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/* global droplab */
require('../window')(function(w){
w.droplabAjaxFilter = {
init: function(hook) {
this.destroyed = false;
this.hook = hook;
this.notLoading();
this.debounceTriggerWrapper = this.debounceTrigger.bind(this);
this.hook.trigger.addEventListener('keydown.dl', this.debounceTriggerWrapper);
this.hook.trigger.addEventListener('focus', this.debounceTriggerWrapper);
this.trigger(true);
},
notLoading: function notLoading() {
this.loading = false;
},
debounceTrigger: function debounceTrigger(e) {
var NON_CHARACTER_KEYS = [16, 17, 18, 20, 37, 38, 39, 40, 91, 93];
var invalidKeyPressed = NON_CHARACTER_KEYS.indexOf(e.detail.which || e.detail.keyCode) > -1;
var focusEvent = e.type === 'focus';
if (invalidKeyPressed || this.loading) {
return;
}
if (this.timeout) {
clearTimeout(this.timeout);
}
this.timeout = setTimeout(this.trigger.bind(this, focusEvent), 200);
},
trigger: function trigger(getEntireList) {
var config = this.hook.config.droplabAjaxFilter;
var searchValue = this.trigger.value;
if (!config || !config.endpoint || !config.searchKey) {
return;
}
if (config.searchValueFunction) {
searchValue = config.searchValueFunction();
}
if (config.loadingTemplate && this.hook.list.data === undefined ||
this.hook.list.data.length === 0) {
var dynamicList = this.hook.list.list.querySelector('[data-dynamic]');
var loadingTemplate = document.createElement('div');
loadingTemplate.innerHTML = config.loadingTemplate;
loadingTemplate.setAttribute('data-loading-template', true);
this.listTemplate = dynamicList.outerHTML;
dynamicList.outerHTML = loadingTemplate.outerHTML;
}
if (getEntireList) {
searchValue = '';
}
if (config.searchKey === searchValue) {
return this.list.show();
}
this.loading = true;
var params = config.params || {};
params[config.searchKey] = searchValue;
var self = this;
self.cache = self.cache || {};
var url = config.endpoint + this.buildParams(params);
var urlCachedData = self.cache[url];
if (urlCachedData) {
self._loadData(urlCachedData, config, self);
} else {
this._loadUrlData(url)
.then(function(data) {
self._loadData(data, config, self);
}, function(xhrError) {
// TODO: properly handle errors due to XHR cancellation
return;
});
}
},
_loadUrlData: function _loadUrlData(url) {
var self = this;
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest;
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if(xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
self.cache[url] = data;
return resolve(data);
} else {
return reject([xhr.responseText, xhr.status]);
}
}
};
xhr.send();
});
},
_loadData: function _loadData(data, config, self) {
if (config.loadingTemplate && self.hook.list.data === undefined ||
self.hook.list.data.length === 0) {
const dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]');
if (dataLoadingTemplate) {
dataLoadingTemplate.outerHTML = self.listTemplate;
}
}
if (!self.destroyed) {
var hookListChildren = self.hook.list.list.children;
var onlyDynamicList = hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic');
if (onlyDynamicList && data.length === 0) {
self.hook.list.hide();
}
self.hook.list.setData.call(self.hook.list, data);
}
self.notLoading();
self.hook.list.currentIndex = 0;
},
buildParams: function(params) {
if (!params) return '';
var paramsArray = Object.keys(params).map(function(param) {
return param + '=' + (params[param] || '');
});
return '?' + paramsArray.join('&');
},
destroy: function destroy() {
if (this.timeout) {
clearTimeout(this.timeout);
}
this.destroyed = true;
this.hook.trigger.removeEventListener('keydown.dl', this.debounceTriggerWrapper);
this.hook.trigger.removeEventListener('focus', this.debounceTriggerWrapper);
}
};
});
},{"../window":2}],2:[function(require,module,exports){
module.exports = function(callback) {
return (function() {
callback(this);
}).call(null);
};
},{}]},{},[1])(1)
});
/* eslint-disable */
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g=(g.droplab||(g.droplab = {}));g=(g.filter||(g.filter = {}));g.js = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/* global droplab */
require('../window')(function(w){
w.droplabFilter = {
keydownWrapper: function(e){
var hiddenCount = 0;
var dataHiddenCount = 0;
var list = e.detail.hook.list;
var data = list.data;
var value = e.detail.hook.trigger.value.toLowerCase();
var config = e.detail.hook.config.droplabFilter;
var matches = [];
var filterFunction;
// will only work on dynamically set data
if(!data){
return;
}
if (config && config.filterFunction && typeof config.filterFunction === 'function') {
filterFunction = config.filterFunction;
} else {
filterFunction = function(o){
// cheap string search
o.droplab_hidden = o[config.template].toLowerCase().indexOf(value) === -1;
return o;
};
}
dataHiddenCount = data.filter(function(o) {
return !o.droplab_hidden;
}).length;
matches = data.map(function(o) {
return filterFunction(o, value);
});
hiddenCount = matches.filter(function(o) {
return !o.droplab_hidden;
}).length;
if (dataHiddenCount !== hiddenCount) {
list.render(matches);
list.currentIndex = 0;
}
},
init: function init(hookInput) {
var config = hookInput.config.droplabFilter;
if (!config || (!config.template && !config.filterFunction)) {
return;
}
this.hookInput = hookInput;
this.hookInput.trigger.addEventListener('keyup.dl', this.keydownWrapper);
this.hookInput.trigger.addEventListener('mousedown.dl', this.keydownWrapper);
},
destroy: function destroy(){
this.hookInput.trigger.removeEventListener('keyup.dl', this.keydownWrapper);
this.hookInput.trigger.removeEventListener('mousedown.dl', this.keydownWrapper);
}
};
});
},{"../window":2}],2:[function(require,module,exports){
module.exports = function(callback) {
return (function() {
callback(this);
}).call(null);
};
},{}]},{},[1])(1)
});
/* eslint-disable */
import DropDown from './drop_down';
var Hook = function(trigger, list, plugins, config){
this.trigger = trigger;
this.list = new DropDown(list);
this.type = 'Hook';
this.event = 'click';
this.plugins = plugins || [];
this.config = config || {};
this.id = trigger.id;
};
Object.assign(Hook.prototype, {
addEvents: function(){},
constructor: Hook,
});
export default Hook;
/* eslint-disable */
import Hook from './hook';
var HookButton = function(trigger, list, plugins, config) {
Hook.call(this, trigger, list, plugins, config);
this.type = 'button';
this.event = 'click';
this.eventWrapper = {};
this.addEvents();
this.addPlugins();
};
HookButton.prototype = Object.create(Hook.prototype);
Object.assign(HookButton.prototype, {
addPlugins: function() {
this.plugins.forEach(plugin => plugin.init(this));
},
clicked: function(e){
var buttonEvent = new CustomEvent('click.dl', {
detail: {
hook: this,
},
bubbles: true,
cancelable: true
});
e.target.dispatchEvent(buttonEvent);
this.list.toggle();
},
addEvents: function(){
this.eventWrapper.clicked = this.clicked.bind(this);
this.trigger.addEventListener('click', this.eventWrapper.clicked);
},
removeEvents: function(){
this.trigger.removeEventListener('click', this.eventWrapper.clicked);
},
restoreInitialState: function() {
this.list.list.innerHTML = this.list.initialState;
},
removePlugins: function() {
this.plugins.forEach(plugin => plugin.destroy());
},
destroy: function() {
this.restoreInitialState();
this.removeEvents();
this.removePlugins();
},
constructor: HookButton,
});
export default HookButton;
/* eslint-disable */
import Hook from './hook';
var HookInput = function(trigger, list, plugins, config) {
Hook.call(this, trigger, list, plugins, config);
this.type = 'input';
this.event = 'input';
this.eventWrapper = {};
this.addEvents();
this.addPlugins();
};
Object.assign(HookInput.prototype, {
addPlugins: function() {
this.plugins.forEach(plugin => plugin.init(this));
},
addEvents: function(){
this.eventWrapper.mousedown = this.mousedown.bind(this);
this.eventWrapper.input = this.input.bind(this);
this.eventWrapper.keyup = this.keyup.bind(this);
this.eventWrapper.keydown = this.keydown.bind(this);
this.trigger.addEventListener('mousedown', this.eventWrapper.mousedown);
this.trigger.addEventListener('input', this.eventWrapper.input);
this.trigger.addEventListener('keyup', this.eventWrapper.keyup);
this.trigger.addEventListener('keydown', this.eventWrapper.keydown);
},
removeEvents: function() {
this.hasRemovedEvents = true;
this.trigger.removeEventListener('mousedown', this.eventWrapper.mousedown);
this.trigger.removeEventListener('input', this.eventWrapper.input);
this.trigger.removeEventListener('keyup', this.eventWrapper.keyup);
this.trigger.removeEventListener('keydown', this.eventWrapper.keydown);
},
input: function(e) {
if(this.hasRemovedEvents) return;
this.list.show();
const inputEvent = new CustomEvent('input.dl', {
detail: {
hook: this,
text: e.target.value,
},
bubbles: true,
cancelable: true
});
e.target.dispatchEvent(inputEvent);
},
mousedown: function(e) {
if (this.hasRemovedEvents) return;
const mouseEvent = new CustomEvent('mousedown.dl', {
detail: {
hook: this,
text: e.target.value,
},
bubbles: true,
cancelable: true,
});
e.target.dispatchEvent(mouseEvent);
},
keyup: function(e) {
if (this.hasRemovedEvents) return;
this.keyEvent(e, 'keyup.dl');
},
keydown: function(e) {
if (this.hasRemovedEvents) return;
this.keyEvent(e, 'keydown.dl');
},
keyEvent: function(e, eventName) {
this.list.show();
const keyEvent = new CustomEvent(eventName, {
detail: {
hook: this,
text: e.target.value,
which: e.which,
key: e.key,
},
bubbles: true,
cancelable: true,
});
e.target.dispatchEvent(keyEvent);
},
restoreInitialState: function() {
this.list.list.innerHTML = this.list.initialState;
},
removePlugins: function() {
this.plugins.forEach(plugin => plugin.destroy());
},
destroy: function() {
this.restoreInitialState();
this.removeEvents();
this.removePlugins();
this.list.destroy();
}
});
export default HookInput;
/* eslint-disable */
import { ACTIVE_CLASS } from './constants';
const Keyboard = function () {
var currentKey;
var currentFocus;
var isUpArrow = false;
var isDownArrow = false;
var removeHighlight = function removeHighlight(list) {
var itemElements = Array.prototype.slice.call(list.list.querySelectorAll('li:not(.divider)'), 0);
var listItems = [];
for(var i = 0; i < itemElements.length; i++) {
var listItem = itemElements[i];
listItem.classList.remove(ACTIVE_CLASS);
if (listItem.style.display !== 'none') {
listItems.push(listItem);
}
}
return listItems;
};
var setMenuForArrows = function setMenuForArrows(list) {
var listItems = removeHighlight(list);
if(list.currentIndex>0){
if(!listItems[list.currentIndex-1]){
list.currentIndex = list.currentIndex-1;
}
if (listItems[list.currentIndex-1]) {
var el = listItems[list.currentIndex-1];
var filterDropdownEl = el.closest('.filter-dropdown');
el.classList.add(ACTIVE_CLASS);
if (filterDropdownEl) {
var filterDropdownBottom = filterDropdownEl.offsetHeight;
var elOffsetTop = el.offsetTop - 30;
if (elOffsetTop > filterDropdownBottom) {
filterDropdownEl.scrollTop = elOffsetTop - filterDropdownBottom;
}
}
}
}
};
var mousedown = function mousedown(e) {
var list = e.detail.hook.list;
removeHighlight(list);
list.show();
list.currentIndex = 0;
isUpArrow = false;
isDownArrow = false;
};
var selectItem = function selectItem(list) {
var listItems = removeHighlight(list);
var currentItem = listItems[list.currentIndex-1];
var listEvent = new CustomEvent('click.dl', {
detail: {
list: list,
selected: currentItem,
data: currentItem.dataset,
},
});
list.list.dispatchEvent(listEvent);
list.hide();
}
var keydown = function keydown(e){
var typedOn = e.target;
var list = e.detail.hook.list;
var currentIndex = list.currentIndex;
isUpArrow = false;
isDownArrow = false;
if(e.detail.which){
currentKey = e.detail.which;
if(currentKey === 13){
selectItem(e.detail.hook.list);
return;
}
if(currentKey === 38) {
isUpArrow = true;
}
if(currentKey === 40) {
isDownArrow = true;
}
} else if(e.detail.key) {
currentKey = e.detail.key;
if(currentKey === 'Enter'){
selectItem(e.detail.hook.list);
return;
}
if(currentKey === 'ArrowUp') {
isUpArrow = true;
}
if(currentKey === 'ArrowDown') {
isDownArrow = true;
}
}
if(isUpArrow){ currentIndex--; }
if(isDownArrow){ currentIndex++; }
if(currentIndex < 0){ currentIndex = 0; }
list.currentIndex = currentIndex;
setMenuForArrows(e.detail.hook.list);
};
document.addEventListener('mousedown.dl', mousedown);
document.addEventListener('keydown.dl', keydown);
};
export default Keyboard;
/* eslint-disable */
const Ajax = {
_loadUrlData: function _loadUrlData(url) {
var self = this;
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest;
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if(xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
self.cache[url] = data;
return resolve(data);
} else {
return reject([xhr.responseText, xhr.status]);
}
}
};
xhr.send();
});
},
_loadData: function _loadData(data, config, self) {
if (config.loadingTemplate) {
var dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]');
if (dataLoadingTemplate) dataLoadingTemplate.outerHTML = self.listTemplate;
}
if (!self.destroyed) self.hook.list[config.method].call(self.hook.list, data);
},
init: function init(hook) {
var self = this;
self.destroyed = false;
self.cache = self.cache || {};
var config = hook.config.Ajax;
this.hook = hook;
if (!config || !config.endpoint || !config.method) {
return;
}
if (config.method !== 'setData' && config.method !== 'addData') {
return;
}
if (config.loadingTemplate) {
var dynamicList = hook.list.list.querySelector('[data-dynamic]');
var loadingTemplate = document.createElement('div');
loadingTemplate.innerHTML = config.loadingTemplate;
loadingTemplate.setAttribute('data-loading-template', '');
this.listTemplate = dynamicList.outerHTML;
dynamicList.outerHTML = loadingTemplate.outerHTML;
}
if (self.cache[config.endpoint]) {
self._loadData(self.cache[config.endpoint], config, self);
} else {
this._loadUrlData(config.endpoint)
.then(function(d) {
self._loadData(d, config, self);
}, config.onError).catch(config.onError);
}
},
destroy: function() {
this.destroyed = true;
}
};
export default Ajax;
/* eslint-disable */
const AjaxFilter = {
init: function(hook) {
this.destroyed = false;
this.hook = hook;
this.notLoading();
this.eventWrapper = {};
this.eventWrapper.debounceTrigger = this.debounceTrigger.bind(this);
this.hook.trigger.addEventListener('keydown.dl', this.eventWrapper.debounceTrigger);
this.hook.trigger.addEventListener('focus', this.eventWrapper.debounceTrigger);
this.trigger(true);
},
notLoading: function notLoading() {
this.loading = false;
},
debounceTrigger: function debounceTrigger(e) {
var NON_CHARACTER_KEYS = [16, 17, 18, 20, 37, 38, 39, 40, 91, 93];
var invalidKeyPressed = NON_CHARACTER_KEYS.indexOf(e.detail.which || e.detail.keyCode) > -1;
var focusEvent = e.type === 'focus';
if (invalidKeyPressed || this.loading) {
return;
}
if (this.timeout) {
clearTimeout(this.timeout);
}
this.timeout = setTimeout(this.trigger.bind(this, focusEvent), 200);
},
trigger: function trigger(getEntireList) {
var config = this.hook.config.AjaxFilter;
var searchValue = this.trigger.value;
if (!config || !config.endpoint || !config.searchKey) {
return;
}
if (config.searchValueFunction) {
searchValue = config.searchValueFunction();
}
if (config.loadingTemplate && this.hook.list.data === undefined ||
this.hook.list.data.length === 0) {
var dynamicList = this.hook.list.list.querySelector('[data-dynamic]');
var loadingTemplate = document.createElement('div');
loadingTemplate.innerHTML = config.loadingTemplate;
loadingTemplate.setAttribute('data-loading-template', true);
this.listTemplate = dynamicList.outerHTML;
dynamicList.outerHTML = loadingTemplate.outerHTML;
}
if (getEntireList) {
searchValue = '';
}
if (config.searchKey === searchValue) {
return this.list.show();
}
this.loading = true;
var params = config.params || {};
params[config.searchKey] = searchValue;
var self = this;
self.cache = self.cache || {};
var url = config.endpoint + this.buildParams(params);
var urlCachedData = self.cache[url];
if (urlCachedData) {
self._loadData(urlCachedData, config, self);
} else {
this._loadUrlData(url)
.then(function(data) {
self._loadData(data, config, self);
}, config.onError).catch(config.onError);
}
},
_loadUrlData: function _loadUrlData(url) {
var self = this;
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest;
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if(xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
self.cache[url] = data;
return resolve(data);
} else {
return reject([xhr.responseText, xhr.status]);
}
}
};
xhr.send();
});
},
_loadData: function _loadData(data, config, self) {
const list = self.hook.list;
if (config.loadingTemplate && list.data === undefined ||
list.data.length === 0) {
const dataLoadingTemplate = list.list.querySelector('[data-loading-template]');
if (dataLoadingTemplate) {
dataLoadingTemplate.outerHTML = self.listTemplate;
}
}
if (!self.destroyed) {
var hookListChildren = list.list.children;
var onlyDynamicList = hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic');
if (onlyDynamicList && data.length === 0) {
list.hide();
}
list.setData.call(list, data);
}
self.notLoading();
list.currentIndex = 0;
},
buildParams: function(params) {
if (!params) return '';
var paramsArray = Object.keys(params).map(function(param) {
return param + '=' + (params[param] || '');
});
return '?' + paramsArray.join('&');
},
destroy: function destroy() {
if (this.timeout)clearTimeout(this.timeout);
this.destroyed = true;
this.hook.trigger.removeEventListener('keydown.dl', this.eventWrapper.debounceTrigger);
this.hook.trigger.removeEventListener('focus', this.eventWrapper.debounceTrigger);
}
};
export default AjaxFilter;
/* eslint-disable */
const Filter = {
keydown: function(e){
if (this.destroyed) return;
var hiddenCount = 0;
var dataHiddenCount = 0;
var list = e.detail.hook.list;
var data = list.data;
var value = e.detail.hook.trigger.value.toLowerCase();
var config = e.detail.hook.config.Filter;
var matches = [];
var filterFunction;
// will only work on dynamically set data
if(!data){
return;
}
if (config && config.filterFunction && typeof config.filterFunction === 'function') {
filterFunction = config.filterFunction;
} else {
filterFunction = function(o){
// cheap string search
o.droplab_hidden = o[config.template].toLowerCase().indexOf(value) === -1;
return o;
};
}
dataHiddenCount = data.filter(function(o) {
return !o.droplab_hidden;
}).length;
matches = data.map(function(o) {
return filterFunction(o, value);
});
hiddenCount = matches.filter(function(o) {
return !o.droplab_hidden;
}).length;
if (dataHiddenCount !== hiddenCount) {
list.setData(matches);
list.currentIndex = 0;
}
},
debounceKeydown: function debounceKeydown(e) {
if ([
13, // enter
16, // shift
17, // ctrl
18, // alt
20, // caps lock
37, // left arrow
38, // up arrow
39, // right arrow
40, // down arrow
91, // left window
92, // right window
93, // select
].indexOf(e.detail.which || e.detail.keyCode) > -1) return;
if (this.timeout) clearTimeout(this.timeout);
this.timeout = setTimeout(this.keydown.bind(this, e), 200);
},
init: function init(hook) {
var config = hook.config.Filter;
if (!config || !config.template) return;
this.hook = hook;
this.destroyed = false;
this.eventWrapper = {};
this.eventWrapper.debounceKeydown = this.debounceKeydown.bind(this);
this.hook.trigger.addEventListener('keydown.dl', this.eventWrapper.debounceKeydown);
this.hook.trigger.addEventListener('mousedown.dl', this.eventWrapper.debounceKeydown);
this.debounceKeydown({ detail: { hook: this.hook } });
},
destroy: function destroy() {
if (this.timeout) clearTimeout(this.timeout);
this.destroyed = true;
this.hook.trigger.removeEventListener('keydown.dl', this.eventWrapper.debounceKeydown);
this.hook.trigger.removeEventListener('mousedown.dl', this.eventWrapper.debounceKeydown);
}
};
export default Filter;
/* eslint-disable */
const InputSetter = {
init(hook) {
this.hook = hook;
this.destroyed = false;
this.config = hook.config.InputSetter || (this.hook.config.InputSetter = {});
this.eventWrapper = {};
this.addEvents();
},
addEvents() {
this.eventWrapper.setInputs = this.setInputs.bind(this);
this.hook.list.list.addEventListener('click.dl', this.eventWrapper.setInputs);
},
removeEvents() {
this.hook.list.list.removeEventListener('click.dl', this.eventWrapper.setInputs);
},
setInputs(e) {
if (this.destroyed) return;
const selectedItem = e.detail.selected;
if (!Array.isArray(this.config)) this.config = [this.config];
this.config.forEach(config => this.setInput(config, selectedItem));
},
setInput(config, selectedItem) {
const input = config.input || this.hook.trigger;
const newValue = selectedItem.getAttribute(config.valueAttribute);
const inputAttribute = config.inputAttribute;
if (!newValue) return;
if (input.hasAttribute(inputAttribute)) return input.setAttribute(inputAttribute, newValue);
if (input.tagName === 'INPUT') return input.value = newValue;
return input.textContent = newValue;
},
destroy() {
this.destroyed = true;
this.removeEvents();
},
};
export default InputSetter;
/* eslint-disable */
import { DATA_TRIGGER, DATA_DROPDOWN } from './constants';
const utils = {
toCamelCase(attr) {
return this.camelize(attr.split('-').slice(1).join(' '));
},
t(s, d) {
for (const p in d) {
if (Object.prototype.hasOwnProperty.call(d, p)) {
s = s.replace(new RegExp(`{{${p}}}`, 'g'), d[p]);
}
}
return s;
},
camelize(str) {
return str.replace(/(?:^\w|[A-Z]|\b\w)/g, (letter, index) => {
return index === 0 ? letter.toLowerCase() : letter.toUpperCase();
}).replace(/\s+/g, '');
},
closest(thisTag, stopTag) {
while (thisTag && thisTag.tagName !== stopTag && thisTag.tagName !== 'HTML') {
thisTag = thisTag.parentNode;
}
return thisTag;
},
isDropDownParts(target) {
if (!target || target.tagName === 'HTML') return false;
return target.hasAttribute(DATA_TRIGGER) || target.hasAttribute(DATA_DROPDOWN);
},
};
export default utils;
require('./filtered_search_dropdown'); import Filter from '~/droplab/plugins/filter';
/* global droplabFilter */ require('./filtered_search_dropdown');
(() => { (() => {
class DropdownHint extends gl.FilteredSearchDropdown { class DropdownHint extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) { constructor(droplab, dropdown, input, filter) {
super(droplab, dropdown, input, filter); super(droplab, dropdown, input, filter);
this.config = { this.config = {
droplabFilter: { Filter: {
template: 'hint', template: 'hint',
filterFunction: gl.DropdownUtils.filterHint.bind(null, input), filterFunction: gl.DropdownUtils.filterHint.bind(null, input),
}, },
...@@ -69,12 +69,12 @@ require('./filtered_search_dropdown'); ...@@ -69,12 +69,12 @@ require('./filtered_search_dropdown');
} }
}); });
this.droplab.changeHookList(this.hookId, this.dropdown, [droplabFilter], this.config); this.droplab.changeHookList(this.hookId, this.dropdown, [Filter], this.config);
this.droplab.setData(this.hookId, dropdownData); this.droplab.setData(this.hookId, dropdownData);
} }
init() { init() {
this.droplab.addHook(this.input, this.dropdown, [droplabFilter], this.config).init(); this.droplab.addHook(this.input, this.dropdown, [Filter], this.config).init();
} }
} }
......
require('./filtered_search_dropdown'); /* global Flash */
import Ajax from '~/droplab/plugins/ajax';
import Filter from '~/droplab/plugins/filter';
/* global droplabAjax */ require('./filtered_search_dropdown');
/* global droplabFilter */
(() => { (() => {
class DropdownNonUser extends gl.FilteredSearchDropdown { class DropdownNonUser extends gl.FilteredSearchDropdown {
...@@ -9,13 +11,19 @@ require('./filtered_search_dropdown'); ...@@ -9,13 +11,19 @@ require('./filtered_search_dropdown');
super(droplab, dropdown, input, filter); super(droplab, dropdown, input, filter);
this.symbol = symbol; this.symbol = symbol;
this.config = { this.config = {
droplabAjax: { Ajax: {
endpoint, endpoint,
method: 'setData', method: 'setData',
loadingTemplate: this.loadingTemplate, loadingTemplate: this.loadingTemplate,
onError() {
/* eslint-disable no-new */
new Flash('An error occured fetching the dropdown data.');
/* eslint-enable no-new */
},
}, },
droplabFilter: { Filter: {
filterFunction: gl.DropdownUtils.filterWithSymbol.bind(null, this.symbol, input), filterFunction: gl.DropdownUtils.filterWithSymbol.bind(null, this.symbol, input),
template: 'title',
}, },
}; };
} }
...@@ -29,13 +37,13 @@ require('./filtered_search_dropdown'); ...@@ -29,13 +37,13 @@ require('./filtered_search_dropdown');
renderContent(forceShowList = false) { renderContent(forceShowList = false) {
this.droplab this.droplab
.changeHookList(this.hookId, this.dropdown, [droplabAjax, droplabFilter], this.config); .changeHookList(this.hookId, this.dropdown, [Ajax, Filter], this.config);
super.renderContent(forceShowList); super.renderContent(forceShowList);
} }
init() { init() {
this.droplab this.droplab
.addHook(this.input, this.dropdown, [droplabAjax, droplabFilter], this.config).init(); .addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
} }
} }
......
require('./filtered_search_dropdown'); /* global Flash */
import AjaxFilter from '~/droplab/plugins/ajax_filter';
/* global droplabAjaxFilter */ require('./filtered_search_dropdown');
(() => { (() => {
class DropdownUser extends gl.FilteredSearchDropdown { class DropdownUser extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) { constructor(droplab, dropdown, input, filter) {
super(droplab, dropdown, input, filter); super(droplab, dropdown, input, filter);
this.config = { this.config = {
droplabAjaxFilter: { AjaxFilter: {
endpoint: `${gon.relative_url_root || ''}/autocomplete/users.json`, endpoint: `${gon.relative_url_root || ''}/autocomplete/users.json`,
searchKey: 'search', searchKey: 'search',
params: { params: {
...@@ -18,6 +20,11 @@ require('./filtered_search_dropdown'); ...@@ -18,6 +20,11 @@ require('./filtered_search_dropdown');
}, },
searchValueFunction: this.getSearchInput.bind(this), searchValueFunction: this.getSearchInput.bind(this),
loadingTemplate: this.loadingTemplate, loadingTemplate: this.loadingTemplate,
onError() {
/* eslint-disable no-new */
new Flash('An error occured fetching the dropdown data.');
/* eslint-enable no-new */
},
}, },
}; };
} }
...@@ -28,7 +35,7 @@ require('./filtered_search_dropdown'); ...@@ -28,7 +35,7 @@ require('./filtered_search_dropdown');
} }
renderContent(forceShowList = false) { renderContent(forceShowList = false) {
this.droplab.changeHookList(this.hookId, this.dropdown, [droplabAjaxFilter], this.config); this.droplab.changeHookList(this.hookId, this.dropdown, [AjaxFilter], this.config);
super.renderContent(forceShowList); super.renderContent(forceShowList);
} }
...@@ -56,7 +63,7 @@ require('./filtered_search_dropdown'); ...@@ -56,7 +63,7 @@ require('./filtered_search_dropdown');
} }
init() { init() {
this.droplab.addHook(this.input, this.dropdown, [droplabAjaxFilter], this.config).init(); this.droplab.addHook(this.input, this.dropdown, [AjaxFilter], this.config).init();
} }
} }
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
class FilteredSearchDropdown { class FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) { constructor(droplab, dropdown, input, filter) {
this.droplab = droplab; this.droplab = droplab;
this.hookId = input && input.getAttribute('data-id'); this.hookId = input && input.id;
this.input = input; this.input = input;
this.filter = filter; this.filter = filter;
this.dropdown = dropdown; this.dropdown = dropdown;
......
/* global DropLab */ import DropLab from '~/droplab/drop_lab';
import FilteredSearchContainer from './container'; import FilteredSearchContainer from './container';
(() => { (() => {
......
...@@ -154,7 +154,7 @@ import eventHub from './event_hub'; ...@@ -154,7 +154,7 @@ import eventHub from './event_hub';
if (e.keyCode === 13) { if (e.keyCode === 13) {
const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown]; const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown];
const dropdownEl = dropdown.element; const dropdownEl = dropdown.element;
const activeElements = dropdownEl.querySelectorAll('.dropdown-active'); const activeElements = dropdownEl.querySelectorAll('.droplab-item-active');
e.preventDefault(); e.preventDefault();
......
...@@ -75,12 +75,6 @@ import './u2f/error'; ...@@ -75,12 +75,6 @@ import './u2f/error';
import './u2f/register'; import './u2f/register';
import './u2f/util'; import './u2f/util';
// droplab
import './droplab/droplab';
import './droplab/droplab_ajax';
import './droplab/droplab_ajax_filter';
import './droplab/droplab_filter';
// everything else // everything else
import './abuse_reports'; import './abuse_reports';
import './activities'; import './activities';
......
...@@ -446,10 +446,8 @@ ...@@ -446,10 +446,8 @@
} }
} }
.filter-dropdown-item.dropdown-active { .filter-dropdown-item.droplab-item-active .btn {
.btn {
@extend %filter-dropdown-item-btn-hover; @extend %filter-dropdown-item-btn-hover;
}
} }
.filter-dropdown-loading { .filter-dropdown-loading {
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
.scroll-container .scroll-container
%ul.tokens-container.list-unstyled %ul.tokens-container.list-unstyled
%li.input-token %li.input-token
%input.form-control.filtered-search{ placeholder: 'Search or filter results...', data: { id: "filtered-search-#{type.to_s}", 'project-id' => @project.id, 'username-params' => @users.to_json(only: [:id, :username]), 'base-endpoint' => namespace_project_path(@project.namespace, @project) } } %input.form-control.filtered-search{ id: "filtered-search-#{type.to_s}", placeholder: 'Search or filter results...', data: { 'project-id' => @project.id, 'username-params' => @users.to_json(only: [:id, :username]), 'base-endpoint' => namespace_project_path(@project.namespace, @project) } }
= icon('filter') = icon('filter')
%button.clear-search.hidden{ type: 'button' } %button.clear-search.hidden{ type: 'button' }
= icon('times') = icon('times')
......
require 'rails_helper' require 'rails_helper'
describe 'Dropdown hint', js: true, feature: true do describe 'Dropdown hint', :js, :feature do
include FilteredSearchHelpers include FilteredSearchHelpers
include WaitForAjax include WaitForAjax
...@@ -9,10 +9,6 @@ describe 'Dropdown hint', js: true, feature: true do ...@@ -9,10 +9,6 @@ describe 'Dropdown hint', js: true, feature: true do
let(:filtered_search) { find('.filtered-search') } let(:filtered_search) { find('.filtered-search') }
let(:js_dropdown_hint) { '#js-dropdown-hint' } let(:js_dropdown_hint) { '#js-dropdown-hint' }
def dropdown_hint_size
page.all('#js-dropdown-hint .filter-dropdown .filter-dropdown-item').size
end
def click_hint(text) def click_hint(text)
find('#js-dropdown-hint .filter-dropdown .filter-dropdown-item', text: text).click find('#js-dropdown-hint .filter-dropdown .filter-dropdown-item', text: text).click
end end
...@@ -46,14 +42,16 @@ describe 'Dropdown hint', js: true, feature: true do ...@@ -46,14 +42,16 @@ describe 'Dropdown hint', js: true, feature: true do
it 'does not filter `Press Enter or click to search`' do it 'does not filter `Press Enter or click to search`' do
filtered_search.set('randomtext') filtered_search.set('randomtext')
expect(page).to have_css(js_dropdown_hint, text: 'Press Enter or click to search', visible: false) hint_dropdown = find(js_dropdown_hint)
expect(dropdown_hint_size).to eq(0)
expect(hint_dropdown).to have_content('Press Enter or click to search')
expect(hint_dropdown).to have_selector('.filter-dropdown .filter-dropdown-item', count: 0)
end end
it 'filters with text' do it 'filters with text' do
filtered_search.set('a') filtered_search.set('a')
expect(dropdown_hint_size).to eq(3) expect(find(js_dropdown_hint)).to have_selector('.filter-dropdown .filter-dropdown-item', count: 3)
end end
end end
......
...@@ -28,10 +28,6 @@ describe 'Dropdown label', js: true, feature: true do ...@@ -28,10 +28,6 @@ describe 'Dropdown label', js: true, feature: true do
filter_dropdown.find('.filter-dropdown-item', text: text).click filter_dropdown.find('.filter-dropdown-item', text: text).click
end end
def dropdown_label_size
filter_dropdown.all('.filter-dropdown-item').size
end
def clear_search_field def clear_search_field
find('.filtered-search-box .clear-search').click find('.filtered-search-box .clear-search').click
end end
...@@ -81,7 +77,7 @@ describe 'Dropdown label', js: true, feature: true do ...@@ -81,7 +77,7 @@ describe 'Dropdown label', js: true, feature: true do
filtered_search.set('label:') filtered_search.set('label:')
expect(filter_dropdown).to have_content(bug_label.title) expect(filter_dropdown).to have_content(bug_label.title)
expect(dropdown_label_size).to eq(1) expect(filter_dropdown).to have_selector('.filter-dropdown-item', count: 1)
end end
end end
...@@ -97,7 +93,8 @@ describe 'Dropdown label', js: true, feature: true do ...@@ -97,7 +93,8 @@ describe 'Dropdown label', js: true, feature: true do
expect(filter_dropdown.find('.filter-dropdown-item', text: bug_label.title)).to be_visible expect(filter_dropdown.find('.filter-dropdown-item', text: bug_label.title)).to be_visible
expect(filter_dropdown.find('.filter-dropdown-item', text: uppercase_label.title)).to be_visible expect(filter_dropdown.find('.filter-dropdown-item', text: uppercase_label.title)).to be_visible
expect(dropdown_label_size).to eq(2)
expect(filter_dropdown).to have_selector('.filter-dropdown-item', count: 2)
clear_search_field clear_search_field
init_label_search init_label_search
...@@ -106,14 +103,14 @@ describe 'Dropdown label', js: true, feature: true do ...@@ -106,14 +103,14 @@ describe 'Dropdown label', js: true, feature: true do
expect(filter_dropdown.find('.filter-dropdown-item', text: bug_label.title)).to be_visible expect(filter_dropdown.find('.filter-dropdown-item', text: bug_label.title)).to be_visible
expect(filter_dropdown.find('.filter-dropdown-item', text: uppercase_label.title)).to be_visible expect(filter_dropdown.find('.filter-dropdown-item', text: uppercase_label.title)).to be_visible
expect(dropdown_label_size).to eq(2) expect(filter_dropdown).to have_selector('.filter-dropdown-item', count: 2)
end end
it 'filters by multiple words with or without symbol' do it 'filters by multiple words with or without symbol' do
filtered_search.send_keys('Hig') filtered_search.send_keys('Hig')
expect(filter_dropdown.find('.filter-dropdown-item', text: two_words_label.title)).to be_visible expect(filter_dropdown.find('.filter-dropdown-item', text: two_words_label.title)).to be_visible
expect(dropdown_label_size).to eq(1) expect(filter_dropdown).to have_selector('.filter-dropdown-item', count: 1)
clear_search_field clear_search_field
init_label_search init_label_search
...@@ -121,14 +118,14 @@ describe 'Dropdown label', js: true, feature: true do ...@@ -121,14 +118,14 @@ describe 'Dropdown label', js: true, feature: true do
filtered_search.send_keys('~Hig') filtered_search.send_keys('~Hig')
expect(filter_dropdown.find('.filter-dropdown-item', text: two_words_label.title)).to be_visible expect(filter_dropdown.find('.filter-dropdown-item', text: two_words_label.title)).to be_visible
expect(dropdown_label_size).to eq(1) expect(filter_dropdown).to have_selector('.filter-dropdown-item', count: 1)
end end
it 'filters by multiple words containing single quotes with or without symbol' do it 'filters by multiple words containing single quotes with or without symbol' do
filtered_search.send_keys('won\'t') filtered_search.send_keys('won\'t')
expect(filter_dropdown.find('.filter-dropdown-item', text: wont_fix_single_label.title)).to be_visible expect(filter_dropdown.find('.filter-dropdown-item', text: wont_fix_single_label.title)).to be_visible
expect(dropdown_label_size).to eq(1) expect(filter_dropdown).to have_selector('.filter-dropdown-item', count: 1)
clear_search_field clear_search_field
init_label_search init_label_search
...@@ -136,14 +133,14 @@ describe 'Dropdown label', js: true, feature: true do ...@@ -136,14 +133,14 @@ describe 'Dropdown label', js: true, feature: true do
filtered_search.send_keys('~won\'t') filtered_search.send_keys('~won\'t')
expect(filter_dropdown.find('.filter-dropdown-item', text: wont_fix_single_label.title)).to be_visible expect(filter_dropdown.find('.filter-dropdown-item', text: wont_fix_single_label.title)).to be_visible
expect(dropdown_label_size).to eq(1) expect(filter_dropdown).to have_selector('.filter-dropdown-item', count: 1)
end end
it 'filters by multiple words containing double quotes with or without symbol' do it 'filters by multiple words containing double quotes with or without symbol' do
filtered_search.send_keys('won"t') filtered_search.send_keys('won"t')
expect(filter_dropdown.find('.filter-dropdown-item', text: wont_fix_label.title)).to be_visible expect(filter_dropdown.find('.filter-dropdown-item', text: wont_fix_label.title)).to be_visible
expect(dropdown_label_size).to eq(1) expect(filter_dropdown).to have_selector('.filter-dropdown-item', count: 1)
clear_search_field clear_search_field
init_label_search init_label_search
...@@ -151,14 +148,14 @@ describe 'Dropdown label', js: true, feature: true do ...@@ -151,14 +148,14 @@ describe 'Dropdown label', js: true, feature: true do
filtered_search.send_keys('~won"t') filtered_search.send_keys('~won"t')
expect(filter_dropdown.find('.filter-dropdown-item', text: wont_fix_label.title)).to be_visible expect(filter_dropdown.find('.filter-dropdown-item', text: wont_fix_label.title)).to be_visible
expect(dropdown_label_size).to eq(1) expect(filter_dropdown).to have_selector('.filter-dropdown-item', count: 1)
end end
it 'filters by special characters with or without symbol' do it 'filters by special characters with or without symbol' do
filtered_search.send_keys('^+') filtered_search.send_keys('^+')
expect(filter_dropdown.find('.filter-dropdown-item', text: special_label.title)).to be_visible expect(filter_dropdown.find('.filter-dropdown-item', text: special_label.title)).to be_visible
expect(dropdown_label_size).to eq(1) expect(filter_dropdown).to have_selector('.filter-dropdown-item', count: 1)
clear_search_field clear_search_field
init_label_search init_label_search
...@@ -166,7 +163,7 @@ describe 'Dropdown label', js: true, feature: true do ...@@ -166,7 +163,7 @@ describe 'Dropdown label', js: true, feature: true do
filtered_search.send_keys('~^+') filtered_search.send_keys('~^+')
expect(filter_dropdown.find('.filter-dropdown-item', text: special_label.title)).to be_visible expect(filter_dropdown.find('.filter-dropdown-item', text: special_label.title)).to be_visible
expect(dropdown_label_size).to eq(1) expect(filter_dropdown).to have_selector('.filter-dropdown-item', count: 1)
end end
end end
...@@ -280,13 +277,13 @@ describe 'Dropdown label', js: true, feature: true do ...@@ -280,13 +277,13 @@ describe 'Dropdown label', js: true, feature: true do
create(:label, project: project, title: 'bug-label') create(:label, project: project, title: 'bug-label')
init_label_search init_label_search
expect(dropdown_label_size).to eq(1) expect(filter_dropdown).to have_selector('.filter-dropdown-item', count: 1)
create(:label, project: project) create(:label, project: project)
clear_search_field clear_search_field
init_label_search init_label_search
expect(dropdown_label_size).to eq(1) expect(filter_dropdown).to have_selector('.filter-dropdown-item', count: 1)
end end
end end
end end
...@@ -65,7 +65,7 @@ describe 'Dropdown milestone', :feature, :js do ...@@ -65,7 +65,7 @@ describe 'Dropdown milestone', :feature, :js do
it 'should load all the milestones when opened' do it 'should load all the milestones when opened' do
filtered_search.set('milestone:') filtered_search.set('milestone:')
expect(dropdown_milestone_size).to be > 0 expect(filter_dropdown).to have_selector('.filter-dropdown .filter-dropdown-item', count: 6)
end end
end end
...@@ -84,37 +84,37 @@ describe 'Dropdown milestone', :feature, :js do ...@@ -84,37 +84,37 @@ describe 'Dropdown milestone', :feature, :js do
it 'filters by name' do it 'filters by name' do
filtered_search.send_keys('v1') filtered_search.send_keys('v1')
expect(dropdown_milestone_size).to eq(1) expect(filter_dropdown).to have_selector('.filter-dropdown .filter-dropdown-item', count: 1)
end end
it 'filters by case insensitive name' do it 'filters by case insensitive name' do
filtered_search.send_keys('V1') filtered_search.send_keys('V1')
expect(dropdown_milestone_size).to eq(1) expect(filter_dropdown).to have_selector('.filter-dropdown .filter-dropdown-item', count: 1)
end end
it 'filters by name with symbol' do it 'filters by name with symbol' do
filtered_search.send_keys('%v1') filtered_search.send_keys('%v1')
expect(dropdown_milestone_size).to eq(1) expect(filter_dropdown).to have_selector('.filter-dropdown .filter-dropdown-item', count: 1)
end end
it 'filters by case insensitive name with symbol' do it 'filters by case insensitive name with symbol' do
filtered_search.send_keys('%V1') filtered_search.send_keys('%V1')
expect(dropdown_milestone_size).to eq(1) expect(filter_dropdown).to have_selector('.filter-dropdown .filter-dropdown-item', count: 1)
end end
it 'filters by special characters' do it 'filters by special characters' do
filtered_search.send_keys('(+') filtered_search.send_keys('(+')
expect(dropdown_milestone_size).to eq(1) expect(filter_dropdown).to have_selector('.filter-dropdown .filter-dropdown-item', count: 1)
end end
it 'filters by special characters with symbol' do it 'filters by special characters with symbol' do
filtered_search.send_keys('%(+') filtered_search.send_keys('%(+')
expect(dropdown_milestone_size).to eq(1) expect(filter_dropdown).to have_selector('.filter-dropdown .filter-dropdown-item', count: 1)
end end
end end
......
...@@ -26,7 +26,7 @@ describe 'Search bar', js: true, feature: true do ...@@ -26,7 +26,7 @@ describe 'Search bar', js: true, feature: true do
filtered_search.native.send_keys(:down) filtered_search.native.send_keys(:down)
page.within '#js-dropdown-hint' do page.within '#js-dropdown-hint' do
expect(page).to have_selector('.dropdown-active') expect(page).to have_selector('.droplab-item-active')
end end
end end
...@@ -79,28 +79,30 @@ describe 'Search bar', js: true, feature: true do ...@@ -79,28 +79,30 @@ describe 'Search bar', js: true, feature: true do
filtered_search.set('author') filtered_search.set('author')
expect(page.all('#js-dropdown-hint .filter-dropdown .filter-dropdown-item').size).to eq(1) expect(find('#js-dropdown-hint')).to have_selector('.filter-dropdown .filter-dropdown-item', count: 1)
find('.filtered-search-box .clear-search').click find('.filtered-search-box .clear-search').click
filtered_search.click filtered_search.click
expect(page.all('#js-dropdown-hint .filter-dropdown .filter-dropdown-item').size).to eq(original_size) expect(find('#js-dropdown-hint')).to have_selector('.filter-dropdown .filter-dropdown-item', count: original_size)
end end
it 'resets the dropdown filters' do it 'resets the dropdown filters' do
filtered_search.click
hint_offset = get_left_style(find('#js-dropdown-hint')['style'])
filtered_search.set('a') filtered_search.set('a')
hint_style = page.find('#js-dropdown-hint')['style']
hint_offset = get_left_style(hint_style)
filtered_search.set('author:') filtered_search.set('author:')
expect(page.all('#js-dropdown-hint .filter-dropdown .filter-dropdown-item').size).to eq(0) find('#js-dropdown-hint', visible: false)
find('.filtered-search-box .clear-search').click find('.filtered-search-box .clear-search').click
filtered_search.click filtered_search.click
expect(page.all('#js-dropdown-hint .filter-dropdown .filter-dropdown-item').size).to be > 0 expect(find('#js-dropdown-hint')).to have_selector('.filter-dropdown .filter-dropdown-item', count: 4)
expect(get_left_style(page.find('#js-dropdown-hint')['style'])).to eq(hint_offset) expect(get_left_style(find('#js-dropdown-hint')['style'])).to eq(hint_offset)
end end
end end
end end
/* eslint-disable */
import * as constants from '~/droplab/constants';
describe('constants', function () {
describe('DATA_TRIGGER', function () {
it('should be `data-dropdown-trigger`', function() {
expect(constants.DATA_TRIGGER).toBe('data-dropdown-trigger');
});
});
describe('DATA_DROPDOWN', function () {
it('should be `data-dropdown`', function() {
expect(constants.DATA_DROPDOWN).toBe('data-dropdown');
});
});
describe('SELECTED_CLASS', function () {
it('should be `droplab-item-selected`', function() {
expect(constants.SELECTED_CLASS).toBe('droplab-item-selected');
});
});
describe('ACTIVE_CLASS', function () {
it('should be `droplab-item-active`', function() {
expect(constants.ACTIVE_CLASS).toBe('droplab-item-active');
});
});
});
This diff is collapsed.
/* eslint-disable */
import Hook from '~/droplab/hook';
import * as dropdownSrc from '~/droplab/drop_down';
describe('Hook', function () {
describe('class constructor', function () {
beforeEach(function () {
this.trigger = { id: 'id' };
this.list = {};
this.plugins = {};
this.config = {};
this.dropdown = {};
spyOn(dropdownSrc, 'default').and.returnValue(this.dropdown);
this.hook = new Hook(this.trigger, this.list, this.plugins, this.config);
});
it('should set .trigger', function () {
expect(this.hook.trigger).toBe(this.trigger);
});
it('should set .list', function () {
expect(this.hook.list).toBe(this.dropdown);
});
it('should call DropDown constructor', function () {
expect(dropdownSrc.default).toHaveBeenCalledWith(this.list);
});
it('should set .type', function () {
expect(this.hook.type).toBe('Hook');
});
it('should set .event', function () {
expect(this.hook.event).toBe('click');
});
it('should set .plugins', function () {
expect(this.hook.plugins).toBe(this.plugins);
});
it('should set .config', function () {
expect(this.hook.config).toBe(this.config);
});
it('should set .id', function () {
expect(this.hook.id).toBe(this.trigger.id);
});
describe('if config argument is undefined', function () {
beforeEach(function () {
this.config = undefined;
this.hook = new Hook(this.trigger, this.list, this.plugins, this.config);
});
it('should set .config to an empty object', function () {
expect(this.hook.config).toEqual({});
});
});
describe('if plugins argument is undefined', function () {
beforeEach(function () {
this.plugins = undefined;
this.hook = new Hook(this.trigger, this.list, this.plugins, this.config);
});
it('should set .plugins to an empty array', function () {
expect(this.hook.plugins).toEqual([]);
});
});
});
describe('addEvents', function () {
it('should exist', function () {
expect(Hook.prototype.hasOwnProperty('addEvents')).toBe(true);
});
});
});
/* eslint-disable */
import InputSetter from '~/droplab/plugins/input_setter';
describe('InputSetter', function () {
describe('init', function () {
beforeEach(function () {
this.config = { InputSetter: {} };
this.hook = { config: this.config };
this.inputSetter = jasmine.createSpyObj('inputSetter', ['addEvents']);
InputSetter.init.call(this.inputSetter, this.hook);
});
it('should set .hook', function () {
expect(this.inputSetter.hook).toBe(this.hook);
});
it('should set .config', function () {
expect(this.inputSetter.config).toBe(this.config.InputSetter);
});
it('should set .eventWrapper', function () {
expect(this.inputSetter.eventWrapper).toEqual({});
});
it('should call .addEvents', function () {
expect(this.inputSetter.addEvents).toHaveBeenCalled();
});
describe('if config.InputSetter is not set', function () {
beforeEach(function () {
this.config = { InputSetter: undefined };
this.hook = { config: this.config };
InputSetter.init.call(this.inputSetter, this.hook);
});
it('should set .config to an empty object', function () {
expect(this.inputSetter.config).toEqual({});
});
it('should set hook.config to an empty object', function () {
expect(this.hook.config.InputSetter).toEqual({});
});
})
});
describe('addEvents', function () {
beforeEach(function () {
this.hook = { list: { list: jasmine.createSpyObj('list', ['addEventListener']) } };
this.inputSetter = { eventWrapper: {}, hook: this.hook, setInputs: () => {} };
InputSetter.addEvents.call(this.inputSetter);
});
it('should set .eventWrapper.setInputs', function () {
expect(this.inputSetter.eventWrapper.setInputs).toEqual(jasmine.any(Function));
});
it('should call .addEventListener', function () {
expect(this.hook.list.list.addEventListener)
.toHaveBeenCalledWith('click.dl', this.inputSetter.eventWrapper.setInputs);
});
});
describe('removeEvents', function () {
beforeEach(function () {
this.hook = { list: { list: jasmine.createSpyObj('list', ['removeEventListener']) } };
this.eventWrapper = jasmine.createSpyObj('eventWrapper', ['setInputs']);
this.inputSetter = { eventWrapper: this.eventWrapper, hook: this.hook };
InputSetter.removeEvents.call(this.inputSetter);
});
it('should call .removeEventListener', function () {
expect(this.hook.list.list.removeEventListener)
.toHaveBeenCalledWith('click.dl', this.eventWrapper.setInputs);
});
});
describe('setInputs', function () {
beforeEach(function () {
this.event = { detail: { selected: {} } };
this.config = [0, 1];
this.inputSetter = { config: this.config, setInput: () => {} };
spyOn(this.inputSetter, 'setInput');
InputSetter.setInputs.call(this.inputSetter, this.event);
});
it('should call .setInput for each config element', function () {
const allArgs = this.inputSetter.setInput.calls.allArgs();
expect(allArgs.length).toEqual(2);
allArgs.forEach((args, i) => {
expect(args[0]).toBe(this.config[i]);
expect(args[1]).toBe(this.event.detail.selected);
});
});
describe('if config isnt an array', function () {
beforeEach(function () {
this.inputSetter = { config: {}, setInput: () => {} };
InputSetter.setInputs.call(this.inputSetter, this.event);
});
it('should set .config to an array with .config as the first element', function () {
expect(this.inputSetter.config).toEqual([{}]);
});
});
});
describe('setInput', function () {
beforeEach(function () {
this.selectedItem = { getAttribute: () => {} };
this.input = { value: 'oldValue', tagName: 'INPUT', hasAttribute: () => {} };
this.config = { valueAttribute: {}, input: this.input };
this.inputSetter = { hook: { trigger: {} } };
this.newValue = 'newValue';
spyOn(this.selectedItem, 'getAttribute').and.returnValue(this.newValue);
spyOn(this.input, 'hasAttribute').and.returnValue(false);
InputSetter.setInput.call(this.inputSetter, this.config, this.selectedItem);
});
it('should call .getAttribute', function () {
expect(this.selectedItem.getAttribute).toHaveBeenCalledWith(this.config.valueAttribute);
});
it('should call .hasAttribute', function () {
expect(this.input.hasAttribute).toHaveBeenCalledWith(undefined);
});
it('should set the value of the input', function () {
expect(this.input.value).toBe(this.newValue);
});
describe('if there is no newValue', function () {
beforeEach(function () {
this.newValue = '';
this.inputSetter = { hook: { trigger: {} } };
this.config = { valueAttribute: {}, input: this.input };
this.input = { value: 'oldValue', tagName: 'INPUT' };
this.selectedItem = { getAttribute: () => {} };
InputSetter.setInput.call(this.inputSetter, this.config, this.selectedItem);
});
it('should not set the value of the input', function () {
expect(this.input.value).toBe('oldValue');
})
});
describe('if no config.input is provided', function () {
beforeEach(function () {
this.config = { valueAttribute: {} };
this.trigger = { value: 'oldValue', tagName: 'INPUT', hasAttribute: () => {} };
this.inputSetter = { hook: { trigger: this.trigger } };
InputSetter.setInput.call(this.inputSetter, this.config, this.selectedItem);
});
it('should set the value of the hook.trigger', function () {
expect(this.trigger.value).toBe(this.newValue);
});
});
describe('if the input tag is not INPUT', function () {
beforeEach(function () {
this.input = { textContent: 'oldValue', tagName: 'SPAN', hasAttribute: () => {} };
this.config = { valueAttribute: {}, input: this.input };
InputSetter.setInput.call(this.inputSetter, this.config, this.selectedItem);
});
it('should set the textContent of the input', function () {
expect(this.input.textContent).toBe(this.newValue);
});
describe('if there is no new value', function () {
beforeEach(function () {
this.selectedItem = { getAttribute: () => {} };
this.input = { textContent: 'oldValue', tagName: 'INPUT', hasAttribute: () => {} };
this.config = { valueAttribute: {}, input: this.input };
this.inputSetter = { hook: { trigger: {} } };
this.newValue = 'newValue';
spyOn(this.selectedItem, 'getAttribute').and.returnValue(this.newValue);
InputSetter.setInput.call(this.inputSetter, this.config, this.selectedItem);
});
it('should not set the value of the input', function () {
expect(this.input.textContent).toBe('oldValue');
});
});
});
describe('if there is an inputAttribute', function () {
beforeEach(function () {
this.selectedItem = { getAttribute: () => {} };
this.input = { id: 'oldValue', hasAttribute: () => {}, setAttribute: () => {} };
this.inputSetter = { hook: { trigger: {} } };
this.newValue = 'newValue';
this.inputAttribute = 'id';
this.config = {
valueAttribute: {},
input: this.input,
inputAttribute: this.inputAttribute,
};
spyOn(this.selectedItem, 'getAttribute').and.returnValue(this.newValue);
spyOn(this.input, 'hasAttribute').and.returnValue(true);
spyOn(this.input, 'setAttribute');
InputSetter.setInput.call(this.inputSetter, this.config, this.selectedItem);
});
it('should call setAttribute', function () {
expect(this.input.setAttribute).toHaveBeenCalledWith(this.inputAttribute, this.newValue);
});
it('should not set the value or textContent of the input', function () {
expect(this.input.value).not.toBe('newValue');
expect(this.input.textContent).not.toBe('newValue');
});
});
});
describe('destroy', function () {
beforeEach(function () {
this.inputSetter = jasmine.createSpyObj('inputSetter', ['removeEvents']);
InputSetter.destroy.call(this.inputSetter);
});
it('should call .removeEvents', function () {
expect(this.inputSetter.removeEvents).toHaveBeenCalled();
});
});
});
...@@ -33,7 +33,7 @@ require('~/filtered_search/dropdown_user'); ...@@ -33,7 +33,7 @@ require('~/filtered_search/dropdown_user');
}); });
}); });
describe('config droplabAjaxFilter\'s endpoint', () => { describe('config AjaxFilter\'s endpoint', () => {
beforeEach(() => { beforeEach(() => {
spyOn(gl.DropdownUser.prototype, 'bindEvents').and.callFake(() => {}); spyOn(gl.DropdownUser.prototype, 'bindEvents').and.callFake(() => {});
spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {}); spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {});
...@@ -45,13 +45,13 @@ require('~/filtered_search/dropdown_user'); ...@@ -45,13 +45,13 @@ require('~/filtered_search/dropdown_user');
}; };
const dropdown = new gl.DropdownUser(); const dropdown = new gl.DropdownUser();
expect(dropdown.config.droplabAjaxFilter.endpoint).toBe('/autocomplete/users.json'); expect(dropdown.config.AjaxFilter.endpoint).toBe('/autocomplete/users.json');
}); });
it('should return endpoint when relative_url_root is undefined', () => { it('should return endpoint when relative_url_root is undefined', () => {
const dropdown = new gl.DropdownUser(); const dropdown = new gl.DropdownUser();
expect(dropdown.config.droplabAjaxFilter.endpoint).toBe('/autocomplete/users.json'); expect(dropdown.config.AjaxFilter.endpoint).toBe('/autocomplete/users.json');
}); });
it('should return endpoint with relative url when available', () => { it('should return endpoint with relative url when available', () => {
...@@ -60,7 +60,7 @@ require('~/filtered_search/dropdown_user'); ...@@ -60,7 +60,7 @@ require('~/filtered_search/dropdown_user');
}; };
const dropdown = new gl.DropdownUser(); const dropdown = new gl.DropdownUser();
expect(dropdown.config.droplabAjaxFilter.endpoint).toBe('/gitlab_directory/autocomplete/users.json'); expect(dropdown.config.AjaxFilter.endpoint).toBe('/gitlab_directory/autocomplete/users.json');
}); });
afterEach(() => { afterEach(() => {
......
This diff is collapsed.
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