You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
274 lines
10 KiB
274 lines
10 KiB
import {Component} from '../../vues6.js'
|
|
import {Status} from './Listing.component.js'
|
|
import {Resource, ResourceActions} from '../Resource.js'
|
|
import {uuid} from '../util.js'
|
|
|
|
const template = `
|
|
<div class="card shadow mb-4 col-lg-6 offset-lg-3" style="padding: 0">
|
|
<div class="card-header py-3">
|
|
{{ title }}
|
|
</div>
|
|
<div class="card-body">
|
|
<div v-if="status === 'loading'">Loading...</div>
|
|
<form v-if="status === 'ready'">
|
|
<div class="form-group" v-for="field of fields">
|
|
<input
|
|
v-if="field.type === 'bool'"
|
|
class="form-check-inline"
|
|
type="checkbox"
|
|
:name="field.key"
|
|
v-model="data[field.key]"
|
|
:id="id + field.key"
|
|
:aria-describedby="id + field.key + '_help'"
|
|
:readonly="mode === 'view' || field.readonly"
|
|
:required="field.required"
|
|
>
|
|
<label for="id + field.key">{{ field.display }}</label>
|
|
<input
|
|
v-if="field.type === 'text' || field.type === 'email' || field.type === 'number' || field.type === 'integer' || !field.type"
|
|
:type="field.type || 'text'"
|
|
class="form-control"
|
|
:name="field.key"
|
|
v-model="data[field.key]"
|
|
:required="field.required"
|
|
:id="id + field.key"
|
|
:aria-describedby="id + field.key + '_help'"
|
|
:placeholder="field.placeholder || ''"
|
|
:readonly="mode === 'view' || field.readonly"
|
|
:step="field.type === 'number' ? 'any' : undefined"
|
|
>
|
|
<textarea
|
|
class="form-control"
|
|
v-if="field.type === 'textarea'"
|
|
v-model="data[field.key]"
|
|
:name="field.key"
|
|
:id="id + field.key"
|
|
:aria-describedby="id + field.key + '_help'"
|
|
:placeholder="field.placeholder || ''"
|
|
:readonly="mode === 'view' || field.readonly"
|
|
:required="field.required"
|
|
rows="10"
|
|
></textarea>
|
|
<input
|
|
v-if="field.type === 'date' && (mode === 'view' || field.readonly)"
|
|
class="form-control"
|
|
:name="field.key"
|
|
:value="data[field.key].toDisplay()"
|
|
:required="field.required"
|
|
:id="id + field.key"
|
|
:aria-describedby="id + field.key + '_help'"
|
|
:placeholder="field.placeholder || ''"
|
|
:readonly="mode === 'view' || field.readonly"
|
|
>
|
|
<cobalt-monaco
|
|
v-if="field.type === 'html' && !(mode === 'view' || field.readonly)"
|
|
syntax="html"
|
|
:initialvalue="data[field.key]"
|
|
@changed="value => data[field.key] = value"
|
|
></cobalt-monaco>
|
|
<div
|
|
v-if="field.type === 'html' && (mode === 'view' || field.readonly)"
|
|
v-html="data[field.key]"
|
|
></div>
|
|
<DatePicker
|
|
v-model="data[field.key]"
|
|
v-if="field.type === 'date' && !(mode === 'view' || field.readonly)"
|
|
:type="field.renderer || 'date'"
|
|
:id="id + field.key"
|
|
:aria-describedby="id + field.key + '_help'"
|
|
:clearable="!field.required"
|
|
input-class="form-control"
|
|
style="display: inherit !important; width: 100%;"
|
|
></DatePicker>
|
|
<small
|
|
class="form-text text-muted"
|
|
:id="id + field.key + '_help'"
|
|
v-if="field.helpText"
|
|
>{{ field.helpText }}</small>
|
|
<small
|
|
class="form-text"
|
|
style="color: darkred"
|
|
:id="id + field.key + '_error'"
|
|
v-if="errors[field.key]"
|
|
>{{ errors[field.key] }}</small>
|
|
</div>
|
|
<div style="display: flex; justify-content: end">
|
|
<div
|
|
class="text-muted"
|
|
style="padding: 5px; margin-right: 20px"
|
|
>{{ statusMessage }}</div>
|
|
<cobalt-action-button
|
|
:action="{overall: true, color: 'secondary', icon: 'fa-angle-left', title: 'Cancel', type: 'back'}"
|
|
style="margin-right: 10px !important;"
|
|
></cobalt-action-button>
|
|
<button
|
|
v-if="mode === 'edit'"
|
|
class="btn btn-primary btn-icon-split"
|
|
type="button"
|
|
@click="save"
|
|
>
|
|
<span class="icon text-white-50">
|
|
<i class="fas fa-save"></i>
|
|
</span>
|
|
<span class="text">Save</span>
|
|
</button>
|
|
<button
|
|
v-if="mode === 'insert'"
|
|
class="btn btn-success btn-icon-split"
|
|
type="button"
|
|
@click="save"
|
|
>
|
|
<span class="icon text-white-50">
|
|
<i class="fas fa-plus"></i>
|
|
</span>
|
|
<span class="text">Create</span>
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
`
|
|
export class FormComponent extends Component {
|
|
static get selector() { return 'cobalt-form' }
|
|
static get template() { return template }
|
|
static get props() { return ['resourcekey', 'resourceid', 'resourcemode'] }
|
|
|
|
constructor() {
|
|
super()
|
|
|
|
this.status = Status.loading
|
|
this.title = 'Loading...'
|
|
this.statusMessage = 'Loading...'
|
|
this.fields = []
|
|
this.data = {}
|
|
this.actions = []
|
|
this.unregister = []
|
|
this.mode = 'edit'
|
|
this.id = uuid()
|
|
this.internalResourceId = 0
|
|
this.errors = {}
|
|
|
|
/** @var {Resource} */
|
|
this.resource = undefined
|
|
}
|
|
|
|
async vue_on_create() {
|
|
this.resource = await Resource.get(this.resourcekey)
|
|
this.title = this.resource.singular()
|
|
this.mode = this.resourcemode || this.mode
|
|
this.internalResourceId = this.resourceid || this.internalResourceId
|
|
|
|
if ( !this.resource.supports(ResourceActions.readOne) ) {
|
|
this.status = Status.errorUnsupported
|
|
}
|
|
|
|
await this.load()
|
|
|
|
console.log('form', this)
|
|
}
|
|
|
|
async load() {
|
|
this.data = this.internalResourceId ? await this.resource.readOne(this.internalResourceId) : {}
|
|
this.fields = [...this.resource.configuration.fields].filter(f => {
|
|
return !(f.hideOn && f.hideOn.form)
|
|
})
|
|
|
|
this.fields.forEach(field => {
|
|
if ( field.type === 'date' && this.data[field.key] ) {
|
|
this.data[field.key] = new Date(this.data[field.key])
|
|
}
|
|
})
|
|
|
|
if ( this.resource.configuration.display.field ) {
|
|
this.title = this.resource.singular()
|
|
if ( this.internalResourceId ) this.title = `${this.title}: ${this.data[this.resource.configuration.display.field]}`
|
|
}
|
|
|
|
this.status = Status.ready
|
|
this.statusMessage = ''
|
|
}
|
|
|
|
onMonacoChange(field, data, args) {
|
|
console.log('on monaco change', {field, data, args})
|
|
this.data[field.key] = args[0]
|
|
console.log('after save', this.data)
|
|
}
|
|
|
|
async save() {
|
|
if ( !this.validate() ) return;
|
|
|
|
this.statusMessage = 'Saving...'
|
|
|
|
const values = {}
|
|
this.fields.forEach(field => {
|
|
let value = this.data[field.key]
|
|
const isUndef = !value && !(['integer', 'number'].includes(field.type) && value === 0) && (field.type !== 'bool')
|
|
if ( isUndef ) return;
|
|
|
|
if ( field.type === 'number' ) value = parseFloat(String(value))
|
|
if ( field.type === 'integer' ) value = parseInt(String(value), 10)
|
|
if ( field.type === 'date' ) value = value.toISOString()
|
|
if ( field.type === 'bool' ) value = !!value
|
|
|
|
values[field.key] = value
|
|
})
|
|
|
|
try {
|
|
if (this.internalResourceId) {
|
|
await this.resource.update(this.internalResourceId, values)
|
|
} else {
|
|
const result = await this.resource.create(values)
|
|
this.internalResourceId = result[this.resource.configuration.primaryKey]
|
|
history.replaceState({}, document.getElementsByTagName('title')[0].innerText, `${location.protocol}//${location.host}/dash/cobalt/form/${this.resource.key}/${this.internalResourceId}`)
|
|
}
|
|
|
|
await this.load()
|
|
|
|
this.statusMessage = `${this.resource.singular()} ${this.mode === 'insert' ? 'created' : 'saved'}`
|
|
this.mode = 'edit'
|
|
setTimeout(() => this.statusMessage = '', 7000)
|
|
} catch (e) {
|
|
console.error(e)
|
|
this.statusMessage = 'An unknown error occurred while saving'
|
|
}
|
|
}
|
|
|
|
validate() {
|
|
let pass = true
|
|
this.fields.forEach(field => this.errors[field.key] = undefined)
|
|
|
|
this.fields.forEach(field => {
|
|
// FIXME select
|
|
|
|
const value = this.data[field.key]
|
|
const isUndef = !value && !(['integer', 'number'].includes(field.type) && value === 0) && (field.type !== 'bool')
|
|
if ( field.required && isUndef ) {
|
|
this.errors[field.key] = 'This field is required'
|
|
pass = false
|
|
return
|
|
}
|
|
|
|
if ( field.type === 'date' && !isUndef && isNaN(value.getHours()) ) {
|
|
this.errors[field.key] = `Invalid ${field.renderer || 'date'}`
|
|
pass = false
|
|
return
|
|
}
|
|
|
|
if ( field.type === 'number' && !isUndef && isNaN(parseFloat(String(value))) ) {
|
|
this.errors[field.key] = 'Invalid number'
|
|
pass = false
|
|
return
|
|
}
|
|
|
|
if ( field.type === 'integer' && !isUndef && (isNaN(parseInt(String(value), 10)) || parseFloat(String(value)) !== parseInt(String(value), 10)) ) {
|
|
this.errors[field.key] = 'Invalid integer'
|
|
pass = false
|
|
return
|
|
}
|
|
})
|
|
|
|
this.$nextTick(() => this.$forceUpdate())
|
|
return pass
|
|
}
|
|
}
|