2
0
mirror of https://github.com/frappe/books.git synced 2024-11-14 01:14:03 +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:
Faris Ansari 2019-11-20 00:38:49 +05:30
parent 97ad435ecb
commit f6ed9fc390

View File

@ -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">
<feather-icon name="x" class="w-4 h-4" />
</Button> </Button>
<span v-if="statusText" class="ml-2 text-base text-gray-600">{{
statusText
}}</span>
</div>
<div class="flex items-stretch">
<Dropdown v-if="actions" :items="actions" right class="text-base">
<template v-slot="{ toggleDropdown }">
<Button
class="text-gray-900"
:icon="true"
@click="toggleDropdown()"
>
<feather-icon name="more-horizontal" class="w-4 h-4" />
</Button>
</template>
</Dropdown>
<Button <Button
:icon="true" :icon="true"
@click="insertDoc" @click="insertDoc"
type="primary" type="primary"
v-if="doc._notInserted" v-if="doc && doc._notInserted"
class="ml-2 flex" class="ml-2 text-white text-xs"
> >
<feather-icon name="check" class="text-white" /> {{ _('Save') }}
</Button> </Button>
<Button <Button
:icon="true" :icon="true"
@click="submitDoc" @click="submitDoc"
type="primary" type="primary"
v-if="meta && meta.isSubmittable && !doc.submitted && !doc._notInserted" v-if="
class="ml-2 flex" meta &&
meta.isSubmittable &&
doc &&
!doc.submitted &&
!doc._notInserted
"
class="ml-2 text-white text-xs"
> >
<feather-icon name="lock" class="text-white" /> {{ _('Submit') }}
</Button> </Button>
</div> </div>
<div class="pl-1 pr-4 pt-2 pb-4 border-b flex items-center justify-between"> </div>
<div class="px-4 pt-2 pb-4 flex items-center justify-center" v-if="doc">
<div class="flex flex-col items-center">
<FormControl <FormControl
v-if="imageField"
:df="imageField"
:value="doc[imageField.fieldname]"
@change="value => valueChange(imageField, value)"
size="small"
class="mb-1"
:letter-placeholder="
doc[titleField.fieldname] ? doc[titleField.fieldname][0] : null
"
/>
<FormControl
input-class="text-center"
ref="titleControl" ref="titleControl"
v-if="titleDocField" v-if="titleField"
:df="titleDocField" :df="titleField"
:value="doc[titleDocField.fieldname]" :value="doc[titleField.fieldname]"
@change="value => valueChange(titleDocField, value)" @change="value => valueChange(titleField, value)"
/> @input="setTitleSize"
<span v-if="statusText" class="text-xs text-gray-600">{{ statusText }}</span>
</div>
<div class="text-xs">
<template v-for="df in fields">
<FormControl
size="small"
v-if="df.fieldtype === 'Table'"
:df="df"
:value="doc[df.fieldname]"
@change="value => valueChange(df, value)"
/>
<div v-else class="grid border-b" style="grid-template-columns: 1fr 2fr">
<div class="py-2 pl-4 text-gray-600 flex items-center">{{ df.label }}</div>
<div class="py-2 pr-4">
<FormControl
size="small"
:df="df"
:value="doc[df.fieldname]"
@change="value => valueChange(df, value)"
@new-doc="doc => valueChange(df, doc.name)"
/> />
</div> </div>
</div> </div>
</template> <div class="px-4 text-xs text-red-600 py-2" v-if="errorMessage">
{{ errorMessage }}
</div> </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);
if (this.doc._dirty && !this.doc._notInserted) {
if (df.fieldtype === 'Table') {
console.log(value);
return;
}
this.updateDoc();
}
},
async fetchDoc() {
this.doc = await frappe.getDoc(this.doctype, this.name);
},
async updateDoc() {
this.statusText = _('Saving...'); this.statusText = _('Saving...');
try { });
await this.doc.update(); this.doc.on('afterUpdate', () => {
this.triggerSaved(); setTimeout(() => {
this.statusText = null;
}, 500);
});
} 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;
}
} }
} }
}; };