src/components/tabs/Tabs.js
import _ from 'lodash';
import NestedComponent from '../_classes/nested/NestedComponent';
export default class TabsComponent extends NestedComponent {
static schema(...extend) {
return NestedComponent.schema({
label: 'Tabs',
type: 'tabs',
input: false,
key: 'tabs',
persistent: false,
tableView: false,
components: [
{
label: 'Tab 1',
key: 'tab1',
components: [],
},
],
verticalLayout: false,
}, ...extend);
}
static get builderInfo() {
return {
title: 'Tabs',
group: 'layout',
icon: 'folder-o',
weight: 50,
documentation: '/userguide/form-building/layout-components#tabs',
showPreview: false,
schema: TabsComponent.schema(),
};
}
static savedValueTypes() {
return [];
}
get defaultSchema() {
return TabsComponent.schema();
}
get schema() {
const schema = super.schema;
// We need to clone this because the builder uses the "components" reference and this would reset that reference.
const components = _.cloneDeep(this.component.components);
schema.components = components.map((tab, index) => {
tab.components = this.tabs[index].map((component) => component.schema);
return tab;
});
return schema;
}
get tabKey() {
return `tab-${this.key}`;
}
get tabLikey() {
return `tabLi-${this.key}`;
}
get tabLinkKey() {
return `tabLink-${this.key}`;
}
constructor(...args) {
super(...args);
this.currentTab = 0;
this.noField = true;
}
init() {
this.components = [];
this.tabs = [];
_.each(this.component.components, (tab, index) => {
this.tabs[index] = [];
// Initialize empty tabs.
tab.components = tab.components || [];
_.each(tab.components, (comp) => {
const component = this.createComponent(comp);
component.tab = index;
this.tabs[index].push(component);
});
});
}
render() {
return super.render(this.renderTemplate(
'tab',
{
tabKey: this.tabKey,
tabLikey: this.tabLikey,
tabLinkKey: this.tabLinkKey,
currentTab: this.currentTab,
tabComponents: this.tabs.map(tab => this.renderComponents(tab)),
},
(
this.options.flatten || this.options.pdf ? 'flat' : null
),
));
}
attach(element) {
this.loadRefs(
element,
{
[this.tabLinkKey]: 'multiple',
[this.tabKey]: 'multiple',
[this.tabLikey]: 'multiple',
},
);
['change', 'error'].forEach(event => this.on(event, this.handleTabsValidation.bind(this)));
const superAttach = super.attach(element);
this.refs[this.tabLinkKey].forEach((tabLink, index) => {
this.addEventListener(tabLink, 'click', (event) => {
event.preventDefault();
this.setTab(index);
});
});
this.refs[this.tabKey].forEach((tab, index) => {
this.attachComponents(tab, this.tabs[index], this.component.components[index].components);
});
return superAttach;
}
detach(all) {
super.detach(all);
}
/**
* Set the current tab.
*
* @param index
*/
setTab(index) {
if (!this.tabs || !this.tabs[index] || !this.refs[this.tabKey] || !this.refs[this.tabKey][index]) {
return;
}
this.currentTab = index;
_.each(this.refs[this.tabKey], (tab) => {
this.removeClass(tab, 'formio-tab-panel-active');
tab.style.display = 'none';
});
this.addClass(this.refs[this.tabKey][index], 'formio-tab-panel-active');
this.refs[this.tabKey][index].style.display = 'block';
_.each(this.refs[this.tabLinkKey], (tabLink, tabIndex) => {
if (this.refs[this.tabLinkKey][tabIndex]) {
this.removeClass(tabLink, 'active');
this.removeClass(tabLink, 'formio-tab-link-active');
}
if (this.refs[this.tabLikey][tabIndex]) {
this.removeClass(this.refs[this.tabLikey][tabIndex], 'active');
this.removeClass(this.refs[this.tabLikey][tabIndex], 'formio-tab-link-container-active');
}
});
if (this.refs[this.tabLikey][index]) {
this.addClass(this.refs[this.tabLikey][index], 'active');
this.addClass(this.refs[this.tabLikey][index], 'formio-tab-link-container-active');
}
if (this.refs[this.tabLinkKey][index]) {
this.addClass(this.refs[this.tabLinkKey][index], 'active');
this.addClass(this.refs[this.tabLinkKey][index], 'formio-tab-link-active');
}
this.triggerChange();
}
beforeFocus(component) {
if ('beforeFocus' in this.parent) {
this.parent.beforeFocus(this);
}
const tabIndex = this.tabs.findIndex((tab) => {
return tab.some((comp) => comp === component);
});
if (tabIndex !== -1 && this.currentTab !== tabIndex) {
this.setTab(tabIndex);
}
}
setErrorClasses(elements, dirty, hasErrors, hasMessages, element = this.element) {
if (this.component.modalEdit) {
super.setErrorClasses(elements, dirty, hasErrors, hasMessages, element);
}
elements.forEach((element) => {
this.addClass(element, 'is-invalid');
if (element.getAttribute('ref') !== 'openModal') {
if (this.options.highlightErrors) {
this.addClass(element, 'tab-error');
}
else {
this.addClass(element, 'has-error');
}
}
});
}
clearErrorClasses(elements) {
if (this.options.server || !this.rendered) {
return;
}
if (this.component.modalEdit) {
const element = Array.isArray(elements) || elements instanceof NodeList ? this.element : elements;
super.clearErrorClasses(element);
}
elements = Array.isArray(elements) || elements instanceof NodeList ? elements : [elements];
elements.forEach((element) => {
this.removeClass(element, 'is-invalid');
this.removeClass(element, 'tab-error');
this.removeClass(element, 'has-error');
});
}
handleTabsValidation() {
if (!this.refs[this.tabLinkKey] || !this.refs[this.tabLinkKey].length || !this.tabs.length) {
return;
}
this.clearErrorClasses(this.refs[this.tabLinkKey]);
const invalidTabsIndexes = this.tabs.reduce((invalidTabs, tab, tabIndex) => {
const hasComponentWithError = tab.some(comp => !!comp.error);
return hasComponentWithError ? [...invalidTabs, tabIndex] : invalidTabs;
}, []);
if (!invalidTabsIndexes.length) {
return;
}
const invalidTabs = [...this.refs[this.tabLinkKey]].filter((_, tabIndex) => invalidTabsIndexes.includes(tabIndex));
this.setErrorClasses(invalidTabs);
}
}