2
0
mirror of https://github.com/frappe/books.git synced 2025-02-10 16:08:35 +00:00

Replace awesomplete with own component

- Add Tree View
This commit is contained in:
Faris Ansari 2018-09-26 19:50:35 +05:30
parent 667a966769
commit 110300005e
7 changed files with 298 additions and 131 deletions

View File

@ -26,7 +26,11 @@ export default {
}, },
computed: { computed: {
iconSVG() { iconSVG() {
return feather.icons[this.name].toSvg({ const icon = feather.icons[this.name];
if (!icon) {
return '';
}
return icon.toSvg({
width: this.size, width: this.size,
height: this.size height: this.size
}); });

View File

@ -0,0 +1,87 @@
<template>
<div class="tree-node">
<div class="tree-label px-3 py-2" @click.self="toggleChildren">
<div @click="toggleChildren">
<feather-icon :name="iconName" v-show="iconName" />
<span>{{ label }}</span>
</div>
</div>
<div :class="['tree-children', expanded ? '' : 'd-none']">
<tree-node v-for="child in children" :key="child.label"
:label="child.label"
:parentValue="child.name"
:doctype="doctype"
/>
</div>
</div>
</template>
<script>
const TreeNode = {
props: ['label', 'parentValue', 'doctype'],
data() {
return {
expanded: false,
children: null
}
},
computed: {
iconName() {
if (this.children && this.children.length ==0) return 'chevron-right';
return this.expanded ? 'chevron-down' : 'chevron-right';
}
},
components: {
TreeNode: () => Promise.resolve(TreeNode)
},
mounted() {
this.settings = frappe.getMeta(this.doctype).treeSettings;
},
methods: {
async toggleChildren() {
await this.getChildren();
this.expanded = !this.expanded;
},
async getChildren() {
if (this.children) return;
this.children = [];
let filters = {
[this.settings.parentField]: this.parentValue
};
const children = await frappe.db.getAll({
doctype: this.doctype,
filters,
fields: [this.settings.parentField, 'isGroup', 'name'],
orderBy: 'name',
order: 'asc'
});
this.children = children.map(c => {
c.label = c.name;
return c;
});
}
}
};
export default TreeNode;
</script>
<style lang="scss">
@import "../../styles/variables";
.tree-node {
font-size: 1rem;
}
.tree-label {
border-radius: 4px;
cursor: pointer;
}
.tree-label:hover {
background-color: $dropdown-link-hover-bg;;
}
.tree-children {
padding-left: 2.25rem;
}
</style>

View File

@ -0,0 +1,53 @@
<template>
<div class="p-3 w-50" v-if="rootNode">
<tree-node :label="rootNode.label" :parentValue="''" :doctype="doctype" ref="rootNode"/>
</div>
</template>
<script>
import frappe from 'frappejs';
import TreeNode from './TreeNode';
import { setTimeout } from 'timers';
export default {
props: ['doctype'],
components: {
TreeNode,
},
data() {
return {
rootNode: null
}
},
mounted() {
setTimeout(() => {
this.$refs.rootNode.find('.tree-label').click();
}, 500);
},
async mounted() {
this.settings = frappe.getMeta(this.doctype).treeSettings;
this.rootNode = {
label: await this.settings.getRootLabel()
};
},
methods: {
async getChildren(parentValue) {
let filters = {
[this.settings.parentField]: parentValue
};
const children = await frappe.db.getAll({
doctype: this.doctype,
filters,
fields: [this.settings.parentField, 'isGroup', 'name'],
orderBy: 'name',
order: 'asc'
});
return children.map(c => {
c.label = c.name;
return c;
});
}
}
}
</script>

View File

@ -1,91 +1,104 @@
<script> <script>
import Awesomplete from 'awesomplete';
import Data from './Data'; import Data from './Data';
export default { export default {
extends: Data, extends: Data,
data() { data() {
return { return {
awesomplete: null popupOpen: false,
popupItems: [],
highlightedItem: -1
} }
}, },
mounted() {
this.setupAwesomplete();
this.awesomplete.container.classList.add('form-control');
this.awesomplete.ul.classList.add('dropdown-menu');
},
methods: { methods: {
getInputListeners() { getInputListeners() {
return { return {
input: e => { input: e => {
this.updateList(e.target.value); this.updateList(e.target.value);
}, },
'awesomplete-select': e => { keydown: e => {
const value = e.text.value; if (e.keyCode === 38) {
this.handleChange(value); // up
this.highlightedItem -= 1;
}
if (e.keyCode === 40) {
// down
this.highlightedItem += 1;
}
if (e.keyCode === 13) {
if (this.highlightedItem > -1) {
this.onItemClick(this.popupItems[this.highlightedItem]);
this.popupOpen = false;
}
}
}, },
focus: async e => { focus: async e => {
await this.updateList(); await this.updateList();
this.awesomplete.evaluate(); },
this.awesomplete.open(); blur: () => {
setTimeout(() => {
this.popupOpen = false;
}, 200);
} }
} }
}, },
async updateList(value) { getChildrenElement(h) {
this.awesomplete.list = await this.getList(value); return [
this.getLabelElement(h),
this.getInputElement(h),
this.getDropdownElement(h)
];
}, },
getList(text) { getDropdownElement(h) {
return this.docfield.getList(text); return h('div', {
class: ['dropdown-menu w-100', this.popupOpen ? 'show' : '']
}, this.getDropdownItems(h));
}, },
setupAwesomplete() { getDropdownItems(h) {
const input = this.$refs.input; return this.popupItems.map((item, i) => {
this.awesomplete = new Awesomplete(input, { return h('a', {
minChars: 0, class: ['dropdown-item', this.highlightedItem === i ? 'active text-dark' : ''],
maxItems: 99, attrs: {
sort: this.sort(), href: '#',
filter: this.filter(), 'data-value': item.value
item: (text, input) => { },
const li = document.createElement('li'); on: {
li.classList.add('dropdown-item'); click: e => {
li.classList.add('d-flex'); e.preventDefault();
li.classList.add('align-items-center'); this.onItemClick(item);
li.innerHTML = text.label; this.popupOpen = false;
return li;
} }
},
domProps: {
innerHTML: item.label
}
})
}); });
this.bindEvents();
}, },
bindEvents() { onItemClick(item) {
this.handleChange(item.value);
}, },
sort() { async updateList(keyword) {
// return a function that handles sorting of items this.popupItems = await this.getList(keyword);
this.popupOpen = this.popupItems.length > 0;
}, },
filter() { async getList(text='') {
// return a function that filters list suggestions based on input let list = await this.docfield.getList(text);
return Awesomplete.FILTER_CONTAINS list = list.map(item => {
if (typeof item === 'string') {
return {
label: item,
value: item
}
}
return item;
});
return list.filter(item => {
const string = (item.label + ' ' + item.value).toLowerCase();
return string.includes(text.toLowerCase());
});
} }
} }
}; };
</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>

View File

@ -37,7 +37,10 @@ export default {
attrs: { attrs: {
'data-fieldname': this.docfield.fieldname 'data-fieldname': this.docfield.fieldname
} }
}, [this.getLabelElement(h), this.getInputElement(h)]); }, this.getChildrenElement(h));
},
getChildrenElement(h) {
return [this.getLabelElement(h), this.getInputElement(h)]
}, },
getLabelElement(h) { getLabelElement(h) {
return h('label', { return h('label', {

View File

@ -1,7 +1,6 @@
<script> <script>
import frappe from 'frappejs'; import frappe from 'frappejs';
import feather from 'feather-icons'; import feather from 'feather-icons';
import Awesomplete from 'awesomplete';
import Autocomplete from './Autocomplete'; import Autocomplete from './Autocomplete';
import FeatherIcon from 'frappejs/ui/components/FeatherIcon'; import FeatherIcon from 'frappejs/ui/components/FeatherIcon';
import Form from '../Form/Form'; import Form from '../Form/Form';
@ -18,9 +17,11 @@ export default {
async getList(query) { async getList(query) {
const list = await frappe.db.getAll({ const list = await frappe.db.getAll({
doctype: this.getTarget(), doctype: this.getTarget(),
filters: query ? { filters: query
? {
keywords: ['like', query] keywords: ['like', query]
} : null, }
: null,
fields: ['name'], fields: ['name'],
limit: 50 limit: 50
}); });
@ -41,31 +42,31 @@ export default {
value: '__newItem' value: '__newItem'
}); });
}, },
getWrapperElement(h) { getChildrenElement(h) {
return [
this.getLabelElement(h),
this.getInputGroupElement(h),
this.getDropdownElement(h)
];
},
getInputGroupElement(h) {
return h( return h(
'div', 'div',
{ {
class: ['form-group', ...this.wrapperClass], class: ['input-group']
attrs: {
'data-fieldname': this.docfield.fieldname
}
}, },
[ [
this.getLabelElement(h), this.getInputElement(h),
this.getInputGroupElement(h) h(
'div',
{
class: ['input-group-append']
},
[this.getFollowLink(h)]
)
] ]
); );
}, },
getInputGroupElement(h) {
return h('div', {
class: ['input-group']
}, [
this.getInputElement(h),
h('div', {
class: ['input-group-append']
}, [this.getFollowLink(h)])
]);
},
getFollowLink(h) { getFollowLink(h) {
const doctype = this.getTarget(); const doctype = this.getTarget();
const name = this.value; const name = this.value;
@ -84,7 +85,9 @@ export default {
} }
}); });
return h('button', { return h(
'button',
{
class: ['btn btn-sm btn-outline-light border d-flex'], class: ['btn btn-sm btn-outline-light border d-flex'],
attrs: { attrs: {
type: 'button' type: 'button'
@ -94,7 +97,9 @@ export default {
this.$router.push(`/edit/${doctype}/${name}`); this.$router.push(`/edit/${doctype}/${name}`);
} }
} }
}, [arrow]) },
[arrow]
);
}, },
getTarget() { getTarget() {
return this.docfield.target; return this.docfield.target;
@ -126,21 +131,19 @@ export default {
if (suggestion.value === '__newItem') { if (suggestion.value === '__newItem') {
return true; return true;
} }
return Awesomplete.FILTER_CONTAINS(suggestion, txt);
}; };
}, },
bindEvents() { onItemClick(item) {
if (item.value === '__newItem') {
this.openFormModal();
} else {
this.handleChange(item.value);
}
},
async openFormModal() {
const input = this.$refs.input; const input = this.$refs.input;
input.addEventListener('awesomplete-select', async e => {
// new item action
if (e.text && e.text.value === '__newItem') {
e.preventDefault();
const newDoc = await frappe.getNewDoc(this.getTarget()); const newDoc = await frappe.getNewDoc(this.getTarget());
this.$formModal.open(newDoc, {
this.$formModal.open(
newDoc,
{
defaultValues: { defaultValues: {
name: input.value !== '__newItem' ? input.value : null name: input.value !== '__newItem' ? input.value : null
}, },
@ -151,8 +154,7 @@ export default {
this.handleChange(''); this.handleChange('');
} }
} }
} });
);
newDoc.on('afterInsert', data => { newDoc.on('afterInsert', data => {
// if new doc was created // if new doc was created
@ -161,8 +163,6 @@ export default {
this.$formModal.close(); this.$formModal.close();
}); });
} }
});
}
} }
}; };
</script> </script>

View File

@ -1,5 +1,6 @@
import ListAndForm from '../pages/ListAndForm'; import ListAndForm from '../pages/ListAndForm';
import ListAndPrintView from '../pages/ListAndPrintView'; import ListAndPrintView from '../pages/ListAndPrintView';
import Tree from '../components/Tree';
export default [ export default [
{ {
@ -14,6 +15,12 @@ export default [
component: ListAndForm, component: ListAndForm,
props: true props: true
}, },
{
path: '/tree/:doctype',
name: 'Tree',
component: Tree,
props: true
},
{ {
path: '/print/:doctype/:name', path: '/print/:doctype/:name',
name: 'PrintView', name: 'PrintView',