mirror of
https://github.com/frappe/books.git
synced 2024-09-20 03:29:00 +00:00
Add Table control with ModelTable
- onlyInput prop for FrappeControl
This commit is contained in:
parent
577ee9d21d
commit
a268e1a4ef
@ -37,6 +37,7 @@ module.exports = {
|
||||
label: 'Schedule'
|
||||
}
|
||||
],
|
||||
titleField: 'title',
|
||||
keywordFields: [],
|
||||
isSingle: 0,
|
||||
listSettings: {
|
||||
|
@ -25,8 +25,10 @@
|
||||
"dependencies": {
|
||||
"bootstrap": "^4.1.1",
|
||||
"feather-icons": "^4.7.3",
|
||||
"frappe-datatable": "^0.0.9",
|
||||
"frappejs": "^0.0.7",
|
||||
"vue": "^2.5.2",
|
||||
"vue-flatpickr-component": "^7.0.2",
|
||||
"vue-router": "^3.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -1,5 +1,4 @@
|
||||
<template>
|
||||
<keep-alive>
|
||||
<div class="frappe-form">
|
||||
<form-actions
|
||||
v-if="shouldRenderForm"
|
||||
@ -11,11 +10,9 @@
|
||||
:doc="doc"
|
||||
:fields="meta.fields"
|
||||
:layout="meta.layout"
|
||||
@field-change="updateDoc"
|
||||
/>
|
||||
<not-found v-if="notFound" />
|
||||
</div>
|
||||
</keep-alive>
|
||||
</template>
|
||||
<script>
|
||||
import frappe from 'frappejs';
|
||||
@ -31,7 +28,7 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
doc: null,
|
||||
docLoaded: false,
|
||||
notFound: false,
|
||||
invalid: false
|
||||
}
|
||||
@ -41,13 +38,14 @@ export default {
|
||||
return frappe.getMeta(this.doctype);
|
||||
},
|
||||
shouldRenderForm() {
|
||||
return this.name && this.doc;
|
||||
return this.name && this.docLoaded;
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
if (!this.name) return;
|
||||
try {
|
||||
this.doc = await frappe.getDoc(this.doctype, this.name);
|
||||
this.docLoaded = true;
|
||||
} catch(e) {
|
||||
this.notFound = true;
|
||||
}
|
||||
@ -71,16 +69,10 @@ export default {
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
// frappe.ui.showAlert({ message: frappe._('Failed'), color: 'red' });
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
updateDoc(fieldname, value) {
|
||||
this.$data[fieldname] = value;
|
||||
this.doc.set(fieldname, value);
|
||||
},
|
||||
|
||||
checkValidity() {
|
||||
return true;
|
||||
},
|
||||
|
@ -7,7 +7,7 @@
|
||||
:key="fieldname"
|
||||
:docfield="getDocField(fieldname)"
|
||||
:value="$data[fieldname]"
|
||||
@change="$emit('field-change', fieldname, $event)"
|
||||
@change="value => updateDoc(docfield.fieldname, value)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -17,36 +17,51 @@
|
||||
:key="docfield.fieldname"
|
||||
:docfield="docfield"
|
||||
:value="$data[docfield.fieldname]"
|
||||
@change="$emit('field-change', docfield.fieldname, $event)"
|
||||
@change="value => updateDoc(docfield.fieldname, value)"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
<script>
|
||||
import FrappeControl from './controls/FrappeControl'
|
||||
import FrappeControl from './controls/FrappeControl';
|
||||
|
||||
export default {
|
||||
name: 'FormLayout',
|
||||
props: ['doc', 'fields', 'layout'],
|
||||
data() {
|
||||
const dataObj = {};
|
||||
for (let field of this.fields) {
|
||||
dataObj[field.fieldname] = this.doc[field.fieldname];
|
||||
}
|
||||
return dataObj;
|
||||
},
|
||||
created() {
|
||||
this.doc.on('change', ({ doc, fieldname }) => {
|
||||
this[fieldname] = doc[fieldname];
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
getDocField(fieldname) {
|
||||
return this.fields.find(df => df.fieldname === fieldname);
|
||||
}
|
||||
},
|
||||
components: {
|
||||
FrappeControl
|
||||
name: 'FormLayout',
|
||||
props: ['doc', 'fields', 'layout'],
|
||||
data() {
|
||||
const dataObj = {};
|
||||
for (let df of this.fields) {
|
||||
dataObj[df.fieldname] = this.doc[df.fieldname];
|
||||
|
||||
if (df.fieldtype === 'Table' && !dataObj[df.fieldname]) {
|
||||
dataObj[df.fieldname] = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
return dataObj;
|
||||
},
|
||||
created() {
|
||||
this.doc.on('change', ({ doc, fieldname }) => {
|
||||
if (fieldname) {
|
||||
// update value
|
||||
this[fieldname] = doc[fieldname];
|
||||
} else {
|
||||
// update all values
|
||||
this.fields.forEach(df => {
|
||||
this[df.fieldname] = doc[df.fieldname];
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
getDocField(fieldname) {
|
||||
return this.fields.find(df => df.fieldname === fieldname);
|
||||
},
|
||||
updateDoc(fieldname, value) {
|
||||
this.doc.set(fieldname, value);
|
||||
}
|
||||
},
|
||||
components: {
|
||||
FrappeControl
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
@ -1,5 +1,4 @@
|
||||
<template>
|
||||
<keep-alive>
|
||||
<div class="frappe-list">
|
||||
<list-actions
|
||||
:doctype="doctype"
|
||||
@ -19,7 +18,6 @@
|
||||
</list-item>
|
||||
</ul>
|
||||
</div>
|
||||
</keep-alive>
|
||||
</template>
|
||||
<script>
|
||||
import frappe from 'frappejs';
|
||||
@ -61,7 +59,7 @@ export default {
|
||||
async updateList() {
|
||||
const data = await frappe.db.getAll({
|
||||
doctype: this.doctype,
|
||||
fields: ['name', ...this.meta.keywordFields]
|
||||
fields: ['name', ...this.meta.keywordFields, this.meta.titleField]
|
||||
});
|
||||
|
||||
this.data = data;
|
||||
@ -71,9 +69,7 @@ export default {
|
||||
this.$router.push(`/edit/${this.doctype}/${name}`);
|
||||
},
|
||||
async deleteCheckedItems() {
|
||||
debugger
|
||||
await frappe.db.deleteMany(this.doctype, this.checkList);
|
||||
debugger
|
||||
this.checkList = [];
|
||||
},
|
||||
toggleCheck(name) {
|
||||
|
138
src/components/ModelTable.vue
Normal file
138
src/components/ModelTable.vue
Normal file
@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<div>
|
||||
<div ref="wrapper" class="datatable-wrapper"></div>
|
||||
<div class="table-actions mt-1">
|
||||
<button type="button" @click="addRow" class="btn btn-sm btn-light border">Add Row</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Vue from 'vue';
|
||||
import frappe from 'frappejs';
|
||||
import Observable from 'frappejs/utils/observable';
|
||||
import DataTable from 'frappe-datatable';
|
||||
import FrappeControl from './controls/FrappeControl';
|
||||
import { convertFieldsToDatatableColumns } from 'frappejs/client/ui/utils';
|
||||
|
||||
export default {
|
||||
props: ['doctype', 'rows'],
|
||||
data() {
|
||||
return {
|
||||
docs: this.getRowDocs()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
meta() {
|
||||
return frappe.getMeta(this.doctype);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.datatable = new DataTable(this.$refs.wrapper, {
|
||||
columns: this.getColumns(),
|
||||
data: this.docs,
|
||||
layout: 'fluid',
|
||||
getEditor: (colIndex, rowIndex, value, parent) => {
|
||||
|
||||
let inputComponent = null;
|
||||
const docfield = this.datatable.getColumn(colIndex).field;
|
||||
|
||||
const fieldWrapper = document.createElement('div');
|
||||
parent.appendChild(fieldWrapper);
|
||||
|
||||
const updateData = (fieldname, value) => {
|
||||
const docs = this.datatable.datamanager.data;
|
||||
const doc = docs[rowIndex];
|
||||
doc.set(fieldname, value);
|
||||
this.emitChange(doc);
|
||||
}
|
||||
|
||||
return {
|
||||
initValue() {
|
||||
inputComponent = new Vue({
|
||||
el: fieldWrapper,
|
||||
data() {
|
||||
return {
|
||||
docfield,
|
||||
value
|
||||
}
|
||||
},
|
||||
components: {
|
||||
FrappeControl
|
||||
},
|
||||
mounted() {
|
||||
this.$el.focus();
|
||||
},
|
||||
template: `<frappe-control
|
||||
:docfield="docfield"
|
||||
:value="value"
|
||||
@change="value => updateValue(docfield.fieldname, value)"
|
||||
:onlyInput="true"
|
||||
/>`,
|
||||
methods: {
|
||||
updateValue(fieldname, value) {
|
||||
this.value = value;
|
||||
updateData(fieldname, value);
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
setValue: (value, rowIndex, column) => {
|
||||
inputComponent.value = value;
|
||||
},
|
||||
getValue: () => {
|
||||
return inputComponent.$el.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log(this.datatable)
|
||||
},
|
||||
destroyed() {
|
||||
this.datatable.destroy();
|
||||
},
|
||||
watch: {
|
||||
docs: function(newVal, oldVal) {
|
||||
this.datatable.refresh(newVal);
|
||||
},
|
||||
rows: function(newVal, oldVal) {
|
||||
this.docs = this.getRowDocs();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getRowDocs() {
|
||||
return this.rows.map((row, i) => {
|
||||
const doc = new Observable();
|
||||
doc.set('idx', i);
|
||||
for (let fieldname in row) {
|
||||
doc.set(fieldname, row[fieldname]);
|
||||
}
|
||||
return doc;
|
||||
});
|
||||
},
|
||||
getColumns() {
|
||||
return convertFieldsToDatatableColumns(this.meta.fields);
|
||||
},
|
||||
addRow() {
|
||||
const doc = new Observable();
|
||||
doc.set('idx', this.docs.length);
|
||||
this.docs.push(doc);
|
||||
},
|
||||
emitChange(doc) {
|
||||
this.$emit('update:rows', this.docs, doc);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
@import "frappe-datatable/dist/frappe-datatable.css";
|
||||
|
||||
.datatable-wrapper {
|
||||
.form-control {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,9 +1,19 @@
|
||||
<script>
|
||||
export default {
|
||||
render(h) {
|
||||
if (this.onlyInput) {
|
||||
return this.getInputElement(h);
|
||||
}
|
||||
return this.getWrapperElement(h);
|
||||
},
|
||||
props: ['docfield', 'value'],
|
||||
props: {
|
||||
docfield: Object,
|
||||
value: [String, Number, Array],
|
||||
onlyInput: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
id() {
|
||||
return this.docfield.fieldname + '-'
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="form-group">
|
||||
<div class="form-group" v-if="!onlyInput">
|
||||
<label>{{ docfield.label }}</label>
|
||||
<flat-pickr
|
||||
:value="value"
|
||||
@ -8,6 +8,13 @@
|
||||
>
|
||||
</flat-pickr>
|
||||
</div>
|
||||
<flat-pickr
|
||||
v-else
|
||||
:value="value"
|
||||
class="form-control"
|
||||
@on-change="emitChange"
|
||||
>
|
||||
</flat-pickr>
|
||||
</template>
|
||||
<script>
|
||||
import flatPickr from 'vue-flatpickr-component';
|
||||
|
@ -1,5 +1,10 @@
|
||||
<template>
|
||||
<component :is="component" :docfield="docfield" :value="value" @change="$emit('change', $event)"/>
|
||||
<component :is="component"
|
||||
:docfield="docfield"
|
||||
:value="value"
|
||||
:onlyInput="onlyInput"
|
||||
@change="$emit('change', $event)"
|
||||
/>
|
||||
</template>
|
||||
<script>
|
||||
import Autocomplete from './Autocomplete';
|
||||
@ -11,10 +16,11 @@ import Date from './Date';
|
||||
import Float from './Float';
|
||||
import Link from './Link';
|
||||
import Select from './Select';
|
||||
import Table from './Table';
|
||||
import Text from './Text';
|
||||
|
||||
export default {
|
||||
props: ['docfield', 'value'],
|
||||
props: ['docfield', 'value', 'onlyInput'],
|
||||
computed: {
|
||||
component() {
|
||||
return {
|
||||
@ -27,6 +33,7 @@ export default {
|
||||
Float,
|
||||
Link,
|
||||
Select,
|
||||
Table,
|
||||
Text,
|
||||
}[this.docfield.fieldtype];
|
||||
}
|
||||
|
26
src/components/controls/Table.vue
Normal file
26
src/components/controls/Table.vue
Normal file
@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<div class="form-group">
|
||||
<model-table
|
||||
:doctype="docfield.childtype"
|
||||
:rows="value"
|
||||
@update:rows="emitChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ModelTable from '../ModelTable';
|
||||
import Base from './Base';
|
||||
|
||||
export default {
|
||||
extends: Base,
|
||||
components: {
|
||||
ModelTable
|
||||
},
|
||||
methods: {
|
||||
emitChange(rows, rowDoc) {
|
||||
this.$emit('change', rows, rowDoc);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
Loading…
Reference in New Issue
Block a user