mirror of
https://github.com/frappe/books.git
synced 2024-11-13 00:46:28 +00:00
fix: QuickEditForm
- Actions dropdown - Image field - Status text position - Error message - Dynamically change title size on input - Delete action - Use TwoColumnForm
This commit is contained in:
parent
97ad435ecb
commit
f6ed9fc390
@ -1,175 +1,266 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="border-l h-full">
|
<div class="border-l h-full">
|
||||||
<div class="flex justify-end px-4 pt-4">
|
<div class="flex items-center justify-between px-4 pt-4">
|
||||||
<Button :icon="true" @click="$router.back()">
|
<div class="flex items-center">
|
||||||
<XIcon class="w-3 h-3 stroke-current text-gray-700" />
|
<Button :icon="true" @click="routeToList">
|
||||||
</Button>
|
<feather-icon name="x" class="w-4 h-4" />
|
||||||
<Button
|
</Button>
|
||||||
:icon="true"
|
<span v-if="statusText" class="ml-2 text-base text-gray-600">{{
|
||||||
@click="insertDoc"
|
statusText
|
||||||
type="primary"
|
}}</span>
|
||||||
v-if="doc._notInserted"
|
</div>
|
||||||
class="ml-2 flex"
|
<div class="flex items-stretch">
|
||||||
>
|
<Dropdown v-if="actions" :items="actions" right class="text-base">
|
||||||
<feather-icon name="check" class="text-white" />
|
<template v-slot="{ toggleDropdown }">
|
||||||
</Button>
|
<Button
|
||||||
<Button
|
class="text-gray-900"
|
||||||
:icon="true"
|
:icon="true"
|
||||||
@click="submitDoc"
|
@click="toggleDropdown()"
|
||||||
type="primary"
|
>
|
||||||
v-if="meta && meta.isSubmittable && !doc.submitted && !doc._notInserted"
|
<feather-icon name="more-horizontal" class="w-4 h-4" />
|
||||||
class="ml-2 flex"
|
</Button>
|
||||||
>
|
</template>
|
||||||
<feather-icon name="lock" class="text-white" />
|
</Dropdown>
|
||||||
</Button>
|
<Button
|
||||||
|
:icon="true"
|
||||||
|
@click="insertDoc"
|
||||||
|
type="primary"
|
||||||
|
v-if="doc && doc._notInserted"
|
||||||
|
class="ml-2 text-white text-xs"
|
||||||
|
>
|
||||||
|
{{ _('Save') }}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
:icon="true"
|
||||||
|
@click="submitDoc"
|
||||||
|
type="primary"
|
||||||
|
v-if="
|
||||||
|
meta &&
|
||||||
|
meta.isSubmittable &&
|
||||||
|
doc &&
|
||||||
|
!doc.submitted &&
|
||||||
|
!doc._notInserted
|
||||||
|
"
|
||||||
|
class="ml-2 text-white text-xs"
|
||||||
|
>
|
||||||
|
{{ _('Submit') }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pl-1 pr-4 pt-2 pb-4 border-b flex items-center justify-between">
|
<div class="px-4 pt-2 pb-4 flex items-center justify-center" v-if="doc">
|
||||||
<FormControl
|
<div class="flex flex-col items-center">
|
||||||
ref="titleControl"
|
|
||||||
v-if="titleDocField"
|
|
||||||
:df="titleDocField"
|
|
||||||
:value="doc[titleDocField.fieldname]"
|
|
||||||
@change="value => valueChange(titleDocField, value)"
|
|
||||||
/>
|
|
||||||
<span v-if="statusText" class="text-xs text-gray-600">{{ statusText }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="text-xs">
|
|
||||||
<template v-for="df in fields">
|
|
||||||
<FormControl
|
<FormControl
|
||||||
|
v-if="imageField"
|
||||||
|
:df="imageField"
|
||||||
|
:value="doc[imageField.fieldname]"
|
||||||
|
@change="value => valueChange(imageField, value)"
|
||||||
size="small"
|
size="small"
|
||||||
v-if="df.fieldtype === 'Table'"
|
class="mb-1"
|
||||||
:df="df"
|
:letter-placeholder="
|
||||||
:value="doc[df.fieldname]"
|
doc[titleField.fieldname] ? doc[titleField.fieldname][0] : null
|
||||||
@change="value => valueChange(df, value)"
|
"
|
||||||
/>
|
/>
|
||||||
<div v-else class="grid border-b" style="grid-template-columns: 1fr 2fr">
|
<FormControl
|
||||||
<div class="py-2 pl-4 text-gray-600 flex items-center">{{ df.label }}</div>
|
input-class="text-center"
|
||||||
<div class="py-2 pr-4">
|
ref="titleControl"
|
||||||
<FormControl
|
v-if="titleField"
|
||||||
size="small"
|
:df="titleField"
|
||||||
:df="df"
|
:value="doc[titleField.fieldname]"
|
||||||
:value="doc[df.fieldname]"
|
@change="value => valueChange(titleField, value)"
|
||||||
@change="value => valueChange(df, value)"
|
@input="setTitleSize"
|
||||||
@new-doc="doc => valueChange(df, doc.name)"
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="px-4 text-xs text-red-600 py-2" v-if="errorMessage">
|
||||||
|
{{ errorMessage }}
|
||||||
|
</div>
|
||||||
|
<TwoColumnForm
|
||||||
|
ref="form"
|
||||||
|
v-if="doc"
|
||||||
|
:doc="doc"
|
||||||
|
:fields="fields"
|
||||||
|
:autosave="true"
|
||||||
|
:column-ratio="[1.1, 2]"
|
||||||
|
:validate-form="validateForm"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import Vue from 'vue';
|
||||||
import frappe from 'frappejs';
|
import frappe from 'frappejs';
|
||||||
import { _ } from 'frappejs';
|
import { _ } from 'frappejs';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import XIcon from '@/components/Icons/X';
|
|
||||||
import FormControl from '@/components/Controls/FormControl';
|
import FormControl from '@/components/Controls/FormControl';
|
||||||
|
import TwoColumnForm from '@/components/TwoColumnForm';
|
||||||
|
import Dropdown from '@/components/Dropdown';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'QuickEditForm',
|
name: 'QuickEditForm',
|
||||||
props: ['doctype', 'name', 'values', 'hideFields'],
|
props: ['doctype', 'name', 'values', 'hideFields'],
|
||||||
components: {
|
components: {
|
||||||
Button,
|
Button,
|
||||||
XIcon,
|
FormControl,
|
||||||
FormControl
|
TwoColumnForm,
|
||||||
|
Dropdown
|
||||||
},
|
},
|
||||||
provide() {
|
provide() {
|
||||||
|
let vm = this;
|
||||||
return {
|
return {
|
||||||
doctype: this.doctype,
|
doctype: this.doctype,
|
||||||
name: this.name
|
name: this.name,
|
||||||
|
get doc() {
|
||||||
|
return vm.doc;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
meta: null,
|
doc: null,
|
||||||
doc: {},
|
titleField: null,
|
||||||
fields: [],
|
imageField: null,
|
||||||
titleDocField: null,
|
statusText: null,
|
||||||
statusText: null
|
validateForm: false,
|
||||||
|
errorMessage: null
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async mounted() {
|
async created() {
|
||||||
await this.fetchMetaAndDoc();
|
await this.fetchMetaAndDoc();
|
||||||
},
|
},
|
||||||
|
errorCaptured(err, vm, info) {
|
||||||
|
this.errorMessage = err.message;
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
meta() {
|
||||||
|
return frappe.getMeta(this.doctype);
|
||||||
|
},
|
||||||
|
fields() {
|
||||||
|
return this.meta
|
||||||
|
.getQuickEditFields()
|
||||||
|
.filter(df => !(this.hideFields || []).includes(df.fieldname));
|
||||||
|
},
|
||||||
|
actions() {
|
||||||
|
if (!this.doc) return null;
|
||||||
|
|
||||||
|
let actions = (this.meta.actions || []).map(d => {
|
||||||
|
d.action = d.action.bind(this, this.doc);
|
||||||
|
return d;
|
||||||
|
});
|
||||||
|
return actions.concat({
|
||||||
|
component: {
|
||||||
|
template: `<span class="text-red-700">{{ _('Delete') }}</span>`
|
||||||
|
},
|
||||||
|
action: () => {
|
||||||
|
this.deleteDoc().then(() => {
|
||||||
|
this.routeToList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async fetchMetaAndDoc() {
|
async fetchMetaAndDoc() {
|
||||||
this.meta = frappe.getMeta(this.doctype);
|
this.titleField = this.meta.getField(this.meta.titleField);
|
||||||
this.fields = this.meta
|
this.imageField = this.meta.getField('image');
|
||||||
.getQuickEditFields()
|
|
||||||
.filter(df => !(this.hideFields || []).includes(df.fieldname));
|
|
||||||
this.titleDocField = this.meta.getField(this.meta.titleField);
|
|
||||||
await this.fetchDoc();
|
await this.fetchDoc();
|
||||||
|
|
||||||
// setup the title field
|
// setup the title field
|
||||||
if (
|
if (
|
||||||
this.doc._notInserted &&
|
this.doc.isNew() &&
|
||||||
!this.titleDocField.readOnly &&
|
!this.titleField.readOnly &&
|
||||||
this.doc[this.titleDocField.fieldname]
|
this.doc[this.titleField.fieldname]
|
||||||
) {
|
) {
|
||||||
this.doc.set(this.titleDocField.fieldname, '');
|
this.doc.set(this.titleField.fieldname, '');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.$refs.titleControl.focus();
|
this.$refs.titleControl.focus();
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set default values
|
||||||
if (this.values) {
|
if (this.values) {
|
||||||
this.doc.set(this.values);
|
this.doc.set(this.values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set title size
|
||||||
|
this.setTitleSize();
|
||||||
},
|
},
|
||||||
valueChange(df, value) {
|
valueChange(df, value) {
|
||||||
if (!value) return;
|
this.$refs.form.onChange(df, value);
|
||||||
let oldValue = this.doc.get(df.fieldname);
|
},
|
||||||
if (
|
async fetchDoc() {
|
||||||
df.fieldname === 'name' &&
|
try {
|
||||||
oldValue !== value &&
|
this.doc = await frappe.getDoc(this.doctype, this.name);
|
||||||
!this.doc._notInserted
|
|
||||||
) {
|
|
||||||
this.doc.rename(value);
|
|
||||||
this.doc.once('afterRename', () => {
|
this.doc.once('afterRename', () => {
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
path: `/list/${this.doctype}`,
|
|
||||||
query: {
|
query: {
|
||||||
edit: 1,
|
edit: 1,
|
||||||
doctype: this.doctype,
|
doctype: this.doctype,
|
||||||
name: this.name
|
name: this.doc.name
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
}
|
this.doc.on('beforeUpdate', () => {
|
||||||
this.doc.set(df.fieldname, value);
|
this.statusText = _('Saving...');
|
||||||
if (this.doc._dirty && !this.doc._notInserted) {
|
});
|
||||||
if (df.fieldtype === 'Table') {
|
this.doc.on('afterUpdate', () => {
|
||||||
console.log(value);
|
setTimeout(() => {
|
||||||
return;
|
this.statusText = null;
|
||||||
}
|
}, 500);
|
||||||
this.updateDoc();
|
});
|
||||||
}
|
|
||||||
},
|
|
||||||
async fetchDoc() {
|
|
||||||
this.doc = await frappe.getDoc(this.doctype, this.name);
|
|
||||||
},
|
|
||||||
async updateDoc() {
|
|
||||||
this.statusText = _('Saving...');
|
|
||||||
try {
|
|
||||||
await this.doc.update();
|
|
||||||
this.triggerSaved();
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await this.fetchDoc();
|
this.$router.back();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
triggerSaved() {
|
|
||||||
this.statusText = _('Saved');
|
|
||||||
setTimeout(() => (this.statusText = null), 1000);
|
|
||||||
},
|
|
||||||
insertDoc() {
|
insertDoc() {
|
||||||
this.doc.insert();
|
let requiredFields = this.fields.filter(df => df.required);
|
||||||
|
if (requiredFields.some(df => this.doc[df.fieldname] == null)) {
|
||||||
|
this.validateForm = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.validateForm = false;
|
||||||
|
this.errorMessage = null;
|
||||||
|
this.doc.insert().catch(e => {
|
||||||
|
if (e.name === 'DuplicateEntryError') {
|
||||||
|
this.errorMessage = _('{0} {1} already exists.', [
|
||||||
|
this.doctype,
|
||||||
|
this.doc.name
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
this.errorMessage = _('An error occurred.');
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
submitDoc() {
|
submitDoc() {
|
||||||
this.doc.submit();
|
this.doc.submit();
|
||||||
},
|
},
|
||||||
|
deleteDoc() {
|
||||||
|
return this.doc.delete().catch(e => {
|
||||||
|
if (e.name === 'LinkValidationError') {
|
||||||
|
this.errorMessage = _('{0} {1} is linked with existing records.', [
|
||||||
|
this.doctype,
|
||||||
|
this.doc.name
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
this.errorMessage = _('An error occurred.');
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
},
|
||||||
routeToList() {
|
routeToList() {
|
||||||
this.$router.push(`/list/${this.doctype}`);
|
this.$router.push(`/list/${this.doctype}`);
|
||||||
|
},
|
||||||
|
setTitleSize() {
|
||||||
|
if (this.$refs.titleControl) {
|
||||||
|
let input = this.$refs.titleControl.getInput();
|
||||||
|
let value = input.value;
|
||||||
|
let valueLength = (value || '').length;
|
||||||
|
if (valueLength < 7) {
|
||||||
|
valueLength = 7;
|
||||||
|
}
|
||||||
|
input.size = valueLength;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user