mirror of
https://github.com/frappe/books.git
synced 2025-01-10 18:24:40 +00:00
More flexible Base, add more controls
- Autocomplete - Check - Code - Currency - Float - Link
This commit is contained in:
parent
0887c4e74c
commit
7eee7cd24f
@ -1,12 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<form class="frappe-form-layout p-3">
|
<form class="frappe-form-layout p-3">
|
||||||
<frappe-control
|
<div class="row" v-if="layout" v-for="(section, i) in layout" :key="i">
|
||||||
v-for="docfield in fields"
|
<div class="col" v-for="(column, j) in section.columns" :key="j">
|
||||||
:key="docfield.fieldname"
|
<frappe-control
|
||||||
:docfield="docfield"
|
v-for="fieldname in column.fields"
|
||||||
:value="$data[docfield.fieldname]"
|
:key="fieldname"
|
||||||
@change="$emit('field-change', docfield.fieldname, $event)"
|
:docfield="getDocField(fieldname)"
|
||||||
/>
|
:value="$data[fieldname]"
|
||||||
|
@change="$emit('field-change', fieldname, $event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="!layout">
|
||||||
|
<frappe-control
|
||||||
|
v-for="docfield in fields"
|
||||||
|
:key="docfield.fieldname"
|
||||||
|
:docfield="docfield"
|
||||||
|
:value="$data[docfield.fieldname]"
|
||||||
|
@change="$emit('field-change', docfield.fieldname, $event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
@ -14,7 +27,7 @@ import FrappeControl from './controls/FrappeControl'
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FormLayout',
|
name: 'FormLayout',
|
||||||
props: ['doc', 'fields'],
|
props: ['doc', 'fields', 'layout'],
|
||||||
data() {
|
data() {
|
||||||
const dataObj = {};
|
const dataObj = {};
|
||||||
for (let field of this.fields) {
|
for (let field of this.fields) {
|
||||||
@ -27,6 +40,11 @@ export default {
|
|||||||
this[fieldname] = doc[fieldname];
|
this[fieldname] = doc[fieldname];
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
|
getDocField(fieldname) {
|
||||||
|
return this.fields.find(df => df.fieldname === fieldname);
|
||||||
|
}
|
||||||
|
},
|
||||||
components: {
|
components: {
|
||||||
FrappeControl
|
FrappeControl
|
||||||
}
|
}
|
||||||
|
@ -4,56 +4,71 @@ import Data from './Data';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
extends: Data,
|
extends: Data,
|
||||||
created() {
|
data() {
|
||||||
this.setupAwesomplete();
|
return {
|
||||||
|
awesomplete: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.setupAwesomplete();
|
||||||
|
this.awesomplete.container.classList.add('form-control');
|
||||||
|
this.awesomplete.ul.classList.add('dropdown-menu');
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getInputListeners() {
|
||||||
|
return {
|
||||||
|
input: async e => {
|
||||||
|
this.awesomplete.list = await this.getList(e.target.value);
|
||||||
|
},
|
||||||
|
'awesomplete-select': e => {
|
||||||
|
this.$emit('change', e.text.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getList(text) {
|
||||||
|
return this.docfield.getList(text);
|
||||||
|
},
|
||||||
setupAwesomplete() {
|
setupAwesomplete() {
|
||||||
const input = this.$refs.input;
|
const input = this.$refs.input;
|
||||||
this.awesomplete = new Awesomplete(input, {
|
this.awesomplete = new Awesomplete(input, {
|
||||||
minChars: 0,
|
minChars: 0,
|
||||||
maxItems: 99,
|
maxItems: 99,
|
||||||
filter: () => true,
|
sort: this.sort(),
|
||||||
sort: (a, b) => {
|
item: (text, input) => {
|
||||||
if (a.value === '__newitem' || b.value === '__newitem') {
|
const li = document.createElement('li');
|
||||||
return -1;
|
li.classList.add('dropdown-item');
|
||||||
}
|
li.classList.add('d-flex');
|
||||||
return a.value > b.value;
|
li.classList.add('align-items-center');
|
||||||
}
|
li.innerHTML = text.label;
|
||||||
});
|
|
||||||
|
return li;
|
||||||
// rebuild the list on input
|
|
||||||
this.input.addEventListener('input', async event => {
|
|
||||||
let list = await this.getList(this.input.value);
|
|
||||||
|
|
||||||
// action to add new item
|
|
||||||
list.push({
|
|
||||||
label: frappe._('+ New {0}', this.label),
|
|
||||||
value: '__newItem'
|
|
||||||
});
|
|
||||||
|
|
||||||
this.awesomplete.list = list;
|
|
||||||
});
|
|
||||||
|
|
||||||
// new item action
|
|
||||||
this.input.addEventListener('awesomplete-select', async e => {
|
|
||||||
if (e.text && e.text.value === '__newItem') {
|
|
||||||
e.preventDefault();
|
|
||||||
const newDoc = await frappe.getNewDoc(this.getTarget());
|
|
||||||
const formModal = await frappe.desk.showFormModal(
|
|
||||||
this.getTarget(),
|
|
||||||
newDoc.name
|
|
||||||
);
|
|
||||||
if (formModal.form.doc.meta.hasField('name')) {
|
|
||||||
formModal.form.doc.set('name', this.input.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
formModal.once('save', async () => {
|
|
||||||
await this.updateDocValue(formModal.form.doc.name);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
sort() {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
@import "../../styles/variables";
|
||||||
|
@import "~awesomplete/awesomplete.base";
|
||||||
|
|
||||||
|
.awesomplete {
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
&> ul {
|
||||||
|
padding: $dropdown-padding-y 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu:not([hidden]) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item[aria-selected="true"] {
|
||||||
|
background-color: $dropdown-link-hover-bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -8,12 +8,21 @@ export default {
|
|||||||
id() {
|
id() {
|
||||||
return this.docfield.fieldname + '-'
|
return this.docfield.fieldname + '-'
|
||||||
+ document.querySelectorAll(`[data-fieldname="${this.docfield.fieldname}"]`).length;
|
+ document.querySelectorAll(`[data-fieldname="${this.docfield.fieldname}"]`).length;
|
||||||
|
},
|
||||||
|
inputClass() {
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
wrapperClass() {
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
labelClass() {
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getWrapperElement(h) {
|
getWrapperElement(h) {
|
||||||
return h('div', {
|
return h('div', {
|
||||||
class: ['form-group'],
|
class: ['form-group', ...this.wrapperClass],
|
||||||
attrs: {
|
attrs: {
|
||||||
'data-fieldname': this.docfield.fieldname
|
'data-fieldname': this.docfield.fieldname
|
||||||
}
|
}
|
||||||
@ -21,6 +30,7 @@ export default {
|
|||||||
},
|
},
|
||||||
getLabelElement(h) {
|
getLabelElement(h) {
|
||||||
return h('label', {
|
return h('label', {
|
||||||
|
class: this.labelClass,
|
||||||
attrs: {
|
attrs: {
|
||||||
for: this.id
|
for: this.id
|
||||||
},
|
},
|
||||||
@ -30,16 +40,19 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
getInputElement(h) {
|
getInputElement(h) {
|
||||||
return h('input', {
|
return h(this.getInputTag(), {
|
||||||
class: ['form-control'],
|
class: this.getInputClass(),
|
||||||
attrs: this.getInputAttrs(),
|
attrs: this.getInputAttrs(),
|
||||||
on: {
|
on: this.getInputListeners(),
|
||||||
change: (e) => {
|
domProps: this.getDomProps(),
|
||||||
this.$emit('change', e.target.value)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ref: 'input'
|
ref: 'input'
|
||||||
})
|
}, this.getInputChildren(h));
|
||||||
|
},
|
||||||
|
getInputTag() {
|
||||||
|
return 'input';
|
||||||
|
},
|
||||||
|
getInputClass() {
|
||||||
|
return ['form-control', ...this.inputClass];
|
||||||
},
|
},
|
||||||
getInputAttrs() {
|
getInputAttrs() {
|
||||||
return {
|
return {
|
||||||
@ -48,6 +61,22 @@ export default {
|
|||||||
placeholder: '',
|
placeholder: '',
|
||||||
value: this.value
|
value: this.value
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
getInputListeners() {
|
||||||
|
return {
|
||||||
|
change: (e) => {
|
||||||
|
this.$emit('change', this.parseValue(e.target.value));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getInputChildren() {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
getDomProps() {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
parseValue(value) {
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
25
src/components/controls/Check.vue
Normal file
25
src/components/controls/Check.vue
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<template>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="custom-control custom-checkbox">
|
||||||
|
<input class="custom-control-input" type="checkbox" :id="id" v-model="checkboxValue" @change="emitChange">
|
||||||
|
<label class="custom-control-label" :for="id">{{ docfield.label }}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Base from './Base';
|
||||||
|
export default {
|
||||||
|
extends: Base,
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
checkboxValue: Boolean(this.value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
emitChange(e) {
|
||||||
|
this.$emit('change', Number(this.checkboxValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
11
src/components/controls/Code.vue
Normal file
11
src/components/controls/Code.vue
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<script>
|
||||||
|
import Text from './Text';
|
||||||
|
export default {
|
||||||
|
extends: Text,
|
||||||
|
computed: {
|
||||||
|
inputClass() {
|
||||||
|
return ['text-monospace'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
6
src/components/controls/Currency.vue
Normal file
6
src/components/controls/Currency.vue
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<script>
|
||||||
|
import Float from './Float';
|
||||||
|
export default {
|
||||||
|
extends: Float
|
||||||
|
}
|
||||||
|
</script>
|
24
src/components/controls/Float.vue
Normal file
24
src/components/controls/Float.vue
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<script>
|
||||||
|
import Base from './Base';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
extends: Base,
|
||||||
|
computed: {
|
||||||
|
inputClass() {
|
||||||
|
return ['text-right']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getInputListeners() {
|
||||||
|
return {
|
||||||
|
change: e => {
|
||||||
|
this.$emit('change', e.target.value)
|
||||||
|
},
|
||||||
|
focus: e => {
|
||||||
|
setTimeout(() => this.$refs.input.select(), 100);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -2,21 +2,30 @@
|
|||||||
<component :is="component" :docfield="docfield" :value="value" @change="$emit('change', $event)"/>
|
<component :is="component" :docfield="docfield" :value="value" @change="$emit('change', $event)"/>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
import Autocomplete from './Autocomplete';
|
||||||
|
import Check from './Check';
|
||||||
|
import Code from './Code';
|
||||||
|
import Currency from './Currency';
|
||||||
import Data from './Data';
|
import Data from './Data';
|
||||||
|
import Float from './Float';
|
||||||
|
import Link from './Link';
|
||||||
import Select from './Select';
|
import Select from './Select';
|
||||||
import Text from './Text';
|
import Text from './Text';
|
||||||
import Autocomplete from './Autocomplete';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['docfield', 'value'],
|
props: ['docfield', 'value'],
|
||||||
computed: {
|
computed: {
|
||||||
component() {
|
component() {
|
||||||
return {
|
return {
|
||||||
|
Autocomplete,
|
||||||
|
Check,
|
||||||
|
Code,
|
||||||
|
Currency,
|
||||||
Data,
|
Data,
|
||||||
|
Float,
|
||||||
|
Link,
|
||||||
Select,
|
Select,
|
||||||
Text,
|
Text,
|
||||||
Autocomplete,
|
|
||||||
Link: Autocomplete
|
|
||||||
}[this.docfield.fieldtype];
|
}[this.docfield.fieldtype];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
45
src/components/controls/Link.vue
Normal file
45
src/components/controls/Link.vue
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<script>
|
||||||
|
import frappe from 'frappejs';
|
||||||
|
import feather from 'feather-icons';
|
||||||
|
import Autocomplete from './Autocomplete';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
extends: Autocomplete,
|
||||||
|
methods: {
|
||||||
|
async getList(query) {
|
||||||
|
const list = await frappe.db.getAll({
|
||||||
|
doctype: this.docfield.target,
|
||||||
|
filters: {
|
||||||
|
keywords: ["like", query]
|
||||||
|
},
|
||||||
|
fields: ['name'],
|
||||||
|
limit: 50
|
||||||
|
});
|
||||||
|
|
||||||
|
const plusIcon = feather.icons.plus.toSvg({
|
||||||
|
class: 'm-1',
|
||||||
|
width: 16,
|
||||||
|
height: 16
|
||||||
|
});
|
||||||
|
|
||||||
|
return list
|
||||||
|
.map(d => ({
|
||||||
|
label: d.name,
|
||||||
|
value: d.name
|
||||||
|
}))
|
||||||
|
.concat({
|
||||||
|
label: plusIcon + ' New ' + this.docfield.target,
|
||||||
|
value: '__newItem'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
sort() {
|
||||||
|
return (a, b) => {
|
||||||
|
if (a.value === '__newitem' || b.value === '__newitem') {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return a.value > b.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -3,8 +3,16 @@ import Base from './Base';
|
|||||||
export default {
|
export default {
|
||||||
extends: Base,
|
extends: Base,
|
||||||
methods: {
|
methods: {
|
||||||
getInputElement(h) {
|
getInputTag() {
|
||||||
const options = this.docfield.options.map(option =>
|
return 'select';
|
||||||
|
},
|
||||||
|
getInputAttrs() {
|
||||||
|
return {
|
||||||
|
id: this.id
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getInputChildren(h) {
|
||||||
|
return this.docfield.options.map(option =>
|
||||||
h('option', {
|
h('option', {
|
||||||
attrs: {
|
attrs: {
|
||||||
key: option,
|
key: option,
|
||||||
@ -15,19 +23,6 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
return h('select', {
|
|
||||||
class: ['form-control'],
|
|
||||||
attrs: {
|
|
||||||
id: this.id
|
|
||||||
},
|
|
||||||
on: {
|
|
||||||
change: (e) => {
|
|
||||||
this.$emit('change', e.target.value)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ref: 'input'
|
|
||||||
}, options)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,23 +3,19 @@ import Base from './Base';
|
|||||||
export default {
|
export default {
|
||||||
extends: Base,
|
extends: Base,
|
||||||
methods: {
|
methods: {
|
||||||
getInputElement(h) {
|
getInputTag() {
|
||||||
return h('textarea', {
|
return 'textarea';
|
||||||
class: ['form-control'],
|
},
|
||||||
attrs: {
|
getInputAttrs() {
|
||||||
id: this.id,
|
return {
|
||||||
rows: 3
|
id: this.id,
|
||||||
},
|
rows: 3
|
||||||
domProps: {
|
};
|
||||||
value: this.value
|
},
|
||||||
},
|
getDomProps() {
|
||||||
on: {
|
return {
|
||||||
change: (e) => {
|
value: this.value
|
||||||
this.$emit('change', e.target.value)
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
ref: 'input'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user