2
0
mirror of https://github.com/frappe/books.git synced 2024-11-10 07:40:55 +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,10 +26,14 @@ export default {
},
computed: {
iconSVG() {
return feather.icons[this.name].toSvg({
width: this.size,
height: this.size
});
const icon = feather.icons[this.name];
if (!icon) {
return '';
}
return icon.toSvg({
width: 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>
import Awesomplete from 'awesomplete';
import Data from './Data';
export default {
extends: Data,
data() {
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: {
getInputListeners() {
return {
input: e => {
this.updateList(e.target.value);
},
'awesomplete-select': e => {
const value = e.text.value;
this.handleChange(value);
keydown: e => {
if (e.keyCode === 38) {
// 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 => {
await this.updateList();
this.awesomplete.evaluate();
this.awesomplete.open();
},
blur: () => {
setTimeout(() => {
this.popupOpen = false;
}, 200);
}
}
},
async updateList(value) {
this.awesomplete.list = await this.getList(value);
getChildrenElement(h) {
return [
this.getLabelElement(h),
this.getInputElement(h),
this.getDropdownElement(h)
];
},
getList(text) {
return this.docfield.getList(text);
getDropdownElement(h) {
return h('div', {
class: ['dropdown-menu w-100', this.popupOpen ? 'show' : '']
}, this.getDropdownItems(h));
},
setupAwesomplete() {
const input = this.$refs.input;
this.awesomplete = new Awesomplete(input, {
minChars: 0,
maxItems: 99,
sort: this.sort(),
filter: this.filter(),
item: (text, input) => {
const li = document.createElement('li');
li.classList.add('dropdown-item');
li.classList.add('d-flex');
li.classList.add('align-items-center');
li.innerHTML = text.label;
return li;
}
getDropdownItems(h) {
return this.popupItems.map((item, i) => {
return h('a', {
class: ['dropdown-item', this.highlightedItem === i ? 'active text-dark' : ''],
attrs: {
href: '#',
'data-value': item.value
},
on: {
click: e => {
e.preventDefault();
this.onItemClick(item);
this.popupOpen = false;
}
},
domProps: {
innerHTML: item.label
}
})
});
this.bindEvents();
},
bindEvents() {
onItemClick(item) {
this.handleChange(item.value);
},
sort() {
// return a function that handles sorting of items
async updateList(keyword) {
this.popupItems = await this.getList(keyword);
this.popupOpen = this.popupItems.length > 0;
},
filter() {
// return a function that filters list suggestions based on input
return Awesomplete.FILTER_CONTAINS
async getList(text='') {
let list = await this.docfield.getList(text);
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>
<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: {
'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) {
return h('label', {
@ -77,9 +80,9 @@ export default {
},
getInputListeners() {
return {
change: (e) => {
this.handleChange(e.target.value);
}
change: (e) => {
this.handleChange(e.target.value);
}
};
},
getInputChildren() {

View File

@ -1,7 +1,6 @@
<script>
import frappe from 'frappejs';
import feather from 'feather-icons';
import Awesomplete from 'awesomplete';
import Autocomplete from './Autocomplete';
import FeatherIcon from 'frappejs/ui/components/FeatherIcon';
import Form from '../Form/Form';
@ -18,9 +17,11 @@ export default {
async getList(query) {
const list = await frappe.db.getAll({
doctype: this.getTarget(),
filters: query ? {
keywords: ['like', query]
} : null,
filters: query
? {
keywords: ['like', query]
}
: null,
fields: ['name'],
limit: 50
});
@ -41,31 +42,31 @@ export default {
value: '__newItem'
});
},
getWrapperElement(h) {
getChildrenElement(h) {
return [
this.getLabelElement(h),
this.getInputGroupElement(h),
this.getDropdownElement(h)
];
},
getInputGroupElement(h) {
return h(
'div',
{
class: ['form-group', ...this.wrapperClass],
attrs: {
'data-fieldname': this.docfield.fieldname
}
class: ['input-group']
},
[
this.getLabelElement(h),
this.getInputGroupElement(h)
this.getInputElement(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) {
const doctype = this.getTarget();
const name = this.value;
@ -84,17 +85,21 @@ export default {
}
});
return h('button', {
class: ['btn btn-sm btn-outline-light border d-flex'],
attrs: {
type: 'button'
},
on: {
click: () => {
this.$router.push(`/edit/${doctype}/${name}`);
return h(
'button',
{
class: ['btn btn-sm btn-outline-light border d-flex'],
attrs: {
type: 'button'
},
on: {
click: () => {
this.$router.push(`/edit/${doctype}/${name}`);
}
}
}
}, [arrow])
},
[arrow]
);
},
getTarget() {
return this.docfield.target;
@ -103,7 +108,7 @@ export default {
return (a, b) => {
a = a.toLowerCase();
b = b.toLowerCase();
if (a.value === '__newItem') {
return 1;
}
@ -126,42 +131,37 @@ export default {
if (suggestion.value === '__newItem') {
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;
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());
this.$formModal.open(
newDoc,
{
defaultValues: {
name: input.value !== '__newItem' ? input.value : null
},
onClose: () => {
// if new doc was not created
// then reset the input value
if (this.value === '__newItem') {
this.handleChange('');
}
}
}
);
newDoc.on('afterInsert', data => {
// if new doc was created
// then set the name of the doc in input
this.handleChange(newDoc.name);
this.$formModal.close();
});
const newDoc = await frappe.getNewDoc(this.getTarget());
this.$formModal.open(newDoc, {
defaultValues: {
name: input.value !== '__newItem' ? input.value : null
},
onClose: () => {
// if new doc was not created
// then reset the input value
if (this.value === '__newItem') {
this.handleChange('');
}
}
});
newDoc.on('afterInsert', data => {
// if new doc was created
// then set the name of the doc in input
this.handleChange(newDoc.name);
this.$formModal.close();
});
}
}
};

View File

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