mirror of
https://github.com/frappe/books.git
synced 2024-11-08 14:50:56 +00:00
fix: QuickEditForm
- Create a new document - Rename a document - Auto save document on value change
This commit is contained in:
parent
9b9181146e
commit
eb348c7210
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<button class="text-sm px-4 py-2 focus:outline-none rounded-lg" :style="style">
|
||||
<button class="text-sm px-4 py-2 focus:outline-none rounded-lg" :style="style" v-bind="$attrs" v-on="$listeners">
|
||||
<slot></slot>
|
||||
</button>
|
||||
</template>
|
||||
@ -15,11 +15,12 @@ export default {
|
||||
computed: {
|
||||
style() {
|
||||
return {
|
||||
'background-image': this.type === 'primary'
|
||||
? 'linear-gradient(180deg, #4AC3F8 0%, #2490EF 100%)'
|
||||
: 'linear-gradient(180deg, #FFFFFF 0%, #F4F4F6 100%)'
|
||||
}
|
||||
'background-image':
|
||||
this.type === 'primary'
|
||||
? 'linear-gradient(180deg, #4AC3F8 0%, #2490EF 100%)'
|
||||
: 'linear-gradient(180deg, #FFFFFF 0%, #F4F4F6 100%)'
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
27
src/components/Controls/Data.vue
Normal file
27
src/components/Controls/Data.vue
Normal file
@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<div>
|
||||
<input
|
||||
ref="input"
|
||||
class="focus:outline-none w-full"
|
||||
:class="inputClass"
|
||||
type="text"
|
||||
:value="value"
|
||||
@blur="triggerChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Data',
|
||||
props: ['df', 'value', 'inputClass'],
|
||||
methods: {
|
||||
focus() {
|
||||
this.$refs.input.focus();
|
||||
},
|
||||
triggerChange(e) {
|
||||
this.$emit('change', e.target.value);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
23
src/components/Controls/FormControl.js
Normal file
23
src/components/Controls/FormControl.js
Normal file
@ -0,0 +1,23 @@
|
||||
import Data from './Data';
|
||||
import Select from './Select';
|
||||
|
||||
export default {
|
||||
name: 'FormControl',
|
||||
render(h) {
|
||||
let controls = {
|
||||
Data,
|
||||
Select
|
||||
};
|
||||
let { df } = this.$attrs;
|
||||
return h(controls[df.fieldtype] || Data, {
|
||||
props: this.$attrs,
|
||||
on: this.$listeners,
|
||||
ref: 'control'
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
focus() {
|
||||
this.$refs.control.focus();
|
||||
}
|
||||
}
|
||||
};
|
35
src/components/Controls/Select.vue
Normal file
35
src/components/Controls/Select.vue
Normal file
@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<div>
|
||||
<select
|
||||
class="appearance-none bg-white rounded-none focus:outline-none w-full"
|
||||
:class="inputClass"
|
||||
:value="value"
|
||||
@blur="triggerChange"
|
||||
>
|
||||
<option v-for="option in options" :value="option.value">{{ option.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Select',
|
||||
props: ['df', 'value', 'inputClass'],
|
||||
methods: {
|
||||
triggerChange(e) {
|
||||
this.$emit('change', e.target.value);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
options() {
|
||||
let options = this.df.options;
|
||||
return options.map(o => {
|
||||
if (typeof o === 'string') {
|
||||
return { label: o, value: o };
|
||||
}
|
||||
return o;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@ -1,22 +1,39 @@
|
||||
<template>
|
||||
<div class="border-l h-full">
|
||||
<div class="flex justify-end px-4 pt-4">
|
||||
<Button>
|
||||
<Button @click="routeToList">
|
||||
<XIcon class="w-3 h-3 stroke-current text-gray-700" />
|
||||
</Button>
|
||||
<Button @click="insertDoc" type="primary" v-if="doc._notInserted" class="ml-2 flex">
|
||||
<feather-icon name="check" class="text-white" />
|
||||
</Button>
|
||||
</div>
|
||||
<div class="px-4 pt-2 pb-4 border-b">
|
||||
<h2 class="font-medium">{{ title }}</h2>
|
||||
<div class="px-4 pt-2 pb-4 border-b flex items-center justify-between">
|
||||
<FormControl
|
||||
ref="titleControl"
|
||||
v-if="titleDocField"
|
||||
input-class="focus:shadow-outline-px"
|
||||
:df="titleDocField"
|
||||
:value="doc[titleDocField.fieldname]"
|
||||
@change="value => valueChange(titleDocField, value)"
|
||||
/>
|
||||
<span v-if="showSaved" class="text-xs text-gray-600">{{ _('Saved') }}</span>
|
||||
</div>
|
||||
<div class="text-xs">
|
||||
<div
|
||||
class="grid border-b px-4 py-3"
|
||||
class="grid border-b"
|
||||
style="grid-template-columns: 1fr 2fr"
|
||||
v-for="df in fields"
|
||||
:key="df.fieldname"
|
||||
>
|
||||
<div class="text-gray-600">{{ df.label }}</div>
|
||||
<div class="text-gray-900">{{ doc[df.fieldname] }}</div>
|
||||
<div class="py-3 pl-4 text-gray-600">{{ df.label }}</div>
|
||||
<FormControl
|
||||
class="py-3 pr-4"
|
||||
input-class="focus:shadow-outline-px"
|
||||
:df="df"
|
||||
:value="doc[df.fieldname]"
|
||||
@change="value => valueChange(df, value)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -26,28 +43,80 @@
|
||||
import frappe from 'frappejs';
|
||||
import Button from '@/components/Button';
|
||||
import XIcon from '@/components/Icons/X';
|
||||
import FormControl from '@/components/Controls/FormControl';
|
||||
|
||||
export default {
|
||||
name: 'QuickEditForm',
|
||||
props: ['doctype', 'name'],
|
||||
components: {
|
||||
Button,
|
||||
XIcon
|
||||
XIcon,
|
||||
FormControl
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
title: '',
|
||||
meta: null,
|
||||
doc: {},
|
||||
fields: []
|
||||
fields: [],
|
||||
titleDocField: null,
|
||||
showSaved: false
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
let meta = frappe.getMeta(this.doctype);
|
||||
this.doc = await frappe.getDoc(this.doctype, this.name);
|
||||
this.title = this.doc[meta.titleField];
|
||||
this.fields = meta
|
||||
this.meta = frappe.getMeta(this.doctype);
|
||||
this.fields = this.meta
|
||||
.getQuickEditFields()
|
||||
.map(fieldname => meta.getField(fieldname));
|
||||
.map(fieldname => this.meta.getField(fieldname));
|
||||
this.titleDocField = this.meta.getField(this.meta.titleField);
|
||||
await this.fetchDoc();
|
||||
|
||||
// setup the title field
|
||||
if (this.doc._notInserted) {
|
||||
this.doc.set(this.titleDocField.fieldname, '');
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.$refs.titleControl.focus()
|
||||
}, 300);
|
||||
},
|
||||
methods: {
|
||||
valueChange(df, value) {
|
||||
if (!value) return;
|
||||
let oldValue = this.doc.get(df.fieldname);
|
||||
if (df.fieldname === 'name' && oldValue !== value && !this.doc._notInserted) {
|
||||
this.doc.rename(value);
|
||||
this.doc.once('afterRename', () => {
|
||||
this.$router.push(`/list/${this.doctype}/${this.doc.name}`);
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.doc.set(df.fieldname, value);
|
||||
if (this.doc._dirty && !this.doc._notInserted) {
|
||||
this.updateDoc();
|
||||
}
|
||||
},
|
||||
async fetchDoc() {
|
||||
this.doc = await frappe.getDoc(this.doctype, this.name);
|
||||
this.title = this.doc[this.meta.titleField];
|
||||
},
|
||||
async updateDoc() {
|
||||
try {
|
||||
await this.doc.update();
|
||||
this.triggerSaved();
|
||||
} catch (e) {
|
||||
await this.fetchDoc();
|
||||
}
|
||||
},
|
||||
triggerSaved() {
|
||||
this.showSaved = true;
|
||||
setTimeout(() => (this.showSaved = false), 1000);
|
||||
},
|
||||
insertDoc() {
|
||||
this.doc.insert();
|
||||
},
|
||||
routeToList() {
|
||||
this.$router.push(`/list/${this.doctype}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
@ -1,12 +1,20 @@
|
||||
module.exports = {
|
||||
theme: {
|
||||
fontFamily: {
|
||||
'sans': ['Inter var experimental', 'sans-serif'],
|
||||
sans: ['Inter var experimental', 'sans-serif']
|
||||
},
|
||||
extend: { }
|
||||
extend: {
|
||||
spacing: {
|
||||
'72': '18rem',
|
||||
'80': '20rem'
|
||||
},
|
||||
boxShadow: {
|
||||
'outline-px': '0 0 0 1px rgba(66,153,225,0.5)'
|
||||
}
|
||||
}
|
||||
},
|
||||
variants: {
|
||||
margin: ['responsive', 'first', 'hover', 'focus'],
|
||||
margin: ['responsive', 'first', 'hover', 'focus']
|
||||
},
|
||||
plugins: []
|
||||
}
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user