debian-mirror-gitlab/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js

405 lines
13 KiB
JavaScript
Raw Normal View History

2019-09-04 21:01:54 +05:30
import VisualTokenValue from './visual_token_value';
2018-03-27 19:54:05 +05:30
import { objectToQueryString } from '~/lib/utils/common_utils';
2017-08-17 22:00:37 +05:30
import FilteredSearchContainer from './container';
2018-03-27 19:54:05 +05:30
export default class FilteredSearchVisualTokens {
2020-03-13 15:44:24 +05:30
static permissibleOperatorValues = ['=', '!='];
static getOperatorToken(value) {
let token = null;
FilteredSearchVisualTokens.permissibleOperatorValues.forEach(operatorToken => {
if (value.startsWith(operatorToken)) {
token = operatorToken;
}
});
return token;
}
static getValueToken(value) {
let newValue = value;
FilteredSearchVisualTokens.permissibleOperatorValues.forEach(operatorToken => {
if (value.startsWith(operatorToken)) {
newValue = value.slice(operatorToken.length);
}
});
return newValue;
}
2017-08-17 22:00:37 +05:30
static getLastVisualTokenBeforeInput() {
const inputLi = FilteredSearchContainer.container.querySelector('.input-token');
const lastVisualToken = inputLi && inputLi.previousElementSibling;
return {
lastVisualToken,
2018-12-13 13:39:08 +05:30
isLastVisualTokenValid:
lastVisualToken === null ||
lastVisualToken.className.indexOf('filtered-search-term') !== -1 ||
2020-03-13 15:44:24 +05:30
(lastVisualToken &&
lastVisualToken.querySelector('.operator') !== null &&
lastVisualToken.querySelector('.value') !== null),
2017-08-17 22:00:37 +05:30
};
}
static unselectTokens() {
2018-12-13 13:39:08 +05:30
const otherTokens = FilteredSearchContainer.container.querySelectorAll(
'.js-visual-token .selectable.selected',
);
2017-08-17 22:00:37 +05:30
[].forEach.call(otherTokens, t => t.classList.remove('selected'));
}
static selectToken(tokenButton, forceSelection = false) {
const selected = tokenButton.classList.contains('selected');
FilteredSearchVisualTokens.unselectTokens();
if (!selected || forceSelection) {
tokenButton.classList.add('selected');
}
}
static removeSelectedToken() {
const selected = FilteredSearchContainer.container.querySelector('.js-visual-token .selected');
if (selected) {
const li = selected.closest('.js-visual-token');
li.parentElement.removeChild(li);
}
}
2018-12-05 23:21:45 +05:30
static createVisualTokenElementHTML(options = {}) {
2020-03-13 15:44:24 +05:30
const {
canEdit = true,
hasOperator = false,
uppercaseTokenName = false,
capitalizeTokenValue = false,
} = options;
2018-12-05 23:21:45 +05:30
2017-08-17 22:00:37 +05:30
return `
2018-03-17 18:26:18 +05:30
<div class="${canEdit ? 'selectable' : 'hidden'}" role="button">
2018-12-05 23:21:45 +05:30
<div class="${uppercaseTokenName ? 'text-uppercase' : ''} name"></div>
2020-03-13 15:44:24 +05:30
${hasOperator ? '<div class="operator"></div>' : ''}
2017-08-17 22:00:37 +05:30
<div class="value-container">
2018-12-05 23:21:45 +05:30
<div class="${capitalizeTokenValue ? 'text-capitalize' : ''} value"></div>
2018-03-17 18:26:18 +05:30
<div class="remove-token" role="button">
<i class="fa fa-close"></i>
</div>
2017-08-17 22:00:37 +05:30
</div>
</div>
`;
}
2020-03-13 15:44:24 +05:30
static renderVisualTokenValue(parentElement, tokenName, tokenValue, tokenOperator) {
2019-07-07 11:18:12 +05:30
const tokenType = tokenName.toLowerCase();
2017-08-17 22:00:37 +05:30
const tokenValueContainer = parentElement.querySelector('.value-container');
2017-09-10 17:25:29 +05:30
const tokenValueElement = tokenValueContainer.querySelector('.value');
2020-06-23 00:09:42 +05:30
tokenValueElement.textContent = tokenValue;
2017-08-17 22:00:37 +05:30
2020-03-13 15:44:24 +05:30
const visualTokenValue = new VisualTokenValue(tokenValue, tokenType, tokenOperator);
2018-12-13 13:39:08 +05:30
2019-07-07 11:18:12 +05:30
visualTokenValue.render(tokenValueContainer, tokenValueElement);
2017-08-17 22:00:37 +05:30
}
2020-03-13 15:44:24 +05:30
static addVisualTokenElement({ name, operator, value, options = {} }) {
2019-07-07 11:18:12 +05:30
const {
isSearchTerm = false,
canEdit,
uppercaseTokenName,
capitalizeTokenValue,
tokenClass = `search-token-${name.toLowerCase()}`,
} = options;
2017-08-17 22:00:37 +05:30
const li = document.createElement('li');
li.classList.add('js-visual-token');
li.classList.add(isSearchTerm ? 'filtered-search-term' : 'filtered-search-token');
2019-07-07 11:18:12 +05:30
if (!isSearchTerm) {
li.classList.add(tokenClass);
}
2020-03-13 15:44:24 +05:30
const hasOperator = Boolean(operator);
2017-08-17 22:00:37 +05:30
if (value) {
2018-12-05 23:21:45 +05:30
li.innerHTML = FilteredSearchVisualTokens.createVisualTokenElementHTML({
canEdit,
uppercaseTokenName,
2020-03-13 15:44:24 +05:30
operator,
hasOperator,
2018-12-05 23:21:45 +05:30
capitalizeTokenValue,
});
2020-03-13 15:44:24 +05:30
FilteredSearchVisualTokens.renderVisualTokenValue(li, name, value, operator);
2017-08-17 22:00:37 +05:30
} else {
2020-03-13 15:44:24 +05:30
const nameHTML = `<div class="${uppercaseTokenName ? 'text-uppercase' : ''} name"></div>`;
let operatorHTML = '';
if (hasOperator) {
operatorHTML = '<div class="operator"></div>';
}
li.innerHTML = nameHTML + operatorHTML;
2017-08-17 22:00:37 +05:30
}
2020-03-13 15:44:24 +05:30
2020-06-23 00:09:42 +05:30
li.querySelector('.name').textContent = name;
2020-03-13 15:44:24 +05:30
if (hasOperator) {
2020-06-23 00:09:42 +05:30
li.querySelector('.operator').textContent = operator;
2020-03-13 15:44:24 +05:30
}
2017-08-17 22:00:37 +05:30
const tokensContainer = FilteredSearchContainer.container.querySelector('.tokens-container');
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
tokensContainer.insertBefore(li, input.parentElement);
}
static addValueToPreviousVisualTokenElement(value) {
2018-12-13 13:39:08 +05:30
const {
lastVisualToken,
isLastVisualTokenValid,
} = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
2017-08-17 22:00:37 +05:30
if (!isLastVisualTokenValid && lastVisualToken.classList.contains('filtered-search-token')) {
const name = FilteredSearchVisualTokens.getLastTokenPartial();
2020-03-13 15:44:24 +05:30
const operator = FilteredSearchVisualTokens.getLastTokenOperator();
lastVisualToken.innerHTML = FilteredSearchVisualTokens.createVisualTokenElementHTML({
hasOperator: Boolean(operator),
});
2020-06-23 00:09:42 +05:30
lastVisualToken.querySelector('.name').textContent = name;
lastVisualToken.querySelector('.operator').textContent = operator;
2020-03-13 15:44:24 +05:30
FilteredSearchVisualTokens.renderVisualTokenValue(lastVisualToken, name, value, operator);
2017-08-17 22:00:37 +05:30
}
}
2018-12-13 13:39:08 +05:30
static addFilterVisualToken(
tokenName,
2020-03-13 15:44:24 +05:30
tokenOperator,
2018-12-13 13:39:08 +05:30
tokenValue,
{ canEdit, uppercaseTokenName = false, capitalizeTokenValue = false } = {},
) {
const {
lastVisualToken,
isLastVisualTokenValid,
} = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
2018-11-08 19:23:39 +05:30
const { addVisualTokenElement } = FilteredSearchVisualTokens;
2017-08-17 22:00:37 +05:30
if (isLastVisualTokenValid) {
2020-03-13 15:44:24 +05:30
addVisualTokenElement({
name: tokenName,
operator: tokenOperator,
value: tokenValue,
options: {
canEdit,
uppercaseTokenName,
capitalizeTokenValue,
},
});
} else if (
!isLastVisualTokenValid &&
(lastVisualToken && !lastVisualToken.querySelector('.operator'))
) {
const tokensContainer = FilteredSearchContainer.container.querySelector('.tokens-container');
tokensContainer.removeChild(lastVisualToken);
addVisualTokenElement({
name: tokenName,
operator: tokenOperator,
value: tokenValue,
options: {
canEdit,
uppercaseTokenName,
capitalizeTokenValue,
},
2018-12-05 23:21:45 +05:30
});
2017-08-17 22:00:37 +05:30
} else {
2020-06-23 00:09:42 +05:30
const previousTokenName = lastVisualToken.querySelector('.name').textContent;
const previousTokenOperator = lastVisualToken.querySelector('.operator').textContent;
2017-08-17 22:00:37 +05:30
const tokensContainer = FilteredSearchContainer.container.querySelector('.tokens-container');
tokensContainer.removeChild(lastVisualToken);
2020-03-13 15:44:24 +05:30
let value = tokenValue;
if (!value && !tokenOperator) {
value = tokenName;
}
addVisualTokenElement({
name: previousTokenName,
operator: previousTokenOperator,
value,
options: {
canEdit,
uppercaseTokenName,
capitalizeTokenValue,
},
2018-12-05 23:21:45 +05:30
});
2017-08-17 22:00:37 +05:30
}
}
static addSearchVisualToken(searchTerm) {
const { lastVisualToken } = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
if (lastVisualToken && lastVisualToken.classList.contains('filtered-search-term')) {
2020-06-23 00:09:42 +05:30
lastVisualToken.querySelector('.name').textContent += ` ${searchTerm}`;
2017-08-17 22:00:37 +05:30
} else {
2020-03-13 15:44:24 +05:30
FilteredSearchVisualTokens.addVisualTokenElement({
name: searchTerm,
operator: null,
value: null,
options: {
isSearchTerm: true,
},
2018-12-05 23:21:45 +05:30
});
2017-08-17 22:00:37 +05:30
}
}
2020-03-13 15:44:24 +05:30
static getLastTokenPartial(includeOperator = false) {
2017-08-17 22:00:37 +05:30
const { lastVisualToken } = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
if (!lastVisualToken) return '';
2017-09-10 17:25:29 +05:30
const valueContainer = lastVisualToken.querySelector('.value-container');
const originalValue = valueContainer && valueContainer.dataset.originalValue;
if (originalValue) {
return originalValue;
}
2017-08-17 22:00:37 +05:30
const value = lastVisualToken.querySelector('.value');
const name = lastVisualToken.querySelector('.name');
2020-06-23 00:09:42 +05:30
const valueText = value ? value.textContent : '';
const nameText = name ? name.textContent : '';
2017-08-17 22:00:37 +05:30
2020-03-13 15:44:24 +05:30
if (includeOperator) {
const operator = lastVisualToken.querySelector('.operator');
2020-06-23 00:09:42 +05:30
const operatorText = operator ? operator.textContent : '';
2020-03-13 15:44:24 +05:30
return valueText || operatorText || nameText;
}
2017-08-17 22:00:37 +05:30
return valueText || nameText;
}
2020-03-13 15:44:24 +05:30
static getLastTokenOperator() {
const { lastVisualToken } = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
const operator = lastVisualToken && lastVisualToken.querySelector('.operator');
2020-06-23 00:09:42 +05:30
return operator?.textContent;
2020-03-13 15:44:24 +05:30
}
2017-08-17 22:00:37 +05:30
static removeLastTokenPartial() {
const { lastVisualToken } = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
if (lastVisualToken) {
const value = lastVisualToken.querySelector('.value');
2020-03-13 15:44:24 +05:30
const operator = lastVisualToken.querySelector('.operator');
2017-08-17 22:00:37 +05:30
if (value) {
const button = lastVisualToken.querySelector('.selectable');
const valueContainer = lastVisualToken.querySelector('.value-container');
button.removeChild(valueContainer);
lastVisualToken.innerHTML = button.innerHTML;
2020-03-13 15:44:24 +05:30
} else if (operator) {
lastVisualToken.removeChild(operator);
2017-08-17 22:00:37 +05:30
} else {
lastVisualToken.closest('.tokens-container').removeChild(lastVisualToken);
}
}
}
static tokenizeInput() {
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
2018-12-13 13:39:08 +05:30
const { isLastVisualTokenValid } = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
2017-08-17 22:00:37 +05:30
if (input.value) {
if (isLastVisualTokenValid) {
2018-03-27 19:54:05 +05:30
FilteredSearchVisualTokens.addSearchVisualToken(input.value);
2017-08-17 22:00:37 +05:30
} else {
FilteredSearchVisualTokens.addValueToPreviousVisualTokenElement(input.value);
}
input.value = '';
}
}
2019-07-07 11:18:12 +05:30
/**
* Returns a computed API endpoint
* and query string composed of values from endpointQueryParams
* @param {String} endpoint
* @param {String} endpointQueryParams
*/
static getEndpointWithQueryParams(endpoint, endpointQueryParams) {
if (!endpointQueryParams) {
return endpoint;
}
const queryString = objectToQueryString(JSON.parse(endpointQueryParams));
return `${endpoint}?${queryString}`;
}
2017-08-17 22:00:37 +05:30
static editToken(token) {
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
FilteredSearchVisualTokens.tokenizeInput();
// Replace token with input field
const tokenContainer = token.parentElement;
const inputLi = input.parentElement;
tokenContainer.replaceChild(inputLi, token);
2017-09-10 17:25:29 +05:30
const nameElement = token.querySelector('.name');
2020-03-13 15:44:24 +05:30
const operatorElement = token.querySelector('.operator');
2017-09-10 17:25:29 +05:30
let value;
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
if (token.classList.contains('filtered-search-token')) {
2020-03-13 15:44:24 +05:30
FilteredSearchVisualTokens.addFilterVisualToken(
2020-06-23 00:09:42 +05:30
nameElement.textContent,
operatorElement.textContent,
2020-03-13 15:44:24 +05:30
null,
{
uppercaseTokenName: nameElement.classList.contains('text-uppercase'),
},
);
2017-09-10 17:25:29 +05:30
const valueContainerElement = token.querySelector('.value-container');
value = valueContainerElement.dataset.originalValue;
if (!value) {
const valueElement = valueContainerElement.querySelector('.value');
2020-06-23 00:09:42 +05:30
value = valueElement.textContent;
2017-09-10 17:25:29 +05:30
}
2017-08-17 22:00:37 +05:30
}
2017-09-10 17:25:29 +05:30
// token is a search term
if (!value) {
2020-06-23 00:09:42 +05:30
value = nameElement.textContent;
2017-09-10 17:25:29 +05:30
}
input.value = value;
2017-08-17 22:00:37 +05:30
// Opens dropdown
const inputEvent = new Event('input');
input.dispatchEvent(inputEvent);
// Adds cursor to input
input.focus();
}
static moveInputToTheRight() {
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
if (!input) return;
const inputLi = input.parentElement;
const tokenContainer = FilteredSearchContainer.container.querySelector('.tokens-container');
FilteredSearchVisualTokens.tokenizeInput();
if (!tokenContainer.lastElementChild.isEqualNode(inputLi)) {
2018-12-13 13:39:08 +05:30
const { isLastVisualTokenValid } = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
2017-08-17 22:00:37 +05:30
if (!isLastVisualTokenValid) {
2018-03-27 19:54:05 +05:30
const lastPartial = FilteredSearchVisualTokens.getLastTokenPartial();
FilteredSearchVisualTokens.removeLastTokenPartial();
FilteredSearchVisualTokens.addSearchVisualToken(lastPartial);
2017-08-17 22:00:37 +05:30
}
tokenContainer.removeChild(inputLi);
tokenContainer.appendChild(inputLi);
}
}
}