/**
*
* Create a form to display a record
*
* @async
* @param {object} record - record to display in the form
*/
const createForm = function (record) {
if (!record) return
const isStandAlone = (kiss.context.ui == "form-view")
const model = record.model
const modelId = model.id
const isMobile = kiss.screen.isMobile
const isOwner = kiss.session.isOwner
const isAccountManager = kiss.session.isAccountManager()
// Can't open the same record twice
if (record && $(record.id)) {
return createNotification(txtTitleCase("#record already opened"))
}
// Compute the form position
function computeFormPosition() {
if (isMobile) {
// Mobile is always fullscreen
return {
top: 0,
left: 0,
width: "100%",
height: "100%"
}
}
if (model.fullscreen) {
// Fullscreen
return {
top: 10,
left: 10,
width: "calc(100% - 20px)",
height: "calc(100% - 20px)"
}
}
else {
if (model.align == "right") {
// Right
return {
top: 0,
left: () => kiss.screen.current.width - Math.min(kiss.screen.current.width / 3 * 2, 1200),
width: () => Math.min(kiss.screen.current.width / 3 * 2, 1200),
height: "100%"
}
}
else {
// Center: the number of records adjust the form panel position / shift
const numberOfOpenedRecords = Array.from(document.querySelectorAll(".form-record")).length
return {
top: 10,
left: () => (kiss.screen.current.width - Math.min(kiss.screen.current.width / 3 * 2, 1000)) / 2 + numberOfOpenedRecords * 10,
width: () => Math.min(kiss.screen.current.width / 3 * 2, 1200),
height: "calc(100% - 20px)"
}
}
}
}
const position = computeFormPosition()
return createPanel({
class: "form-record",
id: record.id,
title: model.name.toTitleCase(),
icon: model.icon,
headerBackgroundColor: model.color,
layout: "horizontal",
position: "fixed",
modal: !isStandAlone,
closable: true,
draggable: !isMobile,
expandable: !isMobile,
top: position.top,
left: position.left,
width: position.width,
height: position.height,
padding: 0,
borderRadius: (isMobile) ? "0px 0px 0px 0px" : "var(--panel-border-radius)",
items: [
{
hidden: true,
class: "form-side-bar",
layout: "vertical",
},
{
layout: "vertical",
flex: 1,
items: [
// Mobile exit button
{
hidden: true,//!isMobile,
id: "mobile-form-exit",
type: "button",
text: "Back",
textAlign: "left",
color: "#ffffff",
backgroundColor: model.color,
icon: "fas fa-chevron-left",
iconColor: "#ffffff",
height: 50,
borderRadius: 0,
action: () => $(record.id).close()
},
// Tab bar
{
class: "form-tabs",
display: "inline",
defaultConfig: {
class: "form-tab",
}
},
// Multiview container for form content + form features
{
class: "form-panels",
multiview: true,
layout: "vertical",
overflow: "hidden",
flex: 1
}
]
}
],
events: {
close: function (forceClose) {
// Closing the form while in stand-alone mode get us back to the home
if (isStandAlone) {
kiss.router.navigateTo({
ui: "home-start"
}, true)
}
// Don't try to validate data when we force to close
if (forceClose) return true
// Prevent from closing if there are invalid values
const isValid = $(record.id).validateContent()
if (!isValid) return false
return true
}
},
subscriptions: {
// Reload the form if its model is updated
"EVT_DB_UPDATE:MODEL": async function (msgData) {
if (msgData.id == modelId) await this.load()
},
// Update the form's record
["EVT_DB_UPDATE:" + model.id.toUpperCase()]: async function (msgData) {
if (msgData.id == record.id) {
Object.assign(this.record, msgData.data)
this.applyHideFormulae()
}
}
},
methods: {
/**
* Load form:
* - bind the form to the record
* - insert default form with fields
* - add form features according to active plugins
* - add one tab + one panel per feature
* - add form headers
* - add form footers
*/
async load() {
let formFeatures = []
let formHeaderFeatures = []
let formFooterFeatures = []
// Bind the record to the form
this.record = record
this.activeFeatureIndex = -1 // Form
// Check if the user can update the model
this.canEditModel = await this.checkIfUserCanEditModel()
// Insert default form with fields
let formPanelFeatures = [createFormContent({
record: this.record,
editMode: !this.record.isLocked // Can edit if the record is locked
})]
if (kiss.app.collections.model) {
let modelRecord = kiss.app.collections.model.getRecord(modelId)
if (modelRecord) {
let modelFeatures = modelRecord.features || {}
// Add form features according to active plugins
this.getActivePlugins(modelFeatures)
.forEach(plugin => {
// Check if the plugin should be loaded for non-admin users
if (plugin.admin == true && !isOwner && !isAccountManager) return
// Check if the plugin is disabled
if (plugin.disabled == true) return
plugin.features
.forEach(feature => {
// Render the plugin view
let featureId = kiss.tools.shortUid()
let newFeatureView = feature.renderer(this)
newFeatureView.classList.add(featureId)
newFeatureView.setAttribute("featureId", featureId)
// Plugins to load as separate sections / tabs
if (feature.type == "form-section") {
formPanelFeatures = formPanelFeatures.concat(newFeatureView)
formFeatures.push({
id: featureId,
pluginId: plugin.id,
icon: plugin.icon,
name: plugin.name
})
}
// Plugins to load in the form header
if (feature.type == "form-header") {
formHeaderFeatures = formHeaderFeatures.concat(newFeatureView)
}
// Plugins to load in the form footer
if (feature.type == "form-footer") {
formFooterFeatures = formFooterFeatures.concat(newFeatureView)
}
})
})
// Add one tab per feature
let tabs = createFormTabBar(this, formFeatures)
let formTabs = this.getFormTabs()
formTabs.setItems(tabs)
// Adjust the tabs underline effect color
const tabElements = formTabs.querySelectorAll(".underline-effect")
tabElements.forEach(tab => tab.style.setProperty("--button-underline-effect", modelRecord.color))
// Adjust form panel header
this.setTitle(modelRecord.name)
this.setIcon(modelRecord.icon)
this.setHeaderBackgroundColor(modelRecord.color)
}
}
// Add one panel per feature
let formPanels = this.getFormPanels()
formPanels.setItems(formPanelFeatures)
// Add form headers
let formHeader = this.getFormHeader()
formHeader.setItems(formHeaderFeatures)
// Add form footers
let formFooter = this.getFormFooter()
formFooter.setItems(formFooterFeatures)
// Build left navigation
let sideMenus = createFormSideBar(this, formFeatures, formHeaderFeatures, formFooterFeatures)
let formSideBar = this.getFormSideBar()
formSideBar.setItems(sideMenus)
// Apply hide formulae
this.applyHideFormulae()
// Restore navigation mode
this.restoreNavigation()
},
/**
* Switch to fullscreen mode if required
*/
// _afterRender() {
// if (model.fullscreen == true && !isMobile) this.maximize(20)
// },
/**
* Check if the active user can update the Model's fields
*/
async checkIfUserCanEditModel() {
if (!kiss.app.collections.model) return false
const modelRecord = kiss.app.collections.model.records.get(model.id)
if (!modelRecord) return false
return await kiss.acl.check({
action: "update",
record: modelRecord
})
},
/**
* Get the model's active features
*/
getActivePlugins(modelFeatures) {
return kiss.plugins.get()
.filter(plugin => {
if (!modelFeatures[plugin.id]) return false
if (modelFeatures[plugin.id].active == false) return false
return true
})
},
/**
* Show a feature / displays its panel
*/
showFeature(featureId) {
const modelRecord = kiss.app.collections.model.getRecord(modelId)
const modelFeatures = modelRecord.features || {}
const activeFeatures = this.getActivePlugins(modelFeatures)
const featureIndex = activeFeatures.findIndex(feature => feature.id == featureId) + 1
const animationName = (featureIndex > this.activeFeatureIndex) ? "slideInRight" : "slideInLeft"
this.activeFeatureIndex = featureIndex
const animation = {
name: animationName,
speed: "faster"
}
const formFeaturesContainer = this.getFormPanels()
formFeaturesContainer.showItemByClass(featureId, animation)
// Adjust the active tab border color
if (this.getNavigationMode() == "tabs") {
const formTabs = $(record.id).querySelector(".form-tabs")
const tabs = formTabs.items
for (let i = 0; i < tabs.length; i++) tabs[i].setBorderColor("var(--button-border)")
tabs[featureIndex + 2].setBorderColor(model.color)
}
},
/**
* Show all sections of the form
*
* @param {string} sectionTitle
*/
showAllSections() {
const content = this.getFormContent()
const formSections = content.querySelectorAll(".a-panel")
Array.from(formSections).forEach(panelElement => $(panelElement.id).show())
},
/**
* Show a single section of the form
*
* @param {string} sectionTitle
*/
showSection(sectionTitle) {
this.showFeature("form-content")
const content = this.getFormContent()
const formSections = content.querySelectorAll(".a-panel")
Array.from(formSections).forEach(panelElement => {
const panel = $(panelElement.id)
if (panel.config.title == sectionTitle) {
panel.show()
panel.expand()
}
else panel.hide()
})
},
/**
* Helpers to access form parts
*/
getFormContent() {
return this.querySelector(".form-content")
},
getFormSections() {
let sections = []
const formContent = this.querySelector(".form-fields")
const formItems = Array.from(formContent.children)
formItems.forEach(item => {
if (item.items) sections.push(item)
})
return sections
},
getFormTabs() {
return this.querySelector(".form-tabs")
},
getFormSideBar() {
return this.querySelector(".form-side-bar")
},
getFormHeader() {
return this.querySelector(".form-header")
},
getFormFooter() {
return this.querySelector(".form-footer")
},
getFormPanels() {
return this.querySelector(".form-panels")
},
getFormFields() {
return this.querySelector(".form-fields")
},
getNavigationMode() {
return localStorage.getItem("config-formNavigationMode-" + model.id)
},
/**
* Show / hide tab bar and side bar
*/
showTabBar() {
const tabs = this.getFormTabs()
tabs.setAnimation({
name: "slideInLeft",
speed: "faster"
}).show()
},
hideTabBar() {
const tabs = this.getFormTabs()
tabs.hide()
},
showSideBar() {
const sideBar = this.getFormSideBar()
sideBar.show()
},
hideSideBar() {
const sideBar = this.getFormSideBar()
sideBar.hide()
},
switchNavigation(mode) {
localStorage.setItem("config-formNavigationMode-" + model.id, mode)
if (mode == "left") {
this.hideTabBar()
this.showSideBar()
}
else {
this.hideSideBar()
this.showTabBar()
this.showAllSections()
}
},
hideNavigation() {
this.hideTabBar()
this.hideSideBar()
},
restoreNavigation() {
const navigationMode = localStorage.getItem("config-formNavigationMode-" + model.id)
if (navigationMode == "left") {
this.hideTabBar()
this.showSideBar()
}
else {
this.hideSideBar()
this.showTabBar()
}
},
validateContent() {
const formContent = this.getFormContent()
const isValid = formContent.validate()
if (!isValid) return false
return true
},
applyHideFormulae() {
const isDesigner = (kiss.router.getRoute().ui == "form-designer")
if (isDesigner) return
this.applyHideFormulaeToSections()
this.applyHideFormulaeToFields()
},
applyHideFormulaeToSections() {
const sections = this.getFormSections()
sections.forEach(section => {
const sectionElement = this.querySelector("#" + section.id.replaceAll(":", "\\:"))
if (!sectionElement) return
const hideWhen = sectionElement.config.hideWhen
if (!hideWhen) return
const hideFormula = section.config.hideFormula
if (!hideFormula) return
try {
kiss.context.record = record
const result = kiss.formula.execute(hideFormula, record, model.getActiveFields())
if (result === true) sectionElement.hide()
else sectionElement.show()
}
catch(err) {
log("kiss.ui - Warning: could not hide the section " + section)
}
})
},
applyHideFormulaeToFields() {
const formContent = this.getFormContent()
const fields = formContent.getFields()
fields.forEach(field => {
const fieldElement = this.querySelector("#" + field.id.replaceAll(":", "\\:"))
if (!fieldElement) return
const hideWhen = fieldElement.config.hideWhen
if (!hideWhen) return
const hideFormula = fieldElement.config.hideFormula
if (!hideFormula) return
try {
kiss.context.record = record
const result = kiss.formula.execute(hideFormula, record, model.getActiveFields())
if (result === true) fieldElement.hide()
else fieldElement.show()
}
catch(err) {
log("kiss.ui - Warning: could not hide the field " + field)
}
})
}
}
}).render()
}
;
Source