Files
mixtape/zero.Backoffice.UI/app/components/ui-form.vue
T

440 lines
10 KiB
Vue
Raw Normal View History

2020-04-08 16:14:12 +02:00
<template>
2020-06-16 12:31:04 +02:00
<form class="ui-form" @keydown="onKeydown" @submit.prevent="onSubmit" @change="onChange">
2020-10-15 14:10:04 +02:00
<slot v-if="loadingState === 'default'" v-bind="slotProps" />
2020-04-19 18:22:44 +02:00
<div v-if="loadingState == 'loading'" class="ui-form-loading">
<i class="ui-form-loading-progress"></i>
</div>
<div v-if="loadingState === 'error'">
error [not implemented]
</div>
2021-12-23 14:39:00 +01:00
<!--<form-error-view v-if="loadingState === 'error'" :error="loadingError" />-->
2020-04-08 16:14:12 +02:00
</form>
</template>
2021-12-09 14:18:38 +01:00
<script lang="ts">
import { defineComponent } from 'vue';
//import FormErrorView from './form-error-view.vue';
2021-12-14 16:06:54 +01:00
import * as overlays from '../services/overlay';
2021-12-09 14:18:38 +01:00
import * as notifications from '../services/notification';
import { arrayGroupBy } from '../utils/arrays';
2020-04-08 23:33:56 +02:00
2021-12-09 14:18:38 +01:00
export default defineComponent({
2020-04-08 16:14:12 +02:00
name: 'uiForm',
//components: { FormErrorView },
2021-12-09 14:18:38 +01:00
2020-04-09 15:53:03 +02:00
props: {
2020-04-10 11:12:11 +02:00
errorComponents: {
type: Array,
2021-12-09 14:18:38 +01:00
default: () => ['uiError']
2020-04-28 14:51:17 +02:00
},
inputComponents: {
type: Array,
2021-12-09 14:18:38 +01:00
default: () => ['uiProperty']
2020-06-23 15:44:14 +02:00
},
route: {
2020-08-31 14:33:31 +02:00
type: [String, Object],
2020-06-23 15:44:14 +02:00
default: null
2020-04-09 15:53:03 +02:00
}
},
2020-04-08 16:14:12 +02:00
data: () => ({
dirty: false,
2020-11-19 00:14:52 +01:00
loadingState: 'default',
loadingError: null,
2020-04-10 11:12:11 +02:00
state: 'default',
errors: [],
2020-04-28 14:51:17 +02:00
canEdit: true,
2020-10-15 14:10:04 +02:00
slotProps: {
state: null
2020-10-15 14:10:04 +02:00
},
2020-06-16 12:31:04 +02:00
submitBlocked: false
2020-04-08 16:14:12 +02:00
}),
2020-04-10 11:12:11 +02:00
watch: {
2022-01-12 17:15:05 +01:00
'$route': {
deep: true,
handler: function (val)
{
this.$nextTick(() =>
{
this.$emit('load', this);
});
}
},
2020-10-15 14:10:04 +02:00
state(val)
{
this.slotProps.state = val;
},
2020-04-28 14:51:17 +02:00
canEdit(val)
{
this.$nextTick(() =>
{
this.setCanEdit(val);
});
2020-04-10 11:12:11 +02:00
}
},
2020-06-23 15:44:14 +02:00
2020-04-08 16:14:12 +02:00
created()
{
2020-10-15 14:10:04 +02:00
this.slotProps.state = this.state;
this.$emit('load', this);
2020-04-08 16:14:12 +02:00
},
2020-06-23 15:44:14 +02:00
2020-04-08 16:14:12 +02:00
methods: {
2020-04-09 15:53:03 +02:00
// shows a confirmation dialog for dirty forms when the route tries to change
// it only works when this method is attached to the route component
2020-04-08 23:33:56 +02:00
beforeRouteLeave(to, from, next)
2021-12-09 14:18:38 +01:00
{
2020-04-08 23:33:56 +02:00
if (this.dirty)
2020-04-08 16:14:12 +02:00
{
Overlay.confirm({
2020-04-24 19:38:33 +02:00
title: '@unsavedchanges.title',
text: '@unsavedchanges.text',
confirmLabel: '@unsavedchanges.confirm',
closeLabel: '@unsavedchanges.close'
}).then(
() => next(false),
2020-11-20 15:17:41 +01:00
() =>
{
this.dirty = false;
next();
}
);
2020-04-08 23:33:56 +02:00
}
else
{
next()
2020-04-08 16:14:12 +02:00
}
},
// loads data on creation of the form
2021-12-14 14:21:48 +01:00
async load(promise)
{
this.setState('loading');
2021-12-14 14:21:48 +01:00
const response = await promise();
2021-12-14 14:21:48 +01:00
if (!response.success)
{
this.setState('error');
if (response.errors)
{
this.loadingError = response.errors[0].message;
}
2021-12-14 14:21:48 +01:00
return null;
}
2020-06-03 13:44:56 +02:00
2021-12-14 14:21:48 +01:00
this.canEdit = true;
this.setState('default');
2021-12-14 14:21:48 +01:00
this.$nextTick(() =>
{
this.$emit('loaded', this);
});
2021-12-14 14:21:48 +01:00
return response.data;
},
// handles a promise as result of the form submission
async handle(response, isCreate)
2020-04-10 01:17:16 +02:00
{
this.setState('loading');
this.clearErrors();
2020-04-10 01:17:16 +02:00
if (!response.success)
2020-04-28 15:45:26 +02:00
{
this.setState('error');
this.setErrors(response.errors);
return null;
}
2020-04-28 15:45:26 +02:00
this.setState('success');
this.setDirty(false);
if (response.data && this.route && response.status === 201)
2020-04-10 01:17:16 +02:00
{
let routeObj = typeof this.route === 'object' ? this.route : { name: this.route };
routeObj.params = routeObj.params || {};
routeObj.query = this.$route.query || {};
routeObj.params.id = response.data.id;
this.$router.replace(routeObj);
}
2020-04-10 01:17:16 +02:00
},
2020-04-08 23:33:56 +02:00
2020-04-09 15:53:03 +02:00
// submits the form
2020-04-10 01:17:16 +02:00
onSubmit(e)
2020-04-08 16:14:12 +02:00
{
2020-06-16 12:31:04 +02:00
if (!this.submitBlocked)
{
this.$emit('submit', this, e);
}
2020-04-08 23:33:56 +02:00
},
2020-04-09 15:53:03 +02:00
// set the form to dirty when one of the fields changes
2020-04-10 01:17:16 +02:00
onChange(e)
2020-04-08 23:33:56 +02:00
{
2020-04-09 15:53:03 +02:00
this.dirty = true;
2020-04-10 01:17:16 +02:00
},
2020-06-23 15:44:14 +02:00
// handle delete event
2021-12-14 16:06:54 +01:00
async onDelete(promise)
2020-06-23 15:44:14 +02:00
{
2021-12-14 16:06:54 +01:00
const overlay = await overlays.confirmDelete();
2021-12-09 14:18:38 +01:00
2021-12-14 16:06:54 +01:00
if (overlay.eventType == 'close')
{
return;
}
const opts = overlay.value;
opts.state('loading');
const response = await promise();
if (response.success)
{
opts.state('success');
opts.close();
this.$router.go(-1);
notifications.success('@deleteoverlay.success', '@deleteoverlay.success_text');
}
else
{
opts.state('error');
opts.errors(response.errors);
}
2020-06-23 15:44:14 +02:00
},
2020-06-16 12:31:04 +02:00
// prevent submission of form on enter key press
onKeydown(e)
{
if (e.keyCode === 13)
{
this.submitBlocked = true;
clearTimeout(this.submitBlockedTimeout);
this.submitBlockedTimeout = setTimeout(() => this.submitBlocked = false, 300);
}
},
2020-04-10 01:17:16 +02:00
// set dirty status
setDirty(dirty)
{
this.dirty = dirty;
},
// set state for this component and all child components which attached it's state from the slot props
2020-04-10 01:17:16 +02:00
setState(state)
{
2020-04-10 11:12:11 +02:00
this.state = state;
2020-04-09 15:53:03 +02:00
},
// clears all errors from the form
clearErrors()
{
this.getErrorComponents().forEach(component =>
{
2020-09-09 13:43:01 +02:00
component.clearErrors();
if (component.tab)
{
component.tab.clearErrors();
}
});
},
2020-04-09 15:53:03 +02:00
// tries to find matching fields for the given errors and displays them
setErrors(errors)
2020-04-09 15:53:03 +02:00
{
if (typeof errors === 'undefined' || !errors)
2020-04-09 15:53:03 +02:00
{
this.errors = [];
}
else
{
2021-12-09 14:18:38 +01:00
this.errors = !Array.isArray(errors) ? [errors] : errors;
2020-04-09 15:53:03 +02:00
}
// get all components + grouped errors
let errorComponents = this.getErrorComponents();
2021-12-09 14:18:38 +01:00
let errorGroups = arrayGroupBy(this.errors, 'property');
2020-04-15 15:13:38 +02:00
let handledGroups = [];
// set errors
errorComponents.forEach(component =>
{
let field = component.field;
if (field && errorGroups[field])
{
handledGroups.push(field);
2020-09-09 13:43:01 +02:00
component.setErrors(errorGroups[field]);
if (component.tab)
{
component.tab.setErrors(true);
}
}
});
2021-12-09 14:18:38 +01:00
for (var field in errorGroups)
{
2021-12-09 14:18:38 +01:00
let errorGroup = errorGroups[field];
if (handledGroups.indexOf(field) < 0)
{
errorComponents.forEach(component =>
{
if (component.catchRemaining || component.catchAll)
{
2020-09-09 13:43:01 +02:00
component.setErrors(errorGroup, true);
if (component.tab)
{
component.tab.setErrors(true);
}
}
});
}
2021-12-09 14:18:38 +01:00
}
},
// find all error components in form
getErrorComponents()
{
2020-04-15 15:13:38 +02:00
let errorComponents = [];
// find components which can output errors
2020-09-09 13:43:01 +02:00
let traverseChildren = (parent, tab) =>
{
parent.$children.forEach(component =>
{
if (this.errorComponents.indexOf(component.$options.name) > -1)
2020-04-15 15:13:38 +02:00
{
2020-09-09 13:43:01 +02:00
errorComponents.push({
name: component.$options.name,
field: component.field,
catchAll: component.catchAll,
catchRemaining: component.catchRemaining,
component: component,
setErrors: component.setErrors,
clearErrors: component.clearErrors,
tab: tab
});
2020-04-15 15:13:38 +02:00
}
else
{
2020-09-09 13:43:01 +02:00
traverseChildren(component, tab || (component.$options.name === 'uiTab' ? component : null));
}
});
};
2021-12-09 14:18:38 +01:00
traverseChildren(this);
2020-04-15 15:13:38 +02:00
return errorComponents;
2020-04-28 14:51:17 +02:00
},
// sets the form editing ability
setCanEdit(canEdit)
{
// find components which can output errors
//let traverseChildren = parent =>
//{
// parent.$children.forEach(component =>
// {
// const isValidComponent = this.inputComponents.indexOf(component.$options.name) > -1;
// //const field = component.field;
// if (typeof component._props.disabled === 'boolean')
// {
// component.$emit('update:disabled', canEdit);
// }
// else
// {
// traverseChildren(component);
// }
// //if (isErrorComponent)
// //{
// // errorComponents.push(component);
// //}
// //if (isErrorComponent && field)
// //{
// // let errorGroup = errorGroups[field];
// // if (errorGroup)
// // {
// // handledGroups.push(field);
// // component.set(errorGroup);
// // }
// //}
// //else
// //{
// // traverseChildren(component);
// //}
// });
//};
//traverseChildren(this);
//console.info('done');
2020-04-10 01:17:16 +02:00
}
2020-04-08 16:14:12 +02:00
}
2021-12-09 14:18:38 +01:00
});
</script>
<style lang="scss">
.ui-form
{
min-height: 100%;
2020-04-21 16:23:43 +02:00
font-size: var(--font-size);
}
2020-04-19 18:22:44 +02:00
.ui-form-loading
{
display: flex;
width: 100%;
height: 100vh;
align-items: center;
justify-content: center;
}
.ui-form-loading-progress
{
width: 32px;
height: 32px;
z-index: 2;
border-radius: 40px;
border: 2px solid var(--color-bg-shade-3);
border-left-color: var(--color-text);
2020-04-19 18:22:44 +02:00
opacity: 1;
will-change: transform;
animation: rotating .5s linear infinite;
transition: opacity .25s ease;
}
@keyframes rotating
{
from
{
-webkit-transform: rotate(0);
transform: rotate(0)
}
to
{
-webkit-transform: rotate(1turn);
transform: rotate(1turn)
}
}
</style>