230 lines
6.1 KiB
JavaScript
230 lines
6.1 KiB
JavaScript
import { globalStore } from '@/stores/global'
|
|
import { createToast } from '@/utils'
|
|
import { call, createListResource } from 'frappe-ui'
|
|
import { reactive } from 'vue'
|
|
import router from '@/router'
|
|
|
|
const doctypeScripts = reactive({})
|
|
|
|
export function getScript(doctype, view = 'Form') {
|
|
const scripts = createListResource({
|
|
doctype: 'CRM Form Script',
|
|
cache: ['Form Scripts', doctype, view],
|
|
fields: ['name', 'dt', 'view', 'script'],
|
|
filters: { view, dt: doctype, enabled: 1 },
|
|
onSuccess: (_scripts) => {
|
|
for (let script of _scripts) {
|
|
if (!doctypeScripts[doctype]) {
|
|
doctypeScripts[doctype] = {}
|
|
}
|
|
doctypeScripts[doctype][script.name] = script || {}
|
|
}
|
|
},
|
|
})
|
|
|
|
if (!doctypeScripts[doctype] && !scripts.loading) {
|
|
scripts.fetch()
|
|
}
|
|
|
|
function setupScript(document, helpers = {}) {
|
|
let scripts = doctypeScripts[doctype]
|
|
if (!scripts) return null
|
|
|
|
const { $dialog, $socket, makeCall } = globalStore()
|
|
|
|
helpers.createDialog = $dialog
|
|
helpers.createToast = createToast
|
|
helpers.socket = $socket
|
|
helpers.router = router
|
|
helpers.call = call
|
|
|
|
helpers.crm = {
|
|
makePhoneCall: makeCall,
|
|
}
|
|
|
|
return setupMultipleFormControllers(scripts, document, helpers)
|
|
}
|
|
|
|
function setupMultipleFormControllers(scriptStrings, document, helpers) {
|
|
const controllers = {}
|
|
|
|
for (let scriptName in scriptStrings) {
|
|
let script = scriptStrings[scriptName]?.script
|
|
if (!script) continue
|
|
try {
|
|
const classNames = getClassNames(script)
|
|
if (!classNames) continue
|
|
|
|
classNames.forEach((className) => {
|
|
if (!className) {
|
|
if (script.includes('setupForm(')) {
|
|
let message = __(
|
|
'setupForm() is deprecated, use class syntax instead. Check the documentation for more details.',
|
|
)
|
|
createToast({
|
|
title: __('Deprecation Warning'),
|
|
text: message,
|
|
icon: 'alert-triangle',
|
|
iconClasses: 'text-orange-500',
|
|
timeout: 10,
|
|
})
|
|
console.warn(message)
|
|
}
|
|
throw new Error(__('No class found in script'))
|
|
}
|
|
|
|
const FormClass = evaluateFormClass(script, className, helpers)
|
|
if (!FormClass) return
|
|
|
|
let parentInstance = null
|
|
let doctypeName = doctype.replace(/\s+/g, '')
|
|
|
|
// if className is not doctype name, then it is a child doctype
|
|
let isChildDoctype = className !== doctypeName
|
|
if (isChildDoctype) {
|
|
parentInstance = controllers[doctypeName]
|
|
}
|
|
|
|
controllers[className] = setupFormController(
|
|
FormClass,
|
|
document,
|
|
parentInstance,
|
|
isChildDoctype,
|
|
)
|
|
})
|
|
} catch (err) {
|
|
console.error('Failed to load form controller:', err)
|
|
}
|
|
}
|
|
|
|
return controllers
|
|
}
|
|
|
|
function setupFormController(
|
|
FormClass,
|
|
document,
|
|
parentInstance = null,
|
|
isChildDoctype = false,
|
|
) {
|
|
let instance = new FormClass()
|
|
|
|
for (const key in document) {
|
|
if (document.hasOwnProperty(key)) {
|
|
instance[key] = document[key]
|
|
}
|
|
}
|
|
|
|
if (isChildDoctype) {
|
|
setupHelperMethods(FormClass, instance, document)
|
|
instance.doc = createDocProxy(document.doc, parentInstance)
|
|
} else {
|
|
instance.doc = createDocProxy(document.doc, instance)
|
|
}
|
|
|
|
instance.actions = (instance.actions || []).filter(
|
|
(action) => typeof action.condition !== 'function' || action.condition(),
|
|
)
|
|
|
|
return instance
|
|
}
|
|
|
|
function setupHelperMethods(FormClass, instance, document) {
|
|
FormClass.prototype.getRow = (parentField, idx) =>
|
|
getRow(parentField, idx, document.doc, instance)
|
|
|
|
exposeHiddenMethods(document.doc, instance, ['getRow'])
|
|
}
|
|
|
|
function getRow(parentField, idx, data, instance) {
|
|
idx = idx || instance.currentRowIdx
|
|
|
|
if (!data[parentField]) {
|
|
console.warn(`⚠️ No data found for parent field: ${parentField}`)
|
|
return null
|
|
}
|
|
const row = data[parentField].find((r) => r.idx === idx)
|
|
|
|
if (!row) {
|
|
console.warn(
|
|
`⚠️ No row found for idx: ${idx} in parent field: ${parentField}`,
|
|
)
|
|
return null
|
|
}
|
|
|
|
return createDocProxy(row, instance)
|
|
}
|
|
|
|
// utility function to setup a form controller
|
|
function getClassNames(script) {
|
|
return (
|
|
[...script.matchAll(/class\s+([A-Za-z0-9_]+)/g)].map(
|
|
(match) => match[1],
|
|
) || []
|
|
)
|
|
}
|
|
|
|
function evaluateFormClass(script, className, helpers = {}) {
|
|
const helperKeys = Object.keys(helpers)
|
|
const helperValues = Object.values(helpers)
|
|
|
|
const wrappedScript = `
|
|
${script}
|
|
return ${className};
|
|
`
|
|
|
|
const FormClass = new Function(...helperKeys, wrappedScript)(
|
|
...helperValues,
|
|
)
|
|
return FormClass
|
|
}
|
|
|
|
function createDocProxy(data, instance) {
|
|
return new Proxy(data, {
|
|
get(target, prop) {
|
|
if (prop === 'trigger') {
|
|
if ('trigger' in data) {
|
|
console.warn(
|
|
`⚠️ Avoid using "trigger" as a field name — it conflicts with the built-in trigger() method.`,
|
|
)
|
|
}
|
|
|
|
return (methodName, ...args) => {
|
|
const method = instance[methodName]
|
|
if (typeof method === 'function') {
|
|
return method.apply(instance, args)
|
|
} else {
|
|
console.warn(`⚠️ Method "${methodName}" not found in class.`)
|
|
}
|
|
}
|
|
}
|
|
|
|
return target[prop]
|
|
},
|
|
set(target, prop, value) {
|
|
target[prop] = value
|
|
return true
|
|
},
|
|
})
|
|
}
|
|
|
|
function exposeHiddenMethods(doc, instance, methodNames = []) {
|
|
for (const name of methodNames) {
|
|
if (typeof instance[name] === 'function') {
|
|
// Show as actual method on doc, bound to instance
|
|
Object.defineProperty(doc, name, {
|
|
value: (...args) => instance[name](...args),
|
|
writable: false,
|
|
enumerable: false,
|
|
configurable: false,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
scripts,
|
|
setupScript,
|
|
setupFormController,
|
|
}
|
|
}
|