@randstad-design/orbit-multitheme
Version:
multitheme Front-end code based on Randstad Human Forward components
507 lines (446 loc) • 15.4 kB
JavaScript
import ElementHelpers from '../helpers/element-helpers';
import trapFocus from '../helpers/trapFocus';
/**
* Declare constants
*/
const attributeBase = 'data-rs-chat';
export class Chat {
constructor(element) {
this.element = element;
this.amountTagsChecked;
this.box = ElementHelpers.getElementsByAttribute(this.attributes.box);
this.button = ElementHelpers.getElementByAttribute(this.attributes.button);
this.close = ElementHelpers.getElementByAttribute(this.attributes.close);
this.header = ElementHelpers.getElementByAttribute(this.attributes.header);
this.inputDialog = ElementHelpers.getElementByAttribute(
this.attributes.input
);
this.languageNotification = ElementHelpers.getElementByAttribute(
this.attributes.languageNotification
);
this.languageSaveButton = ElementHelpers.getElementByAttribute(
this.attributes.languageSaveButton
);
this.languageSelector = ElementHelpers.getElementByAttribute(
this.attributes.languageSelector
);
this.minimized = ElementHelpers.getElementByAttribute(
this.attributes.minimized
);
this.popup = ElementHelpers.getElementByAttribute(this.attributes.popup);
this.popupHide = ElementHelpers.getElementsByAttribute(
this.attributes.popupHide
);
this.popupShow = ElementHelpers.getElementByAttribute(
this.attributes.popupShow
);
this.quickSuggestButton;
this.saveLanguageButton = ElementHelpers.getElementByAttribute(
this.attributes.languageSaveButton
);
this.main = ElementHelpers.getElementByAttribute(this.attributes.main);
this.settings = ElementHelpers.getElementByAttribute(
this.attributes.settings
);
this.settingsButton = ElementHelpers.getElementsByAttribute(
this.attributes.settingsButton
);
this.tagsChecked = [];
this.tagDialog = ElementHelpers.getElementByAttribute(
this.attributes.tagsDialog
);
this.textarea = ElementHelpers.getElementByAttribute(
this.attributes.textarea
);
this.deselectButton = ElementHelpers.getElementByAttribute(
this.attributes.deselectButton
);
this.submitButton = ElementHelpers.getElementByAttribute(
this.attributes.submitButton
);
this.elementIsOpen;
this.keystroke = 0;
this.addEventHandlers();
}
/**
* Declare classes
*/
get classes() {
return {
dividerIsHidden: 'divider--is-hidden',
displayNone: 'display-none',
isActive: 'is-active'
};
}
/**
* Declare attribute constants
*/
get attributes() {
return {
box: attributeBase + '-box',
button: attributeBase + '-button',
close: attributeBase + '-close',
main: attributeBase + '-main',
content: attributeBase + '-content',
deselectButton: attributeBase + '-tags-deselect-button',
header: attributeBase + '-header',
input: attributeBase + '-input',
languageNotification: attributeBase + '-language-notification',
languageSaveButton: attributeBase + '-language-save-button',
languageSelector: attributeBase + '-language-selector',
minimized: attributeBase + '-minimized',
popup: attributeBase + '-popup',
popupHide: attributeBase + '-popup-hide',
popupShow: attributeBase + '-popup-show',
quickSuggest: attributeBase + '-quick-suggest',
quickSuggestButton: attributeBase + '-quick-suggest-button',
saveLanguageButton: attributeBase + '-save-language',
settings: attributeBase + '-settings',
settingsButton: attributeBase + '-settings-button',
submitButton: attributeBase + '-tags-submit-button',
tags: attributeBase + '-tags',
tagsDialog: attributeBase + '-tags-dialog',
tagsDialogLabel: attributeBase + '-tags-dialog-label',
textarea: attributeBase + '-textarea'
};
}
/**
* Add event handlers
*/
addEventHandlers() {
this.handleHeaderDivider();
this.toggleInputButton();
this.quickSuggestButtonsHandler();
this.tagHandler();
this.close.addEventListener('click', (event) => {
this.closeChat(event);
});
this.minimized.addEventListener('click', () => {
this.openChat();
});
this.userInputToSpeechBubble();
this.toggleSettingsHandler();
this.languageSelectorHandler();
this.popUpHandler();
this.languageSaveButtonHandler();
// handle key down event - close modal if escape is pressed
document.addEventListener('keydown', (keydownEvent) => {
this.handleButtonKeys(keydownEvent);
});
}
/**
* Adds a divider to the header when scrolling in the chat box
*/
handleHeaderDivider() {
this.box.forEach((element) => {
element.addEventListener('scroll', () => {
const scrollTop = element.scrollTop;
const header = element.previousElementSibling;
const headerClassList = header.classList;
if (scrollTop > 1) {
headerClassList.remove(this.classes.dividerIsHidden);
} else {
headerClassList.add(this.classes.dividerIsHidden);
}
});
});
}
/**
* Toggles the button from enabled to disabled when typing in the textarea
*/
toggleInputButton() {
this.textarea.addEventListener('input', () => {
if (this.textarea.value.trim().length === 0) {
this.button.disabled = true;
} else {
this.button.disabled = false;
}
});
}
/**
* Opens the chat box when clicking on the chat-minimized button
*/
openChat() {
if (this.minimized) {
this.minimized.classList.add(this.classes.displayNone);
this.element.classList.remove(this.classes.displayNone);
this.elementIsOpen = true;
}
}
/**
* Closes the chatbox when clicking on the close button
*/
closeChat(event) {
if (this.close && this.minimized) {
event.preventDefault();
this.element.classList.add(this.classes.displayNone);
this.minimized.classList.remove(this.classes.displayNone);
this.elementIsOpen = false;
this.keystroke = 0;
}
}
/**
* Creates a speech bubble when clicking on the quick suggestion buttons
*/
quickSuggestButtonsHandler() {
this.quickSuggestButton = ElementHelpers.getElementsByAttribute(
this.attributes.quickSuggestButton
);
if (this.quickSuggestButton) {
this.quickSuggestButton.forEach((item) => {
item.addEventListener('click', () => {
const text = item.innerHTML;
const parentElement = item.parentElement;
parentElement.removeAttribute(this.attributes.quickSuggest);
parentElement.setAttribute(this.attributes.content, 'user');
parentElement.innerHTML = `<div class="chat__content chat__content--user speech-bubble speech-bubble--user">
${text}
</div>`;
this.box.scrollTop = this.box.scrollHeight;
});
});
}
}
/**
* Stores the array of the selected tags and the amount of tags that are checked in a global variable
*/
getArrayOfSelectedTags(event) {
if (event.target.checked) {
this.tagsChecked.push(event.target.value);
} else {
let index = this.tagsChecked.indexOf(event.target.value);
if (index !== -1) {
this.tagsChecked.splice(index, 1);
}
}
this.amountTagsChecked = this.tagsChecked.length;
}
/**
* Handles the visibility of the tag dialog and content in the dialog when the tags are checked/unchecked
*/
tagHandler() {
this.tags = ElementHelpers.getElementByAttribute(this.attributes.tags);
if (!this.tags) {
return;
}
const checkboxes = ElementHelpers.getElementsByAttributeWithinElement(
this.tags,
'data-rs-tags-checkbox'
);
const tagDialogLabel = ElementHelpers.getElementByAttribute(
this.attributes.tagsDialogLabel
);
const label = tagDialogLabel
? tagDialogLabel.getAttribute(this.attributes.tagsDialogLabel)
: '@tag selected';
for (let i = 0; checkboxes[i]; i++) {
checkboxes[i].addEventListener('change', (event) => {
this.getArrayOfSelectedTags(event);
if (this.amountTagsChecked > 0) {
this.tagDialog.classList.remove('hidden');
this.inputDialog.classList.add('hidden');
this.textarea.setAttribute('aria-hidden', true);
this.button.setAttribute('aria-hidden', true);
this.deselectButton.removeAttribute('aria-hidden');
this.submitButton.removeAttribute('aria-hidden');
tagDialogLabel.innerText = label.replace(
'@tag',
this.amountTagsChecked
);
} else {
this.tagDialog.classList.add('hidden');
this.inputDialog.classList.remove('hidden');
this.textarea.removeAttribute('aria-hidden');
this.button.removeAttribute('aria-hidden');
this.deselectButton.setAttribute('aria-hidden', true);
this.submitButton.setAttribute('aria-hidden', true);
}
});
}
this.handleTagDialogButtons();
}
/**
* Handles the buttons in the tag dialog. Changes the tags that are selected into a user speechbubble, or deselect all checkboxes.
*/
handleTagDialogButtons() {
const checkboxes = ElementHelpers.getElementsByAttributeWithinElement(
this.tags,
'data-rs-tags-checkbox'
);
this.deselectButton.addEventListener('click', () => {
this.tagsChecked = [];
checkboxes.forEach((item) => {
if (item.checked) {
// Fake a click to fire the eventlistener in tag.js
item.click();
}
});
checkboxes[0].focus();
});
this.submitButton.addEventListener('click', () => {
const text = this.tagsChecked.join(', ');
// The content should be user content
this.tags.setAttribute(this.attributes.content, 'user');
// The attribute tags should not be there anymore since it's a speech bubble now
this.tags.removeAttribute(this.attributes.tags);
this.tags.innerHTML = `<div class="chat__content chat__content--user speech-bubble speech-bubble--user">
${text}
</div>`;
this.tagDialog.classList.add('hidden');
this.inputDialog.classList.remove('hidden');
this.box.scrollTop = this.box.scrollHeight;
this.submitButton.setAttribute('aria-hidden', true);
this.deselectButton.setAttribute('aria-hidden', true);
this.textarea.removeAttribute('aria-hidden');
this.button.removeAttribute('aria-hidden');
this.textarea.focus();
});
}
createSpeechBubble(e) {
if (e.type === 'click' || e.key === 'Enter') {
const inputText = this.textarea.value;
// Remove the quickSuggestionButtons if they exist
const quickSuggestion = ElementHelpers.getElementByAttribute(
this.attributes.quickSuggest
);
if (quickSuggestion) {
quickSuggestion.remove();
}
// Remove the tags if they exist
if (this.tags) {
this.tags.remove();
}
// If the last element is from the bot it should add a --first modifier to it
const box = ElementHelpers.getElementByAttribute(
this.attributes.box,
'chat'
);
box.innerHTML =
box.innerHTML += `<div class='chat-content__wrapper' data-rs-chat-content='user'><div class="chat__content chat__content--user speech-bubble speech-bubble--user">
${inputText}</div></div>`;
if (box.lastElementChild) {
const islastElementFromBot =
box.lastElementChild.getAttribute(this.attributes.content) === 'bot';
if (islastElementFromBot) {
box.lastElementChild.classList.add('chat-content__wrapper--first');
}
}
// Empty the text box
this.textarea.value = '';
// Set height back to original'
this.textarea.style.height = 'auto';
// Disable the button
this.button.disabled = true;
// Scroll to the bottom of the box
box.scrollTop = box.scrollHeight;
}
}
/**
* Changes the input text to a user speech bubble when clicking on the button
*/
userInputToSpeechBubble() {
this.button.addEventListener('click', (e) => {
this.createSpeechBubble(e);
this.textarea.focus();
});
this.textarea.addEventListener('keydown', (e) => {
this.createSpeechBubble(e);
this.textarea.focus();
});
}
toggleSettingsHandler() {
this.settingsButton.forEach((item) => {
item.addEventListener('click', () => {
this.openAndCloseSettings();
});
});
}
languageSelectorHandler() {
this.languageSelector.addEventListener('change', () => {
const value = this.languageSelector.value;
if (value !== '') {
this.languageSaveButton.removeAttribute('disabled');
this.languageNotification.classList.remove('display-none');
} else {
this.languageSaveButton.setAttribute('disabled', 'disabled');
this.languageNotification.classList.add('display-none');
}
});
}
popUpHandler() {
let focusableElements = [];
this.popupShow.addEventListener('click', () => {
this.popup.classList.add('is-active');
focusableElements = ElementHelpers.getKeyboardFocusableElements(
this.settings
);
focusableElements.forEach((el) => {
el.setAttribute('aria-hidden', true);
});
return focusableElements;
});
this.popupHide.forEach((element) => {
element.addEventListener('click', () => {
this.popup.classList.remove('is-active');
focusableElements.forEach((el) => el.removeAttribute('aria-hidden'));
this.settingsButton.focus();
});
});
}
languageSaveButtonHandler() {
this.saveLanguageButton.addEventListener('click', () => {
const box = ElementHelpers.getElementByAttribute(
this.attributes.box,
'chat'
);
// Empty the box content
box.innerHTML = '';
this.openAndCloseSettings();
});
}
openAndCloseSettings() {
const settingsPanelClasses = this.settings.classList;
if (!settingsPanelClasses.contains(this.classes.isActive)) {
settingsPanelClasses.add(this.classes.isActive);
setTimeout(() => {
this.main.classList.add('hidden');
}, 500);
} else {
this.main.classList.remove('hidden');
settingsPanelClasses.remove(this.classes.isActive);
this.settingsButton[0].focus();
}
}
/**
* Handle button keys - handle key presses escape
*
* @param {Event} event - event triggered
*/
handleButtonKeys(event) {
if (event != null) {
switch (ElementHelpers.getKeyCodeOnKeyDownEvent(event)) {
case 'Escape':
if (this.elementIsOpen) {
this.closeChat(event);
}
break;
// eslint-disable-next-line no-case-declarations
case 'Tab':
const activePanel = !this.main.classList.contains('hidden')
? this.main
: this.settings;
trapFocus(event, activePanel, this.elementIsOpen, this.keystroke);
if (this.elementIsOpen) {
this.keystroke++;
}
break;
}
}
}
/**
* Get selector
*/
static getSelector() {
return `[${attributeBase}]`;
}
}