2
0
mirror of https://github.com/frappe/books.git synced 2024-09-20 11:29:00 +00:00

Tree as Web Component

This commit is contained in:
Faris Ansari 2018-04-09 17:58:51 +05:30
parent f7db1206b0
commit 2909cd74b7
6 changed files with 314 additions and 119 deletions

View File

@ -4,7 +4,6 @@
@import "node_modules/flatpickr/dist/themes/airbnb"; @import "node_modules/flatpickr/dist/themes/airbnb";
@import "node_modules/codemirror/lib/codemirror"; @import "node_modules/codemirror/lib/codemirror";
@import "node_modules/frappe-datatable/dist/frappe-datatable"; @import "node_modules/frappe-datatable/dist/frappe-datatable";
// @import "node_modules/octicons/build/build.css";
@import "./variables.scss"; @import "./variables.scss";
@import "./indicators.scss"; @import "./indicators.scss";

View File

@ -1,111 +1,123 @@
@import "./variables.scss"; @import "./variables.scss";
.tree { .tree-body {
padding: $spacer-3 $spacer-4; padding: $spacer-3 $spacer-4;
} }
.tree li { f-tree, f-tree-node {
list-style: none; display: block;
} }
ul.tree-children { f-tree-node:hover {
padding-left: $spacer-4;
}
.tree-link {
cursor: pointer;
display: flex;
align-items: center;
width: 100%;
}
.tree-link:hover {
background-color: $gray-100; background-color: $gray-100;
cursor: pointer;
} }
.tree-link .node-parent { // .tree {
color: $gray-600; // padding: $spacer-3 $spacer-4;
width: 24px; // }
height: 24px;
text-align: center;
}
.tree-link .node-leaf { // .tree li {
color: $gray-400; // list-style: none;
} // }
.tree-link .node-parent, .tree-link .node-leaf { // ul.tree-children {
padding: $spacer-2; // padding-left: $spacer-4;
} // }
.tree-link.active { // .tree-link {
a { // cursor: pointer;
color: $gray-600; // display: flex;
} // align-items: center;
} // width: 100%;
// }
.tree-hover { // .tree-link:hover {
background-color: $gray-200; // background-color: $gray-100;
min-height: 20px; // }
border: 1px solid $gray-600;
}
.tree-node-toolbar { // .tree-link .node-parent {
display: inline-block; // color: $gray-200;
padding: 0px 5px; // width: 24px;
margin-left: 15px; // height: 24px;
margin-bottom: -4px; // }
margin-top: -8px;
}
// @media (max-width: @screen-xs) { // .tree-link .node-leaf {
// ul.tree-children { // color: $gray-400;
// padding-left: 10px; // }
// .tree-link .node-parent, .tree-link .node-leaf {
// padding: $spacer-2;
// }
// .tree-link.active {
// a {
// color: $gray-600;
// } // }
// } // }
// decoration // .tree-hover {
// .tree, .tree-node { // background-color: $gray-200;
.tree.with-skeleton, .tree.with-skeleton .tree-node { // min-height: 20px;
position: relative; // border: 1px solid $gray-600;
// }
&.opened::before, &:last-child::after { // .tree-node-toolbar {
content: ''; // display: inline-block;
position: absolute; // padding: 0px 5px;
top: 12px; // margin-left: 15px;
left: 7px; // margin-bottom: -4px;
height: calc(100% - 23px); // margin-top: -8px;
width: 1px; // }
background: $gray-400;
z-index: -1;
}
&:last-child::after { // // @media (max-width: @screen-xs) {
top: 11px; // // ul.tree-children {
left: -13px; // // padding-left: 10px;
height: calc(100% - 15px); // // }
width: 3px; // // }
background: #fff;
}
&.opened > .tree-children > .tree-node > .tree-link::before { // // decoration
content: ''; // // .tree, .tree-node {
position: absolute; // .tree.with-skeleton, .tree.with-skeleton .tree-node {
width: 18px; // position: relative;
height: 1px;
top: 10px;
left: -12px;
z-index: -1;
background: $gray-400;
}
}
.tree.with-skeleton.opened::before { // &.opened::before, &:last-child::after {
left: 22px; // content: '';
top: 33px; // position: absolute;
height: calc(100% - 67px); // top: 12px;
} // left: 7px;
// height: calc(100% - 23px);
// width: 1px;
// background: $gray-400;
// z-index: -1;
// }
.tree-link.active ~ .balance-area { // &:last-child::after {
color: $gray-600 !important; // top: 11px;
} // left: -13px;
// height: calc(100% - 15px);
// width: 3px;
// background: #fff;
// }
// &.opened > .tree-children > .tree-node > .tree-link::before {
// content: '';
// position: absolute;
// width: 18px;
// height: 1px;
// top: 10px;
// left: -12px;
// z-index: -1;
// background: $gray-400;
// }
// }
// .tree.with-skeleton.opened::before {
// left: 22px;
// top: 33px;
// height: calc(100% - 67px);
// }
// .tree-link.active ~ .balance-area {
// color: $gray-600 !important;
// }

View File

@ -64,6 +64,35 @@ module.exports = {
element.parentNode.removeChild(element); element.parentNode.removeChild(element);
}, },
on(element, event, selector, handler) {
if (!handler) {
handler = selector;
this.bind(element, event, handler);
} else {
this.delegate(element, event, selector, handler);
}
},
off(element, event, handler) {
element.removeEventListener(event, handler);
},
bind(element, event, callback) {
event.split(/\s+/).forEach(function (event) {
element.addEventListener(event, callback);
});
},
delegate(element, event, selector, callback) {
element.addEventListener(event, function (e) {
const delegatedTarget = e.target.closest(selector);
if (delegatedTarget) {
e.delegatedTarget = delegatedTarget;
callback.call(this, e, delegatedTarget);
}
});
},
empty(element) { empty(element) {
while (element.firstChild) { while (element.firstChild) {
element.removeChild(element.firstChild); element.removeChild(element.firstChild);

View File

@ -2,15 +2,106 @@ const frappe = require('frappejs');
const octicons = require('octicons'); const octicons = require('octicons');
const utils = require('frappejs/client/ui/utils'); const utils = require('frappejs/client/ui/utils');
class Tree { const iconSet = {
constructor({parent, label, iconSet, withSkeleton, method}) { open: octicons["triangle-down"].toSVG({ width: "12", height: "12", "class": "tree-icon-open" }),
close: octicons["triangle-right"].toSVG({ width: "12", height: "12", "class": "tree-icon-closed" })
};
let TreeTemplate = document.createElement('template');
TreeTemplate.innerHTML = `
<div class="tree">
<slot></slot>
</div>
`;
class Tree extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' })
.appendChild(TreeTemplate.content.cloneNode(true));
}
}
window.customElements.define('f-tree', Tree);
let TreeNodeTemplate = document.createElement('template');
TreeNodeTemplate.innerHTML = `
<style>
.tree-node-content {
display: flex;
align-items: center;
}
.tree-node-icon {
width: 2rem;
height: 2rem;
}
.tree-node-icon svg {
padding: 0.5rem;
}
</style>
<div class="tree-node-content">
<div class="tree-node-icon"></div>
<div class="tree-node-label"></div>
<div class="tree-node-actions"></div>
</div>
<slot></slot>
`;
class TreeNode extends HTMLElement {
static get observedAttributes() {
return ['label', 'expanded']
}
constructor() {
super();
let shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.appendChild(TreeNodeTemplate.content.cloneNode(true));
this.iconEl = shadowRoot.querySelector('.tree-node-icon');
this.labelEl = shadowRoot.querySelector('.tree-node-label');
this.actionsEl = shadowRoot.querySelector('.tree-node-actions');
}
attributeChangedCallback(name, oldValue, newValue) {
console.log(name, oldValue, newValue);
switch(name) {
case 'label': {
this.labelEl.innerHTML = newValue || '';
break;
}
case 'expanded': {
this.expanded = this.hasAttribute('expanded');
if (this.expanded) {
this.iconEl.innerHTML = iconSet.open;
} else {
this.iconEl.innerHTML = iconSet.close;
}
break;
}
default: break;
}
}
}
window.customElements.define('f-tree-node', TreeNode);
class TreeOld {
constructor({ parent, label, iconSet, withSkeleton, method }) {
Object.assign(this, arguments[0]); Object.assign(this, arguments[0]);
this.nodes = {}; this.nodes = {};
if(!iconSet) { if (!iconSet) {
this.iconSet = { this.iconSet = {
open: octicons["triangle-down"].toSVG({ "width": 10, "class": "node-parent"}), open: octicons["triangle-down"].toSVG({ "width": 10, "class": "node-parent" }),
closed: octicons["triangle-right"].toSVG({ "width": 5, "class": "node-parent"}), closed: octicons["triangle-right"].toSVG({ "width": 5, "class": "node-parent" }),
leaf: octicons["primitive-dot"].toSVG({ "width": 7, "class": "node-leaf"}) leaf: octicons["primitive-dot"].toSVG({ "width": 7, "class": "node-leaf" })
}; };
} }
this.make(); this.make();
@ -31,7 +122,7 @@ class Tree {
// this.loadChildren(this.selectedNode.parentNode, true); // this.loadChildren(this.selectedNode.parentNode, true);
} }
async loadChildren(node, deep=false) { async loadChildren(node, deep = false) {
let children = !deep ? await this.method(node) : await this.getAllNodes(node); let children = !deep ? await this.method(node) : await this.getAllNodes(node);
this.renderNodeChildren(node, children); this.renderNodeChildren(node, children);
} }
@ -40,7 +131,7 @@ class Tree {
dataList.map(d => { this.renderNodeChildren(this.nodes[d.parent], d.data); }); dataList.map(d => { this.renderNodeChildren(this.nodes[d.parent], d.data); });
} }
renderNodeChildren(node, dataSet=[]) { renderNodeChildren(node, dataSet = []) {
frappe.ui.empty(node.childrenList); frappe.ui.empty(node.childrenList);
dataSet.forEach(data => { dataSet.forEach(data => {
@ -67,7 +158,7 @@ class Tree {
expandable: expandable, expandable: expandable,
}; };
if(parentNode){ if (parentNode) {
node.parentNode = parentNode; node.parentNode = parentNode;
node.parent = parentNode.childrenList; node.parent = parentNode.childrenList;
node.isRoot = 0; node.isRoot = 0;
@ -90,8 +181,8 @@ class Tree {
}); });
let iconHtml = ''; let iconHtml = '';
if(this.iconSet) { if (this.iconSet) {
iconHtml = node.expandable ? this.iconSet.closed : this.iconSet.leaf; iconHtml = node.expandable ? this.iconSet.closed : '';
} }
let labelEl = `<span class="tree-label"> ${node.label}</span>`; let labelEl = `<span class="tree-label"> ${node.label}</span>`;
@ -118,17 +209,17 @@ class Tree {
async onNodeClick(node, click = true) { async onNodeClick(node, click = true) {
this.setSelectedNode(node); this.setSelectedNode(node);
if(click) { if (click) {
this.onClick && this.onClick(node); this.onClick && this.onClick(node);
} }
await this.expandNode(node); await this.expandNode(node);
// select link // select link
utils.activate(this.tree, node.treeLink, 'tree-link', 'active'); utils.activate(this.tree, node.treeLink, 'tree-link', 'active');
if(node.toolbar) this.showToolbar(node); if (node.toolbar) this.showToolbar(node);
} }
async expandNode(node) { async expandNode(node) {
if(node.expandable) { if (node.expandable) {
await this.toggleNode(node); await this.toggleNode(node);
} }
@ -139,11 +230,11 @@ class Tree {
} }
async toggleNode(node) { async toggleNode(node) {
if(!node.loaded) await this.loadChildren(node); if (!node.loaded) await this.loadChildren(node);
// expand children // expand children
if(node.childrenList) { if (node.childrenList) {
if(node.childrenList.innerHTML.length) { if (node.childrenList.innerHTML.length) {
if (node.expanded) { if (node.expanded) {
node.childrenList.classList.add('hide'); node.childrenList.classList.add('hide');
} else { } else {
@ -152,7 +243,7 @@ class Tree {
} }
// open close icon // open close icon
if(this.iconSet) { if (this.iconSet) {
const oldIcon = node.treeLink.querySelector('svg'); const oldIcon = node.treeLink.querySelector('svg');
const newIconKey = node.expanded ? 'closed' : 'open'; const newIconKey = node.expanded ? 'closed' : 'open';
const newIcon = frappe.ui.create(this.iconSet[newIconKey]); const newIcon = frappe.ui.create(this.iconSet[newIconKey]);

View File

@ -56,18 +56,83 @@ module.exports = class BaseTree extends BaseList {
} }
renderTree(rootLabel) { renderTree(rootLabel) {
this.tree = new Tree({ // const tree = new Tree();
// tree.getChildNodes = async node => {
// const children = await this.getData(node) || [];
// return children.map(d => ({
// label: d.name,
// value: d.name,
// expandable: d.isGroup
// }));
// }
// tree.rootNode = {
// label: rootLabel,
// value: rootLabel,
// isRoot: 1,
// expandable: 1
// }
// this.body.appendChild(tree);
this.rootNode = {
label: rootLabel, label: rootLabel,
parent: this.body, value: rootLabel,
method: async node => { isRoot: true,
const children = await this.getData(node) || []; expanded: true,
return children.map(d => ({ children: []
label: d.name, }
value: d.name,
expandable: d.isGroup const getNodeHTML = node =>
})); `<f-tree-node
} label="${node.label}"
value="${node.value}"
${node.expanded ? 'expanded' : ''}
${node.isRoot ? 'is-root' : ''}>
</f-tree-node>`;
this.treeWrapper = frappe.ui.create('f-tree');
this.rootNode.el = frappe.ui.create(getNodeHTML(this.rootNode), {
inside: this.treeWrapper
}); });
this.treeWrapper = frappe.ui.create(`
<f-tree>
${getNodeHTML(this.rootNode)}
</f-tree>
`);
this.body.appendChild(this.treeWrapper);
frappe.ui.on(this.treeWrapper, 'click', 'f-tree-node', async (e, treeNode) => {
if (treeNode.expanded) {
treeNode.removeAttribute('expanded');
} else {
treeNode.setAttribute('expanded', '');
}
let node = null;
// if (treeNode.hasAttribute('is-root')) {
// node = this.rootNode;
// } else {
// }
});
// this.tree = new Tree({
// label: rootLabel,
// parent: this.body,
// method: async node => {
// const children = await this.getData(node) || [];
// return children.map(d => ({
// label: d.name,
// value: d.name,
// expandable: d.isGroup
// }));
// }
// });
} }
async getData(node) { async getData(node) {
@ -119,7 +184,7 @@ module.exports = class BaseTree extends BaseList {
}); });
this.page.body.addEventListener('click', (event) => { this.page.body.addEventListener('click', (event) => {
if(event.target.classList.contains('checkbox')) { if (event.target.classList.contains('checkbox')) {
this.trigger('state-change'); this.trigger('state-change');
} }
}) })
@ -138,7 +203,7 @@ module.exports = class BaseTree extends BaseList {
this.searchInput = this.toolbar.querySelector('input'); this.searchInput = this.toolbar.querySelector('input');
this.searchInput.addEventListener('keypress', (event) => { this.searchInput.addEventListener('keypress', (event) => {
if (event.keyCode===13) { if (event.keyCode === 13) {
this.refresh(); this.refresh();
} }
}); });

View File

@ -5,7 +5,6 @@ module.exports = {
format: 'cjs' format: 'cjs'
}, },
plugins: [ plugins: [
require('rollup-plugin-sass')(),
require('rollup-plugin-postcss')({ require('rollup-plugin-postcss')({
extract: true, extract: true,
plugins: [ plugins: [