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.

239 lines
9.1 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">
<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"
>
<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]
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 = ''
}
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)
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()
values[field.key] = value
})
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)
}
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)
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
}
}