mirror of
https://github.com/frappe/books.git
synced 2024-11-10 07:40:55 +00:00
Removed Redundant files from client folder (#99)
* Removed redundant files from client folder * removed console statement
This commit is contained in:
parent
0a43015249
commit
2614ff8230
@ -1,43 +0,0 @@
|
||||
let templates = {};
|
||||
|
||||
class BaseComponent extends HTMLElement {
|
||||
constructor(name) {
|
||||
super();
|
||||
|
||||
this._name = name;
|
||||
this._shadowRoot = this.attachShadow({ mode: 'open' });
|
||||
|
||||
if (!templates[name]) {
|
||||
makeTemplate(name, this.templateHTML);
|
||||
}
|
||||
|
||||
let template = getTemplate(name);
|
||||
this._shadowRoot.appendChild(template);
|
||||
}
|
||||
|
||||
triggerEvent(eventName, detail = {}) {
|
||||
const event = new CustomEvent(eventName, {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail
|
||||
});
|
||||
this.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function makeTemplate(name, html) {
|
||||
if (!templates[name]) {
|
||||
let template = document.createElement('template');
|
||||
template.id = name;
|
||||
template.innerHTML = html;
|
||||
|
||||
templates[name] = template;
|
||||
}
|
||||
}
|
||||
|
||||
function getTemplate(name) {
|
||||
return templates[name].content.cloneNode(true);
|
||||
}
|
||||
|
||||
module.exports = BaseComponent;
|
@ -1,11 +0,0 @@
|
||||
<!-- styles -->
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- template -->
|
||||
<div class="tree">
|
||||
<slot></slot>
|
||||
</div>
|
@ -1,19 +0,0 @@
|
||||
const BaseComponent = require('../baseComponent');
|
||||
const TreeNode = require('./treeNode');
|
||||
|
||||
class Tree extends BaseComponent {
|
||||
get templateHTML() {
|
||||
return require('./index.html');
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super('Tree');
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('f-tree', Tree);
|
||||
|
||||
module.exports = {
|
||||
Tree,
|
||||
TreeNode
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
<!-- styles -->
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tree-node {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tree-node:hover {
|
||||
background-color: gray;
|
||||
background-color: var(--tree-node-hover);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tree-node:hover .tree-node-actions {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tree-node-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tree-node-actions {
|
||||
display: none;
|
||||
margin-left: 2rem;
|
||||
}
|
||||
|
||||
.tree-node-icon {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.tree-node-icon svg {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.tree-node-children {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- template -->
|
||||
<div class="tree-node">
|
||||
<div class="tree-node-content">
|
||||
<div class="tree-node-icon"></div>
|
||||
<div class="tree-node-label"></div>
|
||||
</div>
|
||||
<div class="tree-node-actions">
|
||||
<slot name="actions"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tree-node-children">
|
||||
<slot></slot>
|
||||
</div>
|
@ -1,94 +0,0 @@
|
||||
const octicons = require('octicons');
|
||||
const BaseComponent = require('../baseComponent');
|
||||
|
||||
const iconSet = {
|
||||
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" })
|
||||
};
|
||||
|
||||
class TreeNode extends BaseComponent {
|
||||
static get observedAttributes() {
|
||||
return ['label', 'expanded'];
|
||||
}
|
||||
|
||||
get templateHTML() {
|
||||
return require('./treeNode.html');
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super('TreeNode');
|
||||
|
||||
let shadowRoot = this._shadowRoot;
|
||||
this.iconEl = shadowRoot.querySelector('.tree-node-icon');
|
||||
this.labelEl = shadowRoot.querySelector('.tree-node-label');
|
||||
this.actionsEl = shadowRoot.querySelector('.tree-node-actions');
|
||||
this.childrenEl = shadowRoot.querySelector('.tree-node-children');
|
||||
|
||||
this.addEventListener('click', e => {
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
if (e.target.matches('[slot="actions"]')) {
|
||||
this.triggerEvent('tree-node-action', {
|
||||
actionEl: e.target
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.expanded) {
|
||||
this.removeAttribute('expanded');
|
||||
} else {
|
||||
this.setAttribute('expanded', '');
|
||||
}
|
||||
});
|
||||
this.onExpand();
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldValue, newValue) {
|
||||
switch(name) {
|
||||
case 'label': {
|
||||
this.labelEl.innerHTML = newValue || '';
|
||||
break;
|
||||
}
|
||||
|
||||
case 'expanded': {
|
||||
const isExpanded = this.hasAttribute('expanded');
|
||||
this.onExpand(isExpanded);
|
||||
break;
|
||||
}
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
onExpand(isExpanded = false) {
|
||||
if (this.isLeaf) return;
|
||||
|
||||
if (isExpanded) {
|
||||
this.iconEl.innerHTML = iconSet.open;
|
||||
this.childrenEl.style.display = '';
|
||||
} else {
|
||||
this.iconEl.innerHTML = iconSet.close;
|
||||
this.childrenEl.style.display = 'none';
|
||||
}
|
||||
|
||||
this.triggerEvent('tree-node-expand', {
|
||||
expanded: isExpanded
|
||||
});
|
||||
}
|
||||
|
||||
get isRoot() {
|
||||
return this.hasAttribute('is-root');
|
||||
}
|
||||
|
||||
get expanded() {
|
||||
return this.hasAttribute('expanded');
|
||||
}
|
||||
|
||||
get isLeaf() {
|
||||
return this.hasAttribute('is-leaf');
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('f-tree-node', TreeNode);
|
||||
|
||||
module.exports = TreeNode;
|
@ -1,47 +0,0 @@
|
||||
const Modal = require('frappejs/client/ui/modal');
|
||||
const view = require('frappejs/client/view');
|
||||
|
||||
module.exports = class FormModal extends Modal {
|
||||
constructor(doctype, name) {
|
||||
super({title: doctype});
|
||||
this.doctype = doctype;
|
||||
}
|
||||
|
||||
async showWith(doctype, name) {
|
||||
if (!name) name = doctype;
|
||||
this.show();
|
||||
await this.setDoc(doctype, name);
|
||||
}
|
||||
|
||||
async setDoc(doctype, name) {
|
||||
if (!this.form) {
|
||||
this.makeForm();
|
||||
}
|
||||
await this.form.setDoc(doctype, name);
|
||||
let input = this.modal.querySelector('input') || this.modal.querySelector('select');
|
||||
input && input.focus();
|
||||
}
|
||||
|
||||
makeForm() {
|
||||
this.form = new (view.getFormClass(this.doctype))({
|
||||
doctype: this.doctype,
|
||||
parent: this.getBody(),
|
||||
container: this,
|
||||
actions: ['save']
|
||||
});
|
||||
|
||||
this.form.on('save', async () => {
|
||||
await this.trigger('save');
|
||||
this.hide();
|
||||
});
|
||||
}
|
||||
|
||||
addButton(label, className, action) {
|
||||
if (className === 'primary') {
|
||||
return this.addPrimary(label, action).get(0);
|
||||
} else {
|
||||
return this.addSecondary(label, action).get(0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
const Page = require('frappejs/client/view/page');
|
||||
const view = require('frappejs/client/view');
|
||||
const frappe = require('frappejs');
|
||||
|
||||
module.exports = class FormPage extends Page {
|
||||
constructor(doctype) {
|
||||
let meta = frappe.getMeta(doctype);
|
||||
super({title: `Edit ${meta.name}`, hasRoute: true});
|
||||
this.wrapper.classList.add('page-form');
|
||||
this.meta = meta;
|
||||
this.doctype = doctype;
|
||||
|
||||
this.form = new (view.getFormClass(doctype))({
|
||||
doctype: doctype,
|
||||
parent: this.body,
|
||||
container: this,
|
||||
actions: ['save', 'delete', 'duplicate', 'settings', 'print']
|
||||
});
|
||||
|
||||
if (this.meta.pageSettings && this.meta.pageSettings.hideTitle) {
|
||||
this.titleElement.classList.add('hide');
|
||||
}
|
||||
|
||||
// if name is different after saving, change the route
|
||||
this.form.on('save', async (params) => {
|
||||
let route = frappe.router.getRoute();
|
||||
if (this.form.doc.name && !(route && route[2] === this.form.doc.name)) {
|
||||
await frappe.router.setRoute('edit', this.form.doc.doctype, this.form.doc.name);
|
||||
frappe.ui.showAlert({message: 'Added', color: 'green'});
|
||||
}
|
||||
});
|
||||
|
||||
this.form.on('delete', async (params) => {
|
||||
this.hide();
|
||||
await frappe.router.setRoute('list', this.form.doctype);
|
||||
});
|
||||
}
|
||||
|
||||
async show(params) {
|
||||
super.show();
|
||||
try {
|
||||
await this.form.setDoc(params.doctype, params.name);
|
||||
frappe.desk.setActiveDoc(this.form.doc);
|
||||
} catch (e) {
|
||||
this.renderError(e.statusCode, e.message);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,181 +0,0 @@
|
||||
const frappe = require('frappejs');
|
||||
// const Search = require('./search');
|
||||
const Router = require('frappejs/common/router');
|
||||
const Page = require('frappejs/client/view/page');
|
||||
|
||||
const views = {};
|
||||
views.Form = require('./formpage');
|
||||
views.List = require('./listpage');
|
||||
views.Tree = require('./treepage');
|
||||
views.Print = require('./printpage');
|
||||
views.FormModal = require('./formmodal');
|
||||
views.Table = require('./tablepage');
|
||||
const DeskMenu = require('./menu');
|
||||
|
||||
module.exports = class Desk {
|
||||
constructor(columns=2) {
|
||||
frappe.router = new Router();
|
||||
frappe.router.listen();
|
||||
|
||||
let body = document.querySelector('body');
|
||||
//this.navbar = new Navbar();
|
||||
frappe.ui.empty(body);
|
||||
this.container = frappe.ui.add('div', '', body);
|
||||
this.containerRow = frappe.ui.add('div', 'row no-gutters', this.container)
|
||||
this.makeColumns(columns);
|
||||
|
||||
this.pages = {
|
||||
formModals: {},
|
||||
List: {}
|
||||
};
|
||||
|
||||
this.routeItems = {};
|
||||
|
||||
this.initRoutes();
|
||||
// this.search = new Search(this.nav);
|
||||
}
|
||||
|
||||
makeColumns(columns) {
|
||||
this.menu = null; this.center = null;
|
||||
this.columnCount = columns;
|
||||
if (columns === 3) {
|
||||
this.makeMenu();
|
||||
this.center = frappe.ui.add('div', 'col-md-4 desk-center', this.containerRow);
|
||||
this.body = frappe.ui.add('div', 'col-md-6 desk-body', this.containerRow);
|
||||
} else if (columns === 2) {
|
||||
this.makeMenu();
|
||||
this.body = frappe.ui.add('div', 'col-md-10 desk-body', this.containerRow);
|
||||
} else if (columns === 1) {
|
||||
this.makeMenuPage();
|
||||
this.body = frappe.ui.add('div', 'col-md-12 desk-body', this.containerRow);
|
||||
} else {
|
||||
throw 'columns can be 1, 2 or 3'
|
||||
}
|
||||
}
|
||||
|
||||
makeMenu() {
|
||||
this.menuColumn = frappe.ui.add('div', 'col-md-2 desk-menu', this.containerRow);
|
||||
this.menu = new DeskMenu(this.menuColumn);
|
||||
}
|
||||
|
||||
makeMenuPage() {
|
||||
// make menu page for 1 column layout
|
||||
this.menuPage = null;
|
||||
}
|
||||
|
||||
initRoutes() {
|
||||
frappe.router.add('not-found', async (params) => {
|
||||
if (!this.notFoundPage) {
|
||||
this.notFoundPage = new Page({title: 'Not Found'});
|
||||
}
|
||||
await this.notFoundPage.show();
|
||||
this.notFoundPage.renderError('Not Found', params ? params.route : '');
|
||||
})
|
||||
|
||||
frappe.router.add('list/:doctype', async (params) => {
|
||||
await this.showViewPage('List', params.doctype);
|
||||
});
|
||||
|
||||
frappe.router.add('tree/:doctype', async (params) => {
|
||||
await this.showViewPage('Tree', params.doctype);
|
||||
});
|
||||
|
||||
frappe.router.add('table/:doctype', async (params) => {
|
||||
await this.showViewPage('Table', params.doctype, params);
|
||||
})
|
||||
|
||||
frappe.router.add('edit/:doctype/:name', async (params) => {
|
||||
await this.showViewPage('Form', params.doctype, params);
|
||||
})
|
||||
|
||||
frappe.router.add('print/:doctype/:name', async (params) => {
|
||||
await this.showViewPage('Print', params.doctype, params);
|
||||
})
|
||||
|
||||
frappe.router.add('new/:doctype', async (params) => {
|
||||
let doc = await frappe.getNewDoc(params.doctype);
|
||||
// unset the name, its local
|
||||
await frappe.router.setRoute('edit', doc.doctype, doc.name);
|
||||
|
||||
// focus on new page
|
||||
frappe.desk.body.activePage.body.querySelector('input').focus();
|
||||
});
|
||||
|
||||
frappe.router.on('change', () => {
|
||||
if (this.menu) {
|
||||
this.menu.setActive();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
toggleCenter(show) {
|
||||
const current = !frappe.desk.center.classList.contains('hide');
|
||||
if (show===undefined) {
|
||||
show = current;
|
||||
} else if (!!show===!!current) {
|
||||
// no change
|
||||
return;
|
||||
}
|
||||
|
||||
// add hide
|
||||
frappe.desk.center.classList.toggle('hide', !show);
|
||||
|
||||
if (show) {
|
||||
// set body to 6
|
||||
frappe.desk.body.classList.toggle('col-md-6', true);
|
||||
frappe.desk.body.classList.toggle('col-md-10', false);
|
||||
} else {
|
||||
// set body to 10
|
||||
frappe.desk.body.classList.toggle('col-md-6', false);
|
||||
frappe.desk.body.classList.toggle('col-md-10', true);
|
||||
}
|
||||
}
|
||||
|
||||
async showViewPage(view, doctype, params) {
|
||||
if (!params) params = doctype;
|
||||
if (!this.pages[view]) this.pages[view] = {};
|
||||
if (!this.pages[view][doctype]) this.pages[view][doctype] = new views[view](doctype);
|
||||
const page = this.pages[view][doctype];
|
||||
await page.show(params);
|
||||
}
|
||||
|
||||
async showFormModal(doctype, name) {
|
||||
if (!this.pages.formModals[doctype]) {
|
||||
this.pages.formModals[doctype] = new views.FormModal(doctype);
|
||||
}
|
||||
await this.pages.formModals[doctype].showWith(doctype, name);
|
||||
return this.pages.formModals[doctype];
|
||||
}
|
||||
|
||||
async setActiveDoc(doc) {
|
||||
this.activeDoc = doc;
|
||||
if (frappe.desk.center && !frappe.desk.center.activePage) {
|
||||
await frappe.desk.showViewPage('List', doc.doctype);
|
||||
}
|
||||
if (frappe.desk.pages.List[doc.doctype]) {
|
||||
frappe.desk.pages.List[doc.doctype].list.setActiveListRow(doc.name);
|
||||
}
|
||||
}
|
||||
|
||||
setActive(item) {
|
||||
let className = 'list-group-item-secondary';
|
||||
let activeItem = this.sidebarList.querySelector('.' + className);
|
||||
if (activeItem) {
|
||||
activeItem.classList.remove(className);
|
||||
}
|
||||
item.classList.add(className);
|
||||
}
|
||||
|
||||
addSidebarItem(label, action) {
|
||||
let item = frappe.ui.add('a', 'list-group-item list-group-item-action', this.sidebarList, label);
|
||||
if (typeof action === 'string') {
|
||||
item.href = action;
|
||||
this.routeItems[action] = item;
|
||||
} else {
|
||||
item.addEventHandler('click', () => {
|
||||
action();
|
||||
this.setActive(item);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
const frappe = require('frappejs');
|
||||
const Page = require('frappejs/client/view/page');
|
||||
const view = require('frappejs/client/view');
|
||||
|
||||
module.exports = class ListPage extends Page {
|
||||
constructor(name) {
|
||||
|
||||
// if center column is present, list does not have its route
|
||||
const hasRoute = frappe.desk.center ? false : true;
|
||||
|
||||
super({
|
||||
title: frappe._("List"),
|
||||
parent: hasRoute ? frappe.desk.body : frappe.desk.center,
|
||||
hasRoute: hasRoute
|
||||
});
|
||||
|
||||
this.name = name;
|
||||
|
||||
this.list = new (view.getListClass(name))({
|
||||
doctype: name,
|
||||
parent: this.body,
|
||||
page: this
|
||||
});
|
||||
|
||||
frappe.docs.on('change', (params) => {
|
||||
if (params.doc.doctype === this.list.meta.name) {
|
||||
this.list.refreshRow(params.doc);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async show(params) {
|
||||
super.show();
|
||||
|
||||
this.setTitle(this.name===this.list.meta.name ? (this.list.meta.label || this.list.meta.name) : this.name);
|
||||
if (frappe.desk.body.activePage && frappe.router.getRoute()[0]==='list') {
|
||||
frappe.desk.body.activePage.hide();
|
||||
}
|
||||
await this.list.refresh();
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
const frappe = require('frappejs');
|
||||
|
||||
module.exports = class DeskMenu {
|
||||
constructor(parent) {
|
||||
this.parent = parent;
|
||||
this.routeItems = {};
|
||||
this.make();
|
||||
}
|
||||
|
||||
make() {
|
||||
this.listGroup = frappe.ui.add('div', 'list-body', this.parent);
|
||||
}
|
||||
|
||||
addItem(label, action) {
|
||||
let item = frappe.ui.add('div', 'list-row', this.listGroup, label);
|
||||
if (typeof action === 'string') {
|
||||
this.routeItems[action] = item;
|
||||
}
|
||||
item.addEventListener('click', async () => {
|
||||
if (typeof action === 'string') {
|
||||
await frappe.router.setRoute(action);
|
||||
} else {
|
||||
action();
|
||||
}
|
||||
this.setActive(item);
|
||||
});
|
||||
}
|
||||
|
||||
setActive() {
|
||||
if (this.routeItems[window.location.hash]) {
|
||||
let item = this.routeItems[window.location.hash];
|
||||
let className = 'active';
|
||||
let activeItem = this.listGroup.querySelector('.' + className);
|
||||
|
||||
if (activeItem) {
|
||||
activeItem.classList.remove(className);
|
||||
}
|
||||
item.classList.add(className);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
const frappe = require('frappejs');
|
||||
|
||||
module.exports = class Navbar {
|
||||
constructor({brand_label = 'Home'} = {}) {
|
||||
Object.assign(this, arguments[0]);
|
||||
this.items = {};
|
||||
this.navbar = frappe.ui.add('div', 'navbar navbar-expand-md border-bottom navbar-dark bg-dark', document.querySelector('body'));
|
||||
|
||||
this.brand = frappe.ui.add('a', 'navbar-brand', this.navbar, brand_label);
|
||||
this.brand.href = '#';
|
||||
|
||||
this.toggler = frappe.ui.add('button', 'navbar-toggler', this.navbar);
|
||||
this.toggler.setAttribute('type', 'button');
|
||||
this.toggler.setAttribute('data-toggle', 'collapse');
|
||||
this.toggler.setAttribute('data-target', 'desk-navbar');
|
||||
this.toggler.innerHTML = `<span class="navbar-toggler-icon"></span>`;
|
||||
|
||||
this.navbar_collapse = frappe.ui.add('div', 'collapse navbar-collapse', this.navbar);
|
||||
this.navbar_collapse.setAttribute('id', 'desk-navbar');
|
||||
|
||||
this.nav = frappe.ui.add('ul', 'navbar-nav mr-auto', this.navbar_collapse);
|
||||
}
|
||||
|
||||
addItem(label, route) {
|
||||
let item = frappe.ui.add('li', 'nav-item', this.nav);
|
||||
item.link = frappe.ui.add('a', 'nav-link', item, label);
|
||||
item.link.href = route;
|
||||
this.items[label] = item;
|
||||
return item;
|
||||
}
|
||||
|
||||
add_dropdown(label) {
|
||||
|
||||
}
|
||||
|
||||
add_search() {
|
||||
let form = frappe.ui.add('form', 'form-inline my-2 my-md-0', this.nav);
|
||||
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
const frappe = require('frappejs');
|
||||
const Page = require('frappejs/client/view/page');
|
||||
const { getHTML } = require('frappejs/common/print');
|
||||
const nunjucks = require('nunjucks/browser/nunjucks');
|
||||
|
||||
nunjucks.configure({ autoescape: false });
|
||||
|
||||
module.exports = class PrintPage extends Page {
|
||||
constructor(doctype) {
|
||||
let meta = frappe.getMeta(doctype);
|
||||
super({title: `${meta.name}`, hasRoute: true});
|
||||
this.meta = meta;
|
||||
this.doctype = doctype;
|
||||
this.titleElement.classList.add('hide');
|
||||
|
||||
this.addButton(frappe._('Edit'), 'primary', () => {
|
||||
frappe.router.setRoute('edit', this.doctype, this.name)
|
||||
});
|
||||
|
||||
this.addButton(frappe._('PDF'), 'secondary', async () => {
|
||||
frappe.getPDF(this.doctype, this.name);
|
||||
});
|
||||
}
|
||||
|
||||
async show(params) {
|
||||
super.show();
|
||||
this.name = params.name;
|
||||
if (this.meta.print) {
|
||||
// render
|
||||
this.renderTemplate();
|
||||
} else {
|
||||
this.renderError('No Print Settings');
|
||||
}
|
||||
}
|
||||
|
||||
async renderTemplate() {
|
||||
let doc = await frappe.getDoc(this.doctype, this.name);
|
||||
frappe.desk.setActiveDoc(doc);
|
||||
const html = await getHTML(this.doctype, this.name);
|
||||
try {
|
||||
this.body.innerHTML = html;
|
||||
// this.setTitle(doc.name);
|
||||
} catch (e) {
|
||||
this.renderError('Template Error', e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
const Page = require('frappejs/client/view/page');
|
||||
const FormLayout = require('frappejs/client/view/formLayout');
|
||||
const DataTable = require('frappe-datatable');
|
||||
const frappe = require('frappejs');
|
||||
const utils = require('frappejs/client/ui/utils');
|
||||
const Observable = require('frappejs/utils/observable');
|
||||
|
||||
// baseclass for report
|
||||
// `url` url for report
|
||||
// `getColumns` return columns
|
||||
|
||||
module.exports = class ReportPage extends Page {
|
||||
constructor({title, filterFields = []}) {
|
||||
super({title: title, hasRoute: true});
|
||||
|
||||
this.fullPage = true;
|
||||
this.filterFields = filterFields;
|
||||
|
||||
this.filterWrapper = frappe.ui.add('div', 'filter-toolbar', this.body);
|
||||
this.tableWrapper = frappe.ui.add('div', 'table-page-wrapper', this.body);
|
||||
|
||||
this.btnNew = this.addButton(frappe._('Refresh'), 'btn-primary', async () => {
|
||||
await this.run();
|
||||
});
|
||||
|
||||
this.makeFilters();
|
||||
this.setDefaultFilterValues();
|
||||
}
|
||||
|
||||
getColumns() {
|
||||
// overrride
|
||||
}
|
||||
|
||||
getRowsForDataTable(data) {
|
||||
return data;
|
||||
}
|
||||
|
||||
makeFilters() {
|
||||
this.filters = new FormLayout({
|
||||
parent: this.filterWrapper,
|
||||
fields: this.filterFields,
|
||||
doc: new Observable(),
|
||||
inline: true
|
||||
});
|
||||
|
||||
this.filterWrapper.appendChild(this.filters.form);
|
||||
}
|
||||
|
||||
setDefaultFilterValues() {
|
||||
|
||||
}
|
||||
|
||||
getFilterValues() {
|
||||
const values = {};
|
||||
for (let control of this.filters.controlList) {
|
||||
values[control.fieldname] = control.getInputValue();
|
||||
if (control.required && !values[control.fieldname]) {
|
||||
frappe.ui.showAlert({message: frappe._('{0} is mandatory', control.label), color: 'red'});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
async show(params) {
|
||||
super.show();
|
||||
await this.run();
|
||||
}
|
||||
|
||||
async run() {
|
||||
if (frappe.params && frappe.params.filters) {
|
||||
for (let key in frappe.params.filters) {
|
||||
if (this.filters.controls[key]) {
|
||||
this.filters.controls[key].setInputValue(frappe.params.filters[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
frappe.params = null;
|
||||
|
||||
|
||||
if (!this.datatable) {
|
||||
this.makeDataTable();
|
||||
}
|
||||
|
||||
const filterValues = this.getFilterValues();
|
||||
if (filterValues === false) return;
|
||||
|
||||
let data = await frappe.call({
|
||||
method: this.method,
|
||||
args: filterValues
|
||||
});
|
||||
|
||||
const rows = this.getRowsForDataTable(data);
|
||||
const columns = utils.convertFieldsToDatatableColumns(this.getColumns(data), this.layout);
|
||||
this.datatable.refresh(rows, columns);
|
||||
}
|
||||
|
||||
makeDataTable() {
|
||||
this.datatable = new DataTable(this.tableWrapper, Object.assign({
|
||||
columns: utils.convertFieldsToDatatableColumns(this.getColumns(), this.layout),
|
||||
data: [],
|
||||
layout: this.layout || 'fluid',
|
||||
}, this.datatableOptions || {}));
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
const frappe = require('frappejs');
|
||||
|
||||
module.exports = class Search {
|
||||
constructor(parent) {
|
||||
this.input = frappe.ui.add('input', 'form-control nav-search', parent);
|
||||
this.input.addEventListener('keypress', function(event) {
|
||||
if (event.keyCode===13) {
|
||||
let list = frappe.router.current_page.list;
|
||||
if (list) {
|
||||
list.search_text = this.value;
|
||||
list.run();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
const Page = require('frappejs/client/view/page');
|
||||
const frappe = require('frappejs');
|
||||
const ModelTable = require('frappejs/client/ui/modelTable');
|
||||
|
||||
module.exports = class TablePage extends Page {
|
||||
constructor(doctype) {
|
||||
let meta = frappe.getMeta(doctype);
|
||||
super({title: `${meta.label || meta.name}`, hasRoute: true});
|
||||
this.filterWrapper = frappe.ui.add('div', 'filter-toolbar', this.body);
|
||||
this.fitlerButton = frappe.ui.add('button', 'btn btn-sm btn-outline-secondary', this.filterWrapper, 'Set Filters');
|
||||
this.tableWrapper = frappe.ui.add('div', 'table-page-wrapper', this.body);
|
||||
this.doctype = doctype;
|
||||
this.fullPage = true;
|
||||
|
||||
this.fitlerButton.addEventListener('click', async () => {
|
||||
const formModal = await frappe.desk.showFormModal('FilterSelector');
|
||||
formModal.form.once('apply-filters', () => {
|
||||
formModal.hide();
|
||||
this.run();
|
||||
})
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
async show(params) {
|
||||
super.show();
|
||||
|
||||
if (!this.filterSelector) {
|
||||
this.filterSelector = await frappe.getSingle('FilterSelector');
|
||||
this.filterSelector.reset(this.doctype);
|
||||
}
|
||||
|
||||
if (frappe.params && frappe.params.filters) {
|
||||
this.filterSelector.setFilters(frappe.params.filters);
|
||||
}
|
||||
frappe.params = null;
|
||||
|
||||
if (!this.modelTable) {
|
||||
this.modelTable = new ModelTable({
|
||||
doctype: this.doctype,
|
||||
parent: this.tableWrapper,
|
||||
layout: 'fluid',
|
||||
getRowData: async (rowIndex) => {
|
||||
return await frappe.getDoc(this.doctype, this.data[rowIndex].name);
|
||||
},
|
||||
setValue: async (control) => {
|
||||
await control.handleChange();
|
||||
await control.doc.update();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.run();
|
||||
}
|
||||
|
||||
async run() {
|
||||
this.displayFilters();
|
||||
this.data = await frappe.db.getAll({
|
||||
doctype: this.doctype,
|
||||
fields: ['*'],
|
||||
filters: this.filterSelector.getFilters(),
|
||||
start: this.start,
|
||||
limit: 500
|
||||
});
|
||||
this.modelTable.refresh(this.data);
|
||||
}
|
||||
|
||||
displayFilters() {
|
||||
this.fitlerButton.textContent = this.filterSelector.getText();
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
const frappe = require('frappejs');
|
||||
const Page = require('frappejs/client/view/page');
|
||||
const view = require('frappejs/client/view');
|
||||
|
||||
module.exports = class TreePage extends Page {
|
||||
constructor(name) {
|
||||
const hasRoute = true;
|
||||
|
||||
super({
|
||||
title: frappe._("Tree"),
|
||||
parent: hasRoute ? frappe.desk.body : frappe.desk.center,
|
||||
hasRoute: hasRoute
|
||||
});
|
||||
|
||||
this.fullPage = true;
|
||||
|
||||
this.name = name;
|
||||
|
||||
this.tree = new (view.getTreeClass(name))({
|
||||
doctype: name,
|
||||
parent: this.body,
|
||||
page: this
|
||||
});
|
||||
}
|
||||
|
||||
async show(params) {
|
||||
super.show();
|
||||
this.setTitle(this.name===this.tree.meta.name ? (this.tree.meta.label || this.tree.meta.name) : this.name);
|
||||
await this.tree.refresh();
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
const common = require('frappejs/common');
|
||||
const sqlite = require('frappejs/backends/sqlite');
|
||||
const frappe = require('frappejs');
|
||||
frappe.ui = require('./ui');
|
||||
const Desk = require('./desk');
|
||||
const Observable = require('frappejs/utils/observable');
|
||||
|
||||
module.exports = {
|
||||
async start({dbPath, columns = 3, models}) {
|
||||
window.frappe = frappe;
|
||||
frappe.isServer = true;
|
||||
frappe.init();
|
||||
frappe.registerLibs(common);
|
||||
frappe.registerModels(require('frappejs/models'));
|
||||
|
||||
if (models) {
|
||||
frappe.registerModels(models);
|
||||
}
|
||||
|
||||
frappe.db = await new sqlite({ dbPath });
|
||||
await frappe.db.connect();
|
||||
await frappe.db.migrate();
|
||||
|
||||
frappe.fetch = window.fetch.bind();
|
||||
|
||||
frappe.docs = new Observable();
|
||||
|
||||
await frappe.getSingle('SystemSettings');
|
||||
|
||||
frappe.desk = new Desk(columns);
|
||||
await frappe.login('Administrator');
|
||||
}
|
||||
};
|
||||
|
@ -1,52 +0,0 @@
|
||||
const common = require('frappejs/common');
|
||||
const HTTPClient = require('frappejs/backends/http');
|
||||
const frappe = require('frappejs');
|
||||
frappe.ui = require('./ui');
|
||||
const Desk = require('./desk');
|
||||
const Observable = require('frappejs/utils/observable');
|
||||
const { getPDF } = require('frappejs/client/pdf');
|
||||
|
||||
module.exports = {
|
||||
async start({server, columns = 2, makeDesk = false}) {
|
||||
window.frappe = frappe;
|
||||
frappe.init();
|
||||
frappe.registerLibs(common);
|
||||
frappe.registerModels(require('frappejs/models'), 'client');
|
||||
frappe.fetch = window.fetch.bind();
|
||||
|
||||
frappe.db = await new HTTPClient({server: server});
|
||||
this.socket = io.connect(`http://${server}`); // eslint-disable-line
|
||||
frappe.db.bindSocketClient(this.socket);
|
||||
|
||||
frappe.docs = new Observable();
|
||||
await frappe.getSingle('SystemSettings');
|
||||
|
||||
if(makeDesk) {
|
||||
this.makeDesk(columns);
|
||||
}
|
||||
},
|
||||
|
||||
async makeDesk(columns) {
|
||||
frappe.desk = new Desk(columns);
|
||||
await frappe.login();
|
||||
},
|
||||
|
||||
setCall() {
|
||||
frappe.call = async (method, args) => {
|
||||
let url = `/api/method/${method}`;
|
||||
let response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(args || {})
|
||||
});
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
frappe.getPDF = getPDF;
|
||||
}
|
||||
};
|
||||
|
@ -1,30 +0,0 @@
|
||||
async function getPDF(doctype, name) {
|
||||
const headers = {
|
||||
'Accept': 'application/pdf',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
const res = await fetch('/api/method/pdf', {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({ doctype, name })
|
||||
});
|
||||
|
||||
const blob = await res.blob();
|
||||
showFile(blob);
|
||||
}
|
||||
|
||||
|
||||
function showFile(blob, filename='file.pdf') {
|
||||
const newBlob = new Blob([blob], { type: "application/pdf" })
|
||||
const data = window.URL.createObjectURL(newBlob);
|
||||
const link = document.createElement('a');
|
||||
link.href = data;
|
||||
link.download = filename;
|
||||
link.click();
|
||||
setTimeout(() => window.URL.revokeObjectURL(data), 100);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getPDF
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
@import "./variables.scss";
|
||||
@import "node_modules/frappe-datatable/dist/frappe-datatable";
|
||||
|
||||
.dt-header {
|
||||
background-color: $gray-200 !important;
|
||||
}
|
||||
|
||||
.dt-cell__edit {
|
||||
padding: 0px;
|
||||
|
||||
input, textarea {
|
||||
outline: none;
|
||||
border-radius: none;
|
||||
border: none;
|
||||
margin: none;
|
||||
padding: $spacer-2;
|
||||
|
||||
&:focus {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.awesomplete > ul {
|
||||
position: fixed;
|
||||
left: auto;
|
||||
width: auto;
|
||||
min-width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
.dt-cell--highlight {
|
||||
background-color: $gray-200;
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
.indicator,
|
||||
.indicator-right {
|
||||
background: none;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.indicator::before,
|
||||
.indicator-right::after {
|
||||
content:'';
|
||||
display: inline-block;
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
border-radius: 8px;
|
||||
background: $gray-300;
|
||||
}
|
||||
|
||||
.indicator::before {
|
||||
margin:0 $spacer-2 0 0;
|
||||
}
|
||||
|
||||
.indicator-right::after {
|
||||
margin:0 0 0 $spacer-2;
|
||||
}
|
||||
|
||||
.indicator.grey::before,
|
||||
.indicator-right.grey::after {
|
||||
background: $gray-300;
|
||||
}
|
||||
.indicator.blue::before,
|
||||
.indicator-right.blue::after {
|
||||
background: $blue;
|
||||
}
|
||||
.indicator.red::before,
|
||||
.indicator-right.red::after {
|
||||
background: $red;
|
||||
}
|
||||
.indicator.green::before,
|
||||
.indicator-right.green::after {
|
||||
background: $green;
|
||||
}
|
||||
.indicator.orange::before,
|
||||
.indicator-right.orange::after {
|
||||
background: $orange;
|
||||
}
|
||||
.indicator.purple::before,
|
||||
.indicator-right.purple::after {
|
||||
background: $purple;
|
||||
}
|
||||
.indicator.darkgrey::before,
|
||||
.indicator-right.darkgrey::after {
|
||||
background: $gray-600;
|
||||
}
|
||||
|
||||
.indicator.black::before,
|
||||
.indicator-right.black::after {
|
||||
background: $gray-800;
|
||||
}
|
||||
|
||||
.indicator.yellow::before,
|
||||
.indicator-right.yellow::after {
|
||||
background: $yellow;
|
||||
}
|
||||
|
||||
.modal-header .indicator {
|
||||
float: left;
|
||||
margin-top: 7.5px;
|
||||
margin-right: 3px;
|
||||
}
|
@ -1,278 +0,0 @@
|
||||
@import "node_modules/bootstrap/scss/bootstrap";
|
||||
@import "node_modules/awesomplete/awesomplete";
|
||||
@import "node_modules/flatpickr/dist/flatpickr";
|
||||
@import "node_modules/flatpickr/dist/themes/airbnb";
|
||||
@import "node_modules/codemirror/lib/codemirror";
|
||||
|
||||
@import "./variables.scss";
|
||||
@import "./indicators.scss";
|
||||
|
||||
html {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.desk-body {
|
||||
border-left: 1px solid $gray-300;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.desk-center {
|
||||
border-left: 1px solid $gray-300;
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.page {
|
||||
padding-bottom: $spacer-4;
|
||||
|
||||
.page-nav {
|
||||
padding: $spacer-2 $spacer-3;
|
||||
background-color: $gray-100;
|
||||
border-bottom: 1px solid $gray-300;
|
||||
|
||||
.btn {
|
||||
margin-left: $spacer-2;
|
||||
}
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-weight: bold;
|
||||
padding: $spacer-4;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.page-links {
|
||||
padding: $spacer-3 $spacer-4;
|
||||
}
|
||||
|
||||
.page-error {
|
||||
text-align: center;
|
||||
padding: 200px 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-body {
|
||||
padding: $spacer-3 $spacer-4;
|
||||
|
||||
.form-check {
|
||||
margin-bottom: $spacer-2;
|
||||
|
||||
.form-check-input {
|
||||
margin-top: $spacer-1;
|
||||
}
|
||||
.form-check-label {
|
||||
margin-left: $spacer-1;
|
||||
}
|
||||
}
|
||||
|
||||
.form-control.font-weight-bold {
|
||||
background-color: lightyellow;
|
||||
}
|
||||
|
||||
.alert {
|
||||
margin-top: $spacer-3;
|
||||
}
|
||||
}
|
||||
|
||||
.form-inline {
|
||||
.form-group {
|
||||
margin-right: $spacer-3;
|
||||
margin-bottom: $spacer-3;
|
||||
}
|
||||
}
|
||||
|
||||
.list-search {
|
||||
padding: $spacer-3 $spacer-4;
|
||||
}
|
||||
|
||||
.list-body {
|
||||
.list-row {
|
||||
padding: $spacer-2 $spacer-4;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
cursor: pointer;
|
||||
|
||||
.checkbox {
|
||||
margin-right: $spacer-2;
|
||||
}
|
||||
|
||||
a, a:hover, a:visited, a:active {
|
||||
color: $gray-800;
|
||||
}
|
||||
}
|
||||
|
||||
.list-row:hover {
|
||||
background-color: $gray-100;
|
||||
}
|
||||
|
||||
.list-row.active {
|
||||
background-color: $gray-200;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.dropdown-item {
|
||||
padding: $spacer-2 $spacer-3;
|
||||
}
|
||||
|
||||
.bottom-right-float {
|
||||
position: fixed;
|
||||
margin-bottom: 0px;
|
||||
bottom: $spacer-3;
|
||||
right: $spacer-3;
|
||||
max-width: 200px;
|
||||
padding: $spacer-2 $spacer-3;
|
||||
}
|
||||
|
||||
.desk-menu {
|
||||
background-color: $gray-200;
|
||||
|
||||
.list-row {
|
||||
border-bottom: 1px solid $gray-200;
|
||||
}
|
||||
|
||||
.list-row:hover {
|
||||
background-color: $gray-300;
|
||||
}
|
||||
|
||||
.list-row.active {
|
||||
background-color: $gray-400;
|
||||
}
|
||||
}
|
||||
|
||||
.print-page {
|
||||
padding: $spacer-5;
|
||||
line-height: 1.8;
|
||||
|
||||
td, th {
|
||||
padding: $spacer-2;
|
||||
}
|
||||
}
|
||||
|
||||
.table-page-wrapper {
|
||||
width: 100%;
|
||||
padding: $spacer-3 $spacer-4;
|
||||
}
|
||||
|
||||
.filter-toolbar {
|
||||
padding: $spacer-3 $spacer-4;
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
margin-top: $spacer-4;
|
||||
margin-bottom: $spacer-4;
|
||||
}
|
||||
|
||||
.table-toolbar {
|
||||
margin-top: $spacer-2;
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
font-family: "SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace;
|
||||
border: 1px solid $gray-300;
|
||||
border-radius: 0.25rem;
|
||||
padding: $spacer-2;
|
||||
}
|
||||
|
||||
.awesomplete {
|
||||
display: block;
|
||||
|
||||
ul {
|
||||
max-height: 150px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
> ul > li {
|
||||
padding: .75rem .375rem;
|
||||
}
|
||||
|
||||
> ul > li:hover {
|
||||
background: $gray-300;
|
||||
color: $body-color;
|
||||
}
|
||||
|
||||
> ul > li[aria-selected="true"] {
|
||||
background: $gray-300;
|
||||
color: $body-color;
|
||||
}
|
||||
|
||||
> ul > li[aria-selected="true"]:hover {
|
||||
background: $gray-300;
|
||||
color: $body-color;
|
||||
}
|
||||
|
||||
li[aria-selected="true"] mark, li[aria-selected="false"] mark {
|
||||
background: inherit;
|
||||
color: inherit;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
mark {
|
||||
padding: none;
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
.align-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.align-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
margin: $spacer-1;
|
||||
}
|
||||
|
||||
.vertical-margin {
|
||||
margin: $spacer-3 0px;
|
||||
}
|
||||
|
||||
@import "./tree.scss";
|
||||
@import "./datatable.scss";
|
||||
|
||||
// just for accounting
|
||||
|
||||
.setup-container {
|
||||
margin: 40px auto;
|
||||
padding: 20px 0px;
|
||||
width: 450px;
|
||||
|
||||
border: 1px solid $gray-300;
|
||||
border-radius: 4px;
|
||||
|
||||
h3 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.form-section.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.setup-link-area {
|
||||
margin: $spacer-1 $spacer-4;
|
||||
}
|
||||
}
|
||||
|
||||
// File Input
|
||||
|
||||
input[type=file] {
|
||||
width: 0.1px;
|
||||
height: 0.1px;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.was-validated input[type=file]:invalid + button {
|
||||
border-color: $red;
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
@import "./variables.scss";
|
||||
|
||||
.tree-body {
|
||||
padding: $spacer-3 $spacer-4;
|
||||
}
|
||||
|
||||
f-tree-node {
|
||||
--tree-node-hover: $gray-100;
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
$spacer-1: 0.25rem;
|
||||
$spacer-2: 0.5rem;
|
||||
$spacer-3: 1rem;
|
||||
$spacer-4: 2rem;
|
||||
$spacer-5: 3rem;
|
||||
$page-width: 500px;
|
||||
|
||||
$shadow-width: 0.5rem;
|
@ -1,54 +0,0 @@
|
||||
const frappe = require('frappejs');
|
||||
const bootstrap = require('bootstrap');
|
||||
const $ = require('jquery');
|
||||
|
||||
class Dropdown {
|
||||
constructor({parent, label, items = [], right, cssClass='btn-secondary'}) {
|
||||
Object.assign(this, arguments[0]);
|
||||
Dropdown.instances += 1;
|
||||
this.id = 'dropdownMenuButton-' + Dropdown.instances;
|
||||
|
||||
this.make();
|
||||
|
||||
// init items
|
||||
if (this.items) {
|
||||
for (let item of this.items) {
|
||||
this.addItem(item.label, item.action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
make() {
|
||||
this.$dropdown = $(`<div class="dropdown ${this.right ? 'float-right' : ''}">
|
||||
<button class="btn ${this.cssClass} dropdown-toggle"
|
||||
type="button" id="${this.id}" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false">${this.label}
|
||||
</button>
|
||||
<div class="dropdown-menu ${this.right ? 'dropdown-menu-right' : ''}" aria-labelledby="${this.id}"></div>
|
||||
</div>`).appendTo(this.parent)
|
||||
this.dropdown = this.$dropdown.get(0);
|
||||
this.dropdownMenu = this.dropdown.querySelector('.dropdown-menu');
|
||||
}
|
||||
|
||||
addItem(label, action) {
|
||||
let item = frappe.ui.add('button', 'dropdown-item', this.dropdownMenu, label);
|
||||
item.setAttribute('type', 'button');
|
||||
if (typeof action === 'string') {
|
||||
item.addEventListener('click', async () => {
|
||||
await frappe.router.setRoute(action);
|
||||
});
|
||||
} else {
|
||||
item.addEventListener('click', async () => {
|
||||
await action();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
floatRight() {
|
||||
frappe.ui.addClass(this.dropdown, 'float-right');
|
||||
}
|
||||
}
|
||||
|
||||
Dropdown.instances = 0;
|
||||
|
||||
module.exports = Dropdown;
|
@ -1,145 +0,0 @@
|
||||
const frappe = require('frappejs');
|
||||
const Dropdown = require('./dropdown');
|
||||
|
||||
module.exports = {
|
||||
create(tag, obj) {
|
||||
if(tag.includes('<')) {
|
||||
let div = document.createElement('div');
|
||||
div.innerHTML = tag.trim();
|
||||
return div.firstChild;
|
||||
}
|
||||
let element = document.createElement(tag);
|
||||
obj = obj || {};
|
||||
|
||||
let $ = (expr, con) => {
|
||||
return typeof expr === "string"
|
||||
? (con || document).querySelector(expr)
|
||||
: expr || null;
|
||||
}
|
||||
|
||||
for (var i in obj) {
|
||||
let val = obj[i];
|
||||
|
||||
if (i === "inside") {
|
||||
$(val).appendChild(element);
|
||||
}
|
||||
else if (i === "around") {
|
||||
let ref = $(val);
|
||||
ref.parentNode.insertBefore(element, ref);
|
||||
element.appendChild(ref);
|
||||
|
||||
} else if (i === "styles") {
|
||||
if(typeof val === "object") {
|
||||
Object.keys(val).map(prop => {
|
||||
element.style[prop] = val[prop];
|
||||
});
|
||||
}
|
||||
} else if (i in element ) {
|
||||
element[i] = val;
|
||||
}
|
||||
else {
|
||||
element.setAttribute(i, val);
|
||||
}
|
||||
}
|
||||
|
||||
return element;
|
||||
},
|
||||
|
||||
add(tag, className, parent, textContent) {
|
||||
let element = document.createElement(tag);
|
||||
if (className) {
|
||||
for (let c of className.split(' ')) {
|
||||
this.addClass(element, c);
|
||||
}
|
||||
}
|
||||
if (parent) {
|
||||
parent.appendChild(element);
|
||||
}
|
||||
if (textContent) {
|
||||
element.textContent = textContent;
|
||||
}
|
||||
return element;
|
||||
},
|
||||
|
||||
remove(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) {
|
||||
while (element.firstChild) {
|
||||
element.removeChild(element.firstChild);
|
||||
}
|
||||
},
|
||||
|
||||
addClass(element, className) {
|
||||
if (element.classList) {
|
||||
element.classList.add(className);
|
||||
} else {
|
||||
element.className += " " + className;
|
||||
}
|
||||
},
|
||||
|
||||
removeClass(element, className) {
|
||||
if (element.classList) {
|
||||
element.classList.remove(className);
|
||||
} else {
|
||||
element.className = element.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
|
||||
}
|
||||
},
|
||||
|
||||
toggleClass(element, className, flag) {
|
||||
if (flag === undefined) {
|
||||
flag = !element.classList.contains(className);
|
||||
}
|
||||
|
||||
if (!flag) {
|
||||
this.removeClass(element, className);
|
||||
} else {
|
||||
this.addClass(element, className);
|
||||
}
|
||||
},
|
||||
|
||||
toggle(element, default_display = '') {
|
||||
element.style.display = element.style.display === 'none' ? default_display : 'none';
|
||||
},
|
||||
|
||||
make_dropdown(label, parent, btn_class = 'btn-secondary') {
|
||||
return new Dropdown({parent: parent, label:label, btn_class:btn_class});
|
||||
},
|
||||
|
||||
showAlert({message, color='yellow', timeout=4}) {
|
||||
let alert = this.add('div', 'alert alert-warning bottom-right-float', document.body);
|
||||
alert.innerHTML = `<span class='indicator ${color}'>${message}</span>`;
|
||||
frappe.sleep(timeout).then(() => alert.remove());
|
||||
return alert;
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
module.exports = {
|
||||
bindKey(element, key, listener) {
|
||||
element.addEventListener('keydown', (e) => {
|
||||
if (key === this.getKey(e)) {
|
||||
listener(e);
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
getKey(e) {
|
||||
var keycode = e.keyCode || e.which;
|
||||
var key = this.keyMap[keycode] || String.fromCharCode(keycode);
|
||||
|
||||
if(e.ctrlKey || e.metaKey) {
|
||||
// add ctrl+ the key
|
||||
key = 'ctrl+' + key;
|
||||
}
|
||||
if(e.shiftKey) {
|
||||
// add ctrl+ the key
|
||||
key = 'shift+' + key;
|
||||
}
|
||||
return key.toLowerCase();
|
||||
},
|
||||
|
||||
keyMap: {
|
||||
8: 'backspace',
|
||||
9: 'tab',
|
||||
13: 'enter',
|
||||
16: 'shift',
|
||||
17: 'ctrl',
|
||||
91: 'meta',
|
||||
18: 'alt',
|
||||
27: 'escape',
|
||||
37: 'left',
|
||||
39: 'right',
|
||||
38: 'up',
|
||||
40: 'down',
|
||||
32: 'space'
|
||||
},
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
const $ = require('jquery');
|
||||
const bootstrap = require('bootstrap'); // eslint-disable-line
|
||||
const Observable = require('frappejs/utils/observable');
|
||||
|
||||
module.exports = class Modal extends Observable {
|
||||
constructor({ title, body, primary, secondary }) {
|
||||
super();
|
||||
Object.assign(this, arguments[0]);
|
||||
this.make();
|
||||
this.show();
|
||||
}
|
||||
|
||||
make() {
|
||||
this.$modal = $(`<div class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">${this.title}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
${this.getBodyHTML()}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`).appendTo(document.body);
|
||||
this.modal = this.$modal.get(0);
|
||||
|
||||
if (this.primary) {
|
||||
this.addPrimary(this.primary.label, this.primary.action);
|
||||
}
|
||||
if (this.secondary) {
|
||||
this.addSecondary(this.secondary.label, this.secondary.action);
|
||||
}
|
||||
|
||||
this.$modal.on('hidden.bs.modal', () => this.trigger('hide'));
|
||||
this.$modal.on('shown.bs.modal', () => {
|
||||
this.trigger('show');
|
||||
});
|
||||
}
|
||||
|
||||
getBodyHTML() {
|
||||
return this.body || '';
|
||||
}
|
||||
|
||||
addPrimary(label, action) {
|
||||
return $(`<button type="button" class="btn btn-primary">
|
||||
${label}</button>`)
|
||||
.appendTo(this.$modal.find('.modal-footer'))
|
||||
.on('click', () => action(this));
|
||||
}
|
||||
|
||||
addSecondary(label, action) {
|
||||
return $(`<button type="button" class="btn btn-secondary">
|
||||
${label}</button>`)
|
||||
.appendTo(this.$modal.find('.modal-footer'))
|
||||
.on('click', () => action(this));
|
||||
}
|
||||
|
||||
setTitle(title) {
|
||||
this.$modal.find('.modal-title').text(title);
|
||||
}
|
||||
|
||||
show() {
|
||||
this.$modal.modal('show');
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.$modal.modal('hide');
|
||||
}
|
||||
|
||||
getBody() {
|
||||
return this.$modal.find('.modal-body').get(0);
|
||||
}
|
||||
}
|
@ -1,134 +0,0 @@
|
||||
const frappe = require('frappejs');
|
||||
const DataTable = require('frappe-datatable');
|
||||
|
||||
const Modal = require('frappejs/client/ui/modal');
|
||||
const utils = require('./utils');
|
||||
|
||||
module.exports = class ModelTable {
|
||||
constructor({doctype, parent, layout, parentControl, getRowData,
|
||||
isDisabled, getTableData}) {
|
||||
Object.assign(this, arguments[0]);
|
||||
this.meta = frappe.getMeta(this.doctype);
|
||||
this.make();
|
||||
}
|
||||
|
||||
make() {
|
||||
this.datatable = new DataTable(this.parent, {
|
||||
columns: this.getColumns(),
|
||||
data: [],
|
||||
layout: this.meta.layout || this.layout || 'fluid',
|
||||
addCheckboxColumn: true,
|
||||
getEditor: this.getTableInput.bind(this),
|
||||
});
|
||||
}
|
||||
|
||||
resize() {
|
||||
this.datatable.setDimensions();
|
||||
}
|
||||
|
||||
getColumns() {
|
||||
return utils.convertFieldsToDatatableColumns(this.getTableFields(), this.layout);
|
||||
}
|
||||
|
||||
getTableFields() {
|
||||
return this.meta.fields.filter(f => f.hidden ? false : true);
|
||||
}
|
||||
|
||||
getTableInput(colIndex, rowIndex, value, parent) {
|
||||
let field = this.datatable.getColumn(colIndex).field;
|
||||
if (field.disabled || (this.isDisabled && this.isDisabled())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (field.fieldtype==='Text') {
|
||||
// text in modal
|
||||
parent = this.getControlModal(field).getBody();
|
||||
}
|
||||
const editor = this.getControl(field, parent);
|
||||
return editor;
|
||||
}
|
||||
|
||||
getControl(field, parent) {
|
||||
field.onlyInput = true;
|
||||
const controls = require('frappejs/client/view/controls');
|
||||
const control = controls.makeControl({field: field, parent: parent});
|
||||
|
||||
// change will be triggered by datatable
|
||||
control.skipChangeEvent = true;
|
||||
|
||||
return {
|
||||
initValue: async (value, rowIndex, column) => {
|
||||
column.activeControl = control;
|
||||
control.parentControl = this.parentControl;
|
||||
control.doc = await this.getRowData(rowIndex);
|
||||
control.setFocus();
|
||||
control.setInputValue(control.doc[column.id]);
|
||||
return control;
|
||||
},
|
||||
setValue: async (value, rowIndex, column) => {
|
||||
await this.setValue(control);
|
||||
},
|
||||
getValue: () => {
|
||||
return control.getInputValue();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async setValue(control) {
|
||||
await control.handleChange();
|
||||
}
|
||||
|
||||
getControlModal(field) {
|
||||
this.modal = new Modal({
|
||||
title: frappe._('Edit {0}', field.label),
|
||||
body: '',
|
||||
primary: {
|
||||
label: frappe._('Submit'),
|
||||
action: (modal) => {
|
||||
this.datatable.cellmanager.submitEditing();
|
||||
modal.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
this.modal.on('hide', () => {
|
||||
this.datatable.cellmanager.deactivateEditing();
|
||||
this.datatable.cellmanager.$focusedCell.focus();
|
||||
});
|
||||
|
||||
return this.modal;
|
||||
}
|
||||
|
||||
checkValidity() {
|
||||
if (!this.datatable) {
|
||||
return true;
|
||||
}
|
||||
let data = this.getTableData();
|
||||
for (let rowIndex=0; rowIndex < data.length; rowIndex++) {
|
||||
let row = data[rowIndex];
|
||||
for (let column of this.datatable.datamanager.columns) {
|
||||
if (column.field && column.field.required) {
|
||||
let value = row[column.field.fieldname];
|
||||
if (value==='' || value===undefined || value===null) {
|
||||
let $cell = this.datatable.cellmanager.getCell$(column.colIndex, rowIndex);
|
||||
this.datatable.cellmanager.activateEditing($cell);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
refresh(data) {
|
||||
return this.datatable.refresh(data);
|
||||
}
|
||||
|
||||
getChecked() {
|
||||
return this.datatable.rowmanager.getCheckedRows();
|
||||
}
|
||||
|
||||
checkAll(check) {
|
||||
return this.datatable.rowmanager.checkAll(check);
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
const BaseControl = require('./base');
|
||||
const Awesomplete = require('awesomplete');
|
||||
|
||||
class AutocompleteControl extends BaseControl {
|
||||
make() {
|
||||
super.make();
|
||||
this.input.setAttribute('type', 'text');
|
||||
this.setupAwesomplete();
|
||||
}
|
||||
|
||||
async setupAwesomplete() {
|
||||
this.awesomplete = new Awesomplete(this.input, {
|
||||
minChars: 0,
|
||||
maxItems: 99
|
||||
});
|
||||
|
||||
this.list = await this.getList();
|
||||
|
||||
// rebuild the list on input
|
||||
this.input.addEventListener('input', (event) => {
|
||||
this.awesomplete.list = this.list;
|
||||
});
|
||||
}
|
||||
|
||||
validate(value) {
|
||||
if (this.list.includes(value)) {
|
||||
return value;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = AutocompleteControl;
|
@ -1,206 +0,0 @@
|
||||
const frappe = require('frappejs');
|
||||
|
||||
class BaseControl {
|
||||
constructor({field, parent, form}) {
|
||||
BaseControl.count++;
|
||||
|
||||
Object.assign(this, field);
|
||||
this.parent = parent;
|
||||
this.form = form;
|
||||
this.id = 'control-' + BaseControl.count;
|
||||
|
||||
if (!this.fieldname) {
|
||||
this.fieldname = frappe.slug(this.label);
|
||||
}
|
||||
if (!this.parent) {
|
||||
this.parent = this.form.form;
|
||||
}
|
||||
if (this.setup) {
|
||||
this.setup();
|
||||
}
|
||||
if (this.template) {
|
||||
this.wrapper = frappe.ui.add('div', 'field-template', this.parent);
|
||||
this.renderTemplate();
|
||||
} else {
|
||||
this.make();
|
||||
}
|
||||
}
|
||||
|
||||
bind(doc) {
|
||||
this.doc = doc;
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
refresh() {
|
||||
if (this.template) {
|
||||
this.renderTemplate();
|
||||
} else {
|
||||
this.setDocValue();
|
||||
}
|
||||
this.setDisabled();
|
||||
}
|
||||
|
||||
renderTemplate() {
|
||||
if (this.form && this.form.doc) {
|
||||
this.wrapper.innerHTML = this.template(this.form.doc, this.doc);
|
||||
} else {
|
||||
this.wrapper.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
setDocValue() {
|
||||
if (this.doc && !this.template) {
|
||||
this.setInputValue(this.doc.get(this.fieldname));
|
||||
}
|
||||
}
|
||||
|
||||
make() {
|
||||
if (!this.onlyInput) {
|
||||
this.makeInputContainer();
|
||||
this.makeLabel();
|
||||
}
|
||||
this.makeInput();
|
||||
this.addChangeHandler();
|
||||
}
|
||||
|
||||
makeInputContainer(className = 'form-group') {
|
||||
this.inputContainer = frappe.ui.add('div', className, this.parent);
|
||||
}
|
||||
|
||||
makeLabel(labelClass = null) {
|
||||
this.labelElement = frappe.ui.add('label', labelClass, this.inputContainer, this.label);
|
||||
this.labelElement.setAttribute('for', this.id);
|
||||
if (this.inline) {
|
||||
this.labelElement.classList.add("sr-only");
|
||||
}
|
||||
}
|
||||
|
||||
makeInput(inputClass='form-control') {
|
||||
this.input = frappe.ui.add('input', inputClass, this.getInputParent());
|
||||
this.input.autocomplete = "off";
|
||||
this.input.id = this.id;
|
||||
|
||||
this.setInputName();
|
||||
this.setRequiredAttribute();
|
||||
this.setDisabled();
|
||||
if (!this.onlyInput) {
|
||||
this.makeDescription();
|
||||
}
|
||||
if (this.placeholder || this.inline) {
|
||||
this.input.setAttribute('placeholder', this.placeholder || this.label);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
isDisabled() {
|
||||
let disabled = this.disabled;
|
||||
|
||||
if (this.doc && this.doc.submitted) {
|
||||
disabled = true;
|
||||
}
|
||||
|
||||
if (this.formula && this.fieldtype !== 'Table') {
|
||||
disabled = true;
|
||||
}
|
||||
|
||||
return disabled;
|
||||
}
|
||||
|
||||
setDisabled() {
|
||||
this.input.disabled = this.isDisabled();
|
||||
}
|
||||
|
||||
getInputParent() {
|
||||
return this.inputContainer || this.parent;
|
||||
}
|
||||
|
||||
setInputName() {
|
||||
this.input.setAttribute('name', this.fieldname);
|
||||
}
|
||||
|
||||
setRequiredAttribute() {
|
||||
if (this.required) {
|
||||
this.input.required = true;
|
||||
this.input.classList.add('font-weight-bold');
|
||||
}
|
||||
}
|
||||
|
||||
makeDescription() {
|
||||
if (this.description) {
|
||||
this.description_element = frappe.ui.add('small', 'form-text text-muted',
|
||||
this.inputContainer, this.description);
|
||||
}
|
||||
}
|
||||
|
||||
setInputValue(value) {
|
||||
this.input.value = this.format(value);
|
||||
}
|
||||
|
||||
format(value) {
|
||||
if (value === undefined || value === null) {
|
||||
value = '';
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
async getParsedValue() {
|
||||
return await this.parse(this.getInputValue());
|
||||
}
|
||||
|
||||
getInputValue() {
|
||||
return this.input.value;
|
||||
}
|
||||
|
||||
async parse(value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
async validate(value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
addChangeHandler() {
|
||||
this.input.addEventListener('change', () => {
|
||||
if (this.skipChangeEvent) return;
|
||||
this.handleChange();
|
||||
});
|
||||
}
|
||||
|
||||
async handleChange(event) {
|
||||
let value = await this.parse(this.getInputValue());
|
||||
value = await this.validate(value);
|
||||
this.input.setCustomValidity(value === false ? 'error' : '');
|
||||
await this.updateDocValue(value);
|
||||
}
|
||||
|
||||
async updateDocValue(value) {
|
||||
if (!this.doc) return;
|
||||
if (this.doc[this.fieldname] !== value) {
|
||||
if (this.parentControl) {
|
||||
// its a child
|
||||
this.doc[this.fieldname] = value;
|
||||
this.parentControl.doc._dirty = true;
|
||||
await this.parentControl.doc.applyChange(this.fieldname);
|
||||
} else {
|
||||
// parent
|
||||
await this.doc.set(this.fieldname, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
disable() {
|
||||
this.input.setAttribute('disabled', 'disabled');
|
||||
}
|
||||
|
||||
enable() {
|
||||
this.input.removeAttribute('disabled');
|
||||
}
|
||||
|
||||
setFocus() {
|
||||
this.input.focus();
|
||||
}
|
||||
}
|
||||
|
||||
BaseControl.count = 0;
|
||||
|
||||
module.exports = BaseControl;
|
@ -1,38 +0,0 @@
|
||||
const BaseControl = require('./base');
|
||||
|
||||
class CheckControl extends BaseControl {
|
||||
make() {
|
||||
if (!this.onlyInput) {
|
||||
this.makeInputContainer();
|
||||
}
|
||||
this.makeInput();
|
||||
if (!this.onlyInput) {
|
||||
this.makeLabel();
|
||||
}
|
||||
this.addChangeHandler();
|
||||
}
|
||||
|
||||
makeInputContainer() {
|
||||
super.makeInputContainer('form-check');
|
||||
}
|
||||
|
||||
makeLabel() {
|
||||
super.makeLabel('form-check-label');
|
||||
}
|
||||
|
||||
makeInput() {
|
||||
super.makeInput('form-check-input');
|
||||
this.input.type = 'checkbox';
|
||||
}
|
||||
|
||||
setInputValue(value) {
|
||||
if (value === '0') value = 0;
|
||||
this.input.checked = value ? true : false;
|
||||
}
|
||||
|
||||
getInputValue() {
|
||||
return this.input.checked ? 1 : 0
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = CheckControl;
|
@ -1,35 +0,0 @@
|
||||
const BaseControl = require('./base');
|
||||
// const frappe = require('frappejs');
|
||||
const CodeMirror = require('codemirror');
|
||||
const modeHTML = require('codemirror/mode/htmlmixed/htmlmixed'); // eslint-disable-line
|
||||
const modeJavascript = require('codemirror/mode/javascript/javascript'); // eslint-disable-line
|
||||
|
||||
class CodeControl extends BaseControl {
|
||||
makeInput() {
|
||||
if (!this.options) {
|
||||
this.options = {};
|
||||
}
|
||||
this.options.theme = 'default';
|
||||
this.input = new CodeMirror(this.getInputParent(), this.options);
|
||||
}
|
||||
|
||||
setInputValue(value) {
|
||||
if (value !== this.input.getValue()) {
|
||||
this.input.setValue(value || '');
|
||||
}
|
||||
}
|
||||
|
||||
getInputValue(value) {
|
||||
return this.input.getValue();
|
||||
}
|
||||
|
||||
addChangeHandler() {
|
||||
this.input.on('blur', () => {
|
||||
if (this.skipChangeEvent) return;
|
||||
this.handleChange();
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
module.exports = CodeControl;
|
@ -1,13 +0,0 @@
|
||||
const FloatControl = require('./float');
|
||||
const frappe = require('frappejs');
|
||||
|
||||
class CurrencyControl extends FloatControl {
|
||||
parse(value) {
|
||||
return frappe.parse_number(value);
|
||||
}
|
||||
format(value) {
|
||||
return frappe.format_number(value);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = CurrencyControl;
|
@ -1,14 +0,0 @@
|
||||
const BaseControl = require('./base');
|
||||
|
||||
class DataControl extends BaseControl {
|
||||
make() {
|
||||
super.make();
|
||||
|
||||
if (!this.inputType) {
|
||||
this.inputType = 'text';
|
||||
}
|
||||
this.input.setAttribute('type', this.inputType);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = DataControl;
|
@ -1,40 +0,0 @@
|
||||
const flatpickr = require('flatpickr');
|
||||
const BaseControl = require('./base');
|
||||
const frappe = require('frappejs');
|
||||
|
||||
class DateControl extends BaseControl {
|
||||
make() {
|
||||
let dateFormat = {
|
||||
'yyyy-mm-dd': 'Y-m-d',
|
||||
'dd/mm/yyyy': 'd/m/Y',
|
||||
'dd-mm-yyyy': 'd-m-Y',
|
||||
'mm/dd/yyyy': 'm/d/Y',
|
||||
'mm-dd-yyyy': 'm-d-Y'
|
||||
}
|
||||
let altFormat = frappe.SystemSettings ?
|
||||
dateFormat[frappe.SystemSettings.dateFormat] :
|
||||
dateFormat['yyyy-mm-dd'];
|
||||
|
||||
super.make();
|
||||
this.input.setAttribute('type', 'text');
|
||||
this.flatpickr = flatpickr(this.input, {
|
||||
altInput: true,
|
||||
altFormat: altFormat,
|
||||
dateFormat:'Y-m-d'
|
||||
});
|
||||
}
|
||||
|
||||
setDisabled() {
|
||||
this.input.disabled = this.isDisabled();
|
||||
if (this.flatpickr && this.flatpickr.altInput) {
|
||||
this.flatpickr.altInput.disabled = this.isDisabled();
|
||||
}
|
||||
}
|
||||
|
||||
setInputValue(value) {
|
||||
super.setInputValue(value);
|
||||
this.flatpickr.setDate(value);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = DateControl;
|
@ -1,9 +0,0 @@
|
||||
const LinkControl = require('./link');
|
||||
|
||||
class DynamicLinkControl extends LinkControl {
|
||||
getTarget() {
|
||||
return this.doc[this.references];
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = DynamicLinkControl;
|
@ -1,53 +0,0 @@
|
||||
const frappe = require('frappejs');
|
||||
const BaseControl = require('./base');
|
||||
|
||||
class FileControl extends BaseControl {
|
||||
make() {
|
||||
super.make();
|
||||
this.fileButton = frappe.ui.create('button', {
|
||||
className: 'btn btn-outline-secondary btn-block',
|
||||
inside: this.getInputParent(),
|
||||
type: 'button',
|
||||
textContent: 'Choose a file...',
|
||||
onclick: () => {
|
||||
this.input.click();
|
||||
}
|
||||
});
|
||||
|
||||
this.input.setAttribute('type', 'file');
|
||||
|
||||
if (this.directory) {
|
||||
this.input.setAttribute('webkitdirectory', '');
|
||||
}
|
||||
|
||||
if (this.allowMultiple) {
|
||||
this.input.setAttribute('multiple', '');
|
||||
}
|
||||
}
|
||||
|
||||
async handleChange() {
|
||||
await super.handleChange();
|
||||
this.setDocValue();
|
||||
}
|
||||
|
||||
getInputValue() {
|
||||
return this.input.files;
|
||||
}
|
||||
|
||||
setInputValue(files) {
|
||||
|
||||
let label;
|
||||
if (!files || files.length === 0) {
|
||||
label = 'Choose a file...'
|
||||
} else if (files.length === 1) {
|
||||
label = files[0].name;
|
||||
} else {
|
||||
label = `${files.length} files selected`;
|
||||
}
|
||||
|
||||
this.fileButton.textContent = label;
|
||||
this.input.files = files;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = FileControl;
|
@ -1,20 +0,0 @@
|
||||
const BaseControl = require('./base');
|
||||
|
||||
class FloatControl extends BaseControl {
|
||||
make() {
|
||||
super.make();
|
||||
this.input.setAttribute('type', 'text');
|
||||
this.input.classList.add('text-right');
|
||||
this.input.addEventListener('focus', () => {
|
||||
setTimeout(() => {
|
||||
this.input.select();
|
||||
}, 100);
|
||||
})
|
||||
}
|
||||
parse(value) {
|
||||
value = parseFloat(value);
|
||||
return isNaN(value) ? 0 : value;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = FloatControl;
|
@ -1,28 +0,0 @@
|
||||
const controlClasses = {
|
||||
Autocomplete: require('./autocomplete'),
|
||||
Check: require('./check'),
|
||||
Code: require('./code'),
|
||||
Data: require('./data'),
|
||||
Date: require('./date'),
|
||||
DynamicLink: require('./dynamicLink'),
|
||||
Currency: require('./currency'),
|
||||
Float: require('./float'),
|
||||
File: require('./file'),
|
||||
Int: require('./int'),
|
||||
Link: require('./link'),
|
||||
Password: require('./password'),
|
||||
Select: require('./select'),
|
||||
Table: require('./table'),
|
||||
Text: require('./text')
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getControlClass(fieldtype) {
|
||||
return controlClasses[fieldtype];
|
||||
},
|
||||
makeControl({field, form, parent}) {
|
||||
const controlClass = this.getControlClass(field.fieldtype);
|
||||
let control = new controlClass({field:field, form:form, parent:parent});
|
||||
return control;
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
const FloatControl = require('./float');
|
||||
|
||||
class IntControl extends FloatControl {
|
||||
parse(value) {
|
||||
value = parseInt(value);
|
||||
return isNaN(value) ? 0 : value;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = IntControl;
|
@ -1,73 +0,0 @@
|
||||
const frappe = require('frappejs');
|
||||
const BaseControl = require('./base');
|
||||
const Awesomplete = require('awesomplete');
|
||||
|
||||
class LinkControl extends BaseControl {
|
||||
make() {
|
||||
super.make();
|
||||
this.input.setAttribute('type', 'text');
|
||||
this.setupAwesomplete();
|
||||
}
|
||||
|
||||
setupAwesomplete() {
|
||||
this.awesomplete = new Awesomplete(this.input, {
|
||||
minChars: 0,
|
||||
maxItems: 99,
|
||||
filter: () => true,
|
||||
sort: (a, b) => {
|
||||
if (a.value === '__newitem' || b.value === '__newitem') {
|
||||
return -1;
|
||||
}
|
||||
return a.value > b.value;
|
||||
}
|
||||
});
|
||||
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
async getList(query) {
|
||||
return (await frappe.db.getAll({
|
||||
doctype: this.getTarget(),
|
||||
filters: this.getFilters(query, this),
|
||||
limit: 50
|
||||
})).map(d => d.name);
|
||||
}
|
||||
|
||||
getFilters(query) {
|
||||
return { keywords: ["like", query] }
|
||||
}
|
||||
|
||||
getTarget() {
|
||||
return this.target;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = LinkControl;
|
@ -1,10 +0,0 @@
|
||||
const BaseControl = require('./base');
|
||||
|
||||
class PasswordControl extends BaseControl {
|
||||
make() {
|
||||
super.make();
|
||||
this.input.setAttribute('type', 'password');
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = PasswordControl;
|
@ -1,52 +0,0 @@
|
||||
const BaseControl = require('./base');
|
||||
const frappe = require('frappejs');
|
||||
|
||||
class SelectControl extends BaseControl {
|
||||
makeInput() {
|
||||
this.input = frappe.ui.add('select', 'form-control', this.getInputParent());
|
||||
this.addOptions();
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.addOptions();
|
||||
super.refresh();
|
||||
}
|
||||
|
||||
addOptions() {
|
||||
const options = this.getOptions();
|
||||
if (this.areOptionsSame(options)) return;
|
||||
|
||||
frappe.ui.empty(this.input);
|
||||
for (let value of options) {
|
||||
let option = frappe.ui.add('option', null, this.input, value.label || value);
|
||||
option.setAttribute('value', value.value || value);
|
||||
}
|
||||
this.lastOptions = options;
|
||||
}
|
||||
|
||||
getOptions() {
|
||||
let options = this.options;
|
||||
if (typeof options==='string') {
|
||||
options = options.split('\n');
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
areOptionsSame(options) {
|
||||
let same = false;
|
||||
if (this.lastOptions && options.length===this.lastOptions.length) {
|
||||
same = options.every((v ,i) => {
|
||||
const v1 = this.lastOptions[i];
|
||||
return (v.value || v) === (v1.value || v1)
|
||||
});
|
||||
}
|
||||
return same;
|
||||
}
|
||||
|
||||
make() {
|
||||
super.make();
|
||||
this.input.setAttribute('row', '3');
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = SelectControl;
|
@ -1,100 +0,0 @@
|
||||
const frappe = require('frappejs');
|
||||
const BaseControl = require('./base');
|
||||
const ModelTable = require('frappejs/client/ui/modelTable');
|
||||
|
||||
class TableControl extends BaseControl {
|
||||
make() {
|
||||
this.makeWrapper();
|
||||
this.modelTable = new ModelTable({
|
||||
doctype: this.childtype,
|
||||
parent: this.wrapper.querySelector('.datatable-wrapper'),
|
||||
parentControl: this,
|
||||
layout: this.layout || 'ratio',
|
||||
getTableData: () => this.getTableData(),
|
||||
getRowData: (rowIndex) => this.doc[this.fieldname][rowIndex],
|
||||
isDisabled: () => this.isDisabled(),
|
||||
});
|
||||
this.setupToolbar();
|
||||
}
|
||||
|
||||
makeWrapper() {
|
||||
this.wrapper = frappe.ui.add('div', 'table-wrapper', this.getInputParent());
|
||||
this.wrapper.innerHTML =
|
||||
`<div class="datatable-wrapper" style="width: 100%"></div>
|
||||
<div class="table-toolbar">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary btn-add">
|
||||
${frappe._("Add")}</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary btn-remove">
|
||||
${frappe._("Remove")}</button>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
setupToolbar() {
|
||||
this.wrapper.querySelector('.btn-add').addEventListener('click', async (event) => {
|
||||
this.doc[this.fieldname].push({});
|
||||
await this.doc.commit();
|
||||
this.refresh();
|
||||
});
|
||||
|
||||
this.wrapper.querySelector('.btn-remove').addEventListener('click', async (event) => {
|
||||
let checked = this.modelTable.getChecked();
|
||||
this.doc[this.fieldname] = this.doc[this.fieldname].filter(d => !checked.includes(d.idx + ''));
|
||||
await this.doc.commit();
|
||||
this.refresh();
|
||||
this.modelTable.checkAll(false);
|
||||
});
|
||||
}
|
||||
|
||||
getInputValue() {
|
||||
return this.doc[this.fieldname];
|
||||
}
|
||||
|
||||
setInputValue(value) {
|
||||
this.modelTable.refresh(this.getTableData(value));
|
||||
}
|
||||
|
||||
setDisabled() {
|
||||
this.refreshToolbar();
|
||||
}
|
||||
|
||||
getToolbar() {
|
||||
return this.wrapper.querySelector('.table-toolbar');
|
||||
}
|
||||
|
||||
refreshToolbar() {
|
||||
const toolbar = this.wrapper.querySelector('.table-toolbar');
|
||||
if (toolbar) {
|
||||
toolbar.classList.toggle('hide', this.isDisabled() ? true : false);
|
||||
}
|
||||
}
|
||||
|
||||
getTableData(value) {
|
||||
return (value && value.length) ? value : this.getDefaultData();
|
||||
}
|
||||
|
||||
getDefaultData() {
|
||||
// build flat table
|
||||
if (!this.doc) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!this.doc[this.fieldname]) {
|
||||
this.doc[this.fieldname] = [{idx: 0}];
|
||||
}
|
||||
|
||||
if (this.doc[this.fieldname].length === 0 && this.neverEmpty) {
|
||||
this.doc[this.fieldname] = [{idx: 0}];
|
||||
}
|
||||
|
||||
return this.doc[this.fieldname];
|
||||
}
|
||||
|
||||
checkValidity() {
|
||||
if (!this.modelTable) {
|
||||
return true;
|
||||
}
|
||||
return this.modelTable.checkValidity();
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = TableControl;
|
@ -1,14 +0,0 @@
|
||||
const BaseControl = require('./base');
|
||||
const frappe = require('frappejs');
|
||||
|
||||
class TextControl extends BaseControl {
|
||||
makeInput() {
|
||||
this.input = frappe.ui.add('textarea', 'form-control', this.getInputParent());
|
||||
}
|
||||
make() {
|
||||
super.make();
|
||||
this.input.setAttribute('rows', '8');
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = TextControl;
|
@ -1,336 +0,0 @@
|
||||
const frappe = require('frappejs');
|
||||
const controls = require('./controls');
|
||||
const FormLayout = require('./formLayout');
|
||||
const Observable = require('frappejs/utils/observable');
|
||||
const keyboard = require('frappejs/client/ui/keyboard');
|
||||
const utils = require('frappejs/client/ui/utils');
|
||||
|
||||
module.exports = class BaseForm extends Observable {
|
||||
constructor({doctype, parent, submit_label='Submit', container, meta, inline=false}) {
|
||||
super();
|
||||
Object.assign(this, arguments[0]);
|
||||
this.links = [];
|
||||
|
||||
if (!this.meta) {
|
||||
this.meta = frappe.getMeta(this.doctype);
|
||||
}
|
||||
|
||||
if (this.setup) {
|
||||
this.setup();
|
||||
}
|
||||
|
||||
this.make();
|
||||
this.bindFormEvents();
|
||||
|
||||
if (this.doc) {
|
||||
// bootstrapped with a doc
|
||||
this.bindEvents(this.doc);
|
||||
}
|
||||
}
|
||||
|
||||
make() {
|
||||
if (this.body || !this.parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.inline) {
|
||||
this.body = this.parent
|
||||
} else {
|
||||
this.body = frappe.ui.add('div', 'form-body', this.parent);
|
||||
}
|
||||
|
||||
if (this.actions) {
|
||||
this.makeToolbar();
|
||||
}
|
||||
|
||||
this.form = frappe.ui.add('form', 'form-container', this.body);
|
||||
|
||||
if (this.inline) {
|
||||
this.form.classList.add('form-inline');
|
||||
}
|
||||
|
||||
this.form.onValidate = true;
|
||||
|
||||
this.formLayout = new FormLayout({
|
||||
fields: this.meta.fields,
|
||||
layout: this.meta.layout
|
||||
});
|
||||
|
||||
this.form.appendChild(this.formLayout.form);
|
||||
|
||||
this.bindKeyboard();
|
||||
}
|
||||
|
||||
bindFormEvents() {
|
||||
if (this.meta.formEvents) {
|
||||
for (let key in this.meta.formEvents) {
|
||||
this.on(key, this.meta.formEvents[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
makeToolbar() {
|
||||
if (this.actions.includes('save')) {
|
||||
this.makeSaveButton();
|
||||
|
||||
if (this.meta.isSubmittable) {
|
||||
this.makeSubmitButton();
|
||||
this.makeRevertButton();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.meta.print && this.actions.includes('print')) {
|
||||
let menu = this.container.getDropdown(frappe._('Menu'));
|
||||
menu.addItem(frappe._("Print"), async (e) => {
|
||||
await frappe.router.setRoute('print', this.doctype, this.doc.name);
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.meta.isSingle && this.actions.includes('delete')) {
|
||||
let menu = this.container.getDropdown(frappe._('Menu'));
|
||||
menu.addItem(frappe._("Delete"), async (e) => {
|
||||
await this.delete();
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.meta.isSingle && this.actions.includes('duplicate')) {
|
||||
let menu = this.container.getDropdown(frappe._('Menu'));
|
||||
menu.addItem(frappe._('Duplicate'), async () => {
|
||||
let newDoc = await frappe.getDuplicate(this.doc);
|
||||
await frappe.router.setRoute('edit', newDoc.doctype, newDoc.name);
|
||||
newDoc.set('name', '');
|
||||
});
|
||||
}
|
||||
|
||||
if (this.meta.settings && this.actions.includes('settings')) {
|
||||
let menu = this.container.getDropdown(frappe._('Menu'));
|
||||
menu.addItem(frappe._('Settings...'), () => {
|
||||
frappe.desk.showFormModal(this.meta.settings, this.meta.settings);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
makeSaveButton() {
|
||||
this.saveButton = this.container.addButton(frappe._("Save"), 'primary', async (event) => {
|
||||
await this.save();
|
||||
});
|
||||
this.on('change', () => {
|
||||
const show = this.doc._dirty && !this.doc.submitted;
|
||||
this.saveButton.classList.toggle('hide', !show);
|
||||
});
|
||||
}
|
||||
|
||||
makeSubmitButton() {
|
||||
this.submitButton = this.container.addButton(frappe._("Submit"), 'primary', async (event) => {
|
||||
await this.submit();
|
||||
});
|
||||
this.on('change', () => {
|
||||
const show = this.meta.isSubmittable && !this.doc._dirty && !this.doc.submitted;
|
||||
this.submitButton.classList.toggle('hide', !show);
|
||||
});
|
||||
}
|
||||
|
||||
makeRevertButton() {
|
||||
this.revertButton = this.container.addButton(frappe._("Revert"), 'secondary', async (event) => {
|
||||
await this.revert();
|
||||
});
|
||||
this.on('change', () => {
|
||||
const show = this.meta.isSubmittable && !this.doc._dirty && this.doc.submitted;
|
||||
this.revertButton.classList.toggle('hide', !show);
|
||||
});
|
||||
}
|
||||
|
||||
bindKeyboard() {
|
||||
keyboard.bindKey(this.form, 'ctrl+s', (e) => {
|
||||
if (document.activeElement) {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
e.preventDefault();
|
||||
if (this.doc._notInserted || this.doc._dirty) {
|
||||
this.save();
|
||||
} else {
|
||||
if (this.meta.isSubmittable && !this.doc.submitted) this.submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async setDoc(doctype, name) {
|
||||
this.doc = await frappe.getDoc(doctype, name);
|
||||
this.bindEvents(this.doc);
|
||||
if (this.doc._notInserted && !this.doc._nameCleared) {
|
||||
this.doc._nameCleared = true;
|
||||
// flag so that name is cleared only once
|
||||
await this.doc.set('name', '');
|
||||
}
|
||||
this.setTitle();
|
||||
frappe._curFrm = this;
|
||||
}
|
||||
|
||||
setTitle() {
|
||||
if (!this.container) return;
|
||||
|
||||
const doctypeLabel = this.doc.meta.label || this.doc.meta.name;
|
||||
|
||||
if (this.doc.meta.isSingle || this.doc.meta.naming === 'random') {
|
||||
this.container.setTitle(doctypeLabel);
|
||||
} else if (this.doc._notInserted) {
|
||||
this.container.setTitle(frappe._('New {0}', doctypeLabel));
|
||||
} else {
|
||||
this.container.setTitle(this.doc.name);
|
||||
}
|
||||
if (this.doc.submitted) {
|
||||
// this.container.addTitleBadge('✓', frappe._('Submitted'));
|
||||
}
|
||||
}
|
||||
|
||||
setLinks(label, options) {
|
||||
// set links to helpful reports as identified by this.meta.links
|
||||
if (this.meta.links) {
|
||||
let links = this.getLinks();
|
||||
if (!links.equals(this.links)) {
|
||||
this.refreshLinks(links);
|
||||
this.links = links;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getLinks() {
|
||||
let links = [];
|
||||
for (let link of this.meta.links) {
|
||||
if (link.condition(this)) {
|
||||
links.push(link);
|
||||
}
|
||||
}
|
||||
return links;
|
||||
}
|
||||
|
||||
refreshLinks(links) {
|
||||
if (!(this.container && this.container.clearLinks)) return;
|
||||
|
||||
this.container.clearLinks();
|
||||
for(let link of links) {
|
||||
// make the link
|
||||
utils.addButton(link.label, this.container.linksElement, () => {
|
||||
let options = link.action(this);
|
||||
|
||||
if (options) {
|
||||
if (options.params) {
|
||||
// set route parameters
|
||||
frappe.params = options.params;
|
||||
}
|
||||
|
||||
if (options.route) {
|
||||
// go to the given route
|
||||
frappe.router.setRoute(...options.route);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async bindEvents(doc) {
|
||||
if (this.doc && this.docListener) {
|
||||
// stop listening to the old doc
|
||||
this.doc.off(this.docListener);
|
||||
}
|
||||
this.doc = doc;
|
||||
for (let control of this.formLayout.controlList) {
|
||||
control.bind(this.doc);
|
||||
}
|
||||
|
||||
this.refresh();
|
||||
this.setupDocListener();
|
||||
this.trigger('use', {doc:doc});
|
||||
}
|
||||
|
||||
setupDocListener() {
|
||||
// refresh value in control
|
||||
this.docListener = (params) => {
|
||||
if (params.fieldname) {
|
||||
// only single value changed
|
||||
let control = this.formLayout.controls[params.fieldname];
|
||||
if (control && control.getInputValue() !== control.format(params.fieldname)) {
|
||||
control.refresh();
|
||||
}
|
||||
} else {
|
||||
// multiple values changed
|
||||
this.refresh();
|
||||
}
|
||||
this.trigger('change');
|
||||
this.form.classList.remove('was-validated');
|
||||
};
|
||||
|
||||
this.doc.on('change', this.docListener);
|
||||
this.trigger('change');
|
||||
}
|
||||
|
||||
checkValidity() {
|
||||
let validity = this.form.checkValidity();
|
||||
if (validity) {
|
||||
for (let control of this.formLayout.controlList) {
|
||||
// check validity in table
|
||||
if (control.fieldtype==='Table') {
|
||||
validity = control.checkValidity();
|
||||
if (!validity) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return validity;
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.formLayout.refresh();
|
||||
this.trigger('refresh', this);
|
||||
this.setLinks();
|
||||
}
|
||||
|
||||
async submit() {
|
||||
this.doc.submitted = 1;
|
||||
await this.save();
|
||||
}
|
||||
|
||||
async revert() {
|
||||
this.doc.submitted = 0;
|
||||
await this.save();
|
||||
}
|
||||
|
||||
async save() {
|
||||
if (!this.checkValidity()) {
|
||||
this.form.classList.add('was-validated');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
let oldName = this.doc.name;
|
||||
if (this.doc._notInserted) {
|
||||
await this.doc.insert();
|
||||
} else {
|
||||
await this.doc.update();
|
||||
}
|
||||
frappe.ui.showAlert({message: frappe._('Saved'), color: 'green'});
|
||||
if (oldName !== this.doc.name) {
|
||||
frappe.router.setRoute('edit', this.doctype, this.doc.name);
|
||||
return;
|
||||
}
|
||||
this.refresh();
|
||||
this.trigger('change');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
frappe.ui.showAlert({message: frappe._('Failed'), color: 'red'});
|
||||
return;
|
||||
}
|
||||
await this.trigger('save');
|
||||
}
|
||||
|
||||
async delete() {
|
||||
try {
|
||||
await this.doc.delete();
|
||||
frappe.ui.showAlert({message: frappe._('Deleted'), color: 'green'});
|
||||
this.trigger('delete');
|
||||
} catch (e) {
|
||||
frappe.ui.showAlert({message: e, color: 'red'});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
const frappe = require('frappejs');
|
||||
const controls = require('./controls');
|
||||
const Observable = require('frappejs/utils/observable');
|
||||
|
||||
module.exports = class FormLayout extends Observable {
|
||||
constructor({fields, doc, layout, inline = false, events = []}) {
|
||||
super();
|
||||
Object.assign(this, arguments[0]);
|
||||
this.controls = {};
|
||||
this.controlList = [];
|
||||
this.sections = [];
|
||||
this.links = [];
|
||||
|
||||
this.form = document.createElement('div');
|
||||
this.form.classList.add('form-body');
|
||||
|
||||
if (this.inline) {
|
||||
this.form.classList.add('row');
|
||||
this.form.classList.add('p-0');
|
||||
}
|
||||
|
||||
this.makeLayout();
|
||||
|
||||
if (doc) {
|
||||
this.bindEvents(doc);
|
||||
}
|
||||
}
|
||||
|
||||
makeLayout() {
|
||||
if (this.layout) {
|
||||
for (let section of this.layout) {
|
||||
this.makeSection(section);
|
||||
}
|
||||
} else {
|
||||
this.makeControls(this.fields);
|
||||
}
|
||||
}
|
||||
|
||||
makeSection(section) {
|
||||
const sectionElement = frappe.ui.add('div', 'form-section', this.form);
|
||||
const sectionHead = frappe.ui.add('div', 'form-section-head', sectionElement);
|
||||
const sectionBody = frappe.ui.add('div', 'form-section-body', sectionElement);
|
||||
|
||||
if (section.title) {
|
||||
const head = frappe.ui.add('h6', 'uppercase', sectionHead);
|
||||
head.textContent = section.title;
|
||||
}
|
||||
|
||||
if (section.columns) {
|
||||
sectionBody.classList.add('row');
|
||||
for (let column of section.columns) {
|
||||
let columnElement = frappe.ui.add('div', 'col', sectionBody);
|
||||
this.makeControls(this.getFieldsFromLayoutElement(column.fields), columnElement);
|
||||
}
|
||||
} else {
|
||||
this.makeControls(this.getFieldsFromLayoutElement(section.fields), sectionBody);
|
||||
}
|
||||
this.sections.push(sectionBody);
|
||||
}
|
||||
|
||||
getFieldsFromLayoutElement(fields) {
|
||||
return this.fields.filter(d => fields.includes(d.fieldname));
|
||||
}
|
||||
|
||||
makeControls(fields, parent) {
|
||||
for(let field of fields) {
|
||||
if (!field.hidden && controls.getControlClass(field.fieldtype)) {
|
||||
let control = controls.makeControl({field: field, form: this, parent: parent});
|
||||
this.controlList.push(control);
|
||||
this.controls[field.fieldname] = control;
|
||||
if (this.inline) {
|
||||
control.inputContainer.classList.add('col');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async bindEvents(doc) {
|
||||
this.doc = doc;
|
||||
this.controlList.forEach(control => {
|
||||
control.bind(this.doc);
|
||||
});
|
||||
|
||||
this.doc.on('change', ({doc, fieldname}) => {
|
||||
this.controls[fieldname].refresh();
|
||||
});
|
||||
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
setValue(key, value) {
|
||||
if (!this.doc) return;
|
||||
this.doc.set(key, value);
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.controlList.forEach(control => {
|
||||
control.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
bindFormEvents() {
|
||||
if (this.events) {
|
||||
for (let key in this.events) {
|
||||
this.on(key, this.events[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
const BaseList = require('frappejs/client/view/list');
|
||||
const BaseTree = require('frappejs/client/view/tree');
|
||||
const BaseForm = require('frappejs/client/view/form');
|
||||
const frappe = require('frappejs');
|
||||
|
||||
module.exports = {
|
||||
getFormClass(doctype) {
|
||||
return (frappe.views['Form'] && frappe.views['Form'][doctype]) || BaseForm;
|
||||
},
|
||||
getListClass(doctype) {
|
||||
return (frappe.views['List'] && frappe.views['List'][doctype]) || BaseList;
|
||||
},
|
||||
getTreeClass(doctype) {
|
||||
return (frappe.views['Tree'] && frappe.views['Tree'][doctype] || BaseTree);
|
||||
}
|
||||
}
|
@ -1,319 +0,0 @@
|
||||
const frappe = require('frappejs');
|
||||
const keyboard = require('frappejs/client/ui/keyboard');
|
||||
const Observable = require('frappejs/utils/observable');
|
||||
|
||||
module.exports = class BaseList extends Observable {
|
||||
constructor({doctype, parent, fields=[], page}) {
|
||||
super();
|
||||
Object.assign(this, arguments[0]);
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.meta = frappe.getMeta(this.doctype);
|
||||
|
||||
this.start = 0;
|
||||
this.pageLength = 20;
|
||||
|
||||
this.body = null;
|
||||
this.rows = [];
|
||||
this.data = [];
|
||||
|
||||
this.setupTreeSettings();
|
||||
|
||||
frappe.db.on(`change:${this.doctype}`, (params) => {
|
||||
this.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
setupTreeSettings() {
|
||||
// list settings that can be overridden by meta
|
||||
this.listSettings = {
|
||||
getFields: list => list.fields,
|
||||
getRowHTML: (list, data) => {
|
||||
return `<div class="col-11">
|
||||
${list.getNameHTML(data)}
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.meta.listSettings) {
|
||||
Object.assign(this.listSettings, this.meta.listSettings);
|
||||
}
|
||||
}
|
||||
|
||||
makeBody() {
|
||||
if (!this.body) {
|
||||
this.makeToolbar();
|
||||
this.parent.classList.add('list-page');
|
||||
this.body = frappe.ui.add('div', 'list-body', this.parent);
|
||||
this.body.setAttribute('data-doctype', this.doctype);
|
||||
this.makeMoreBtn();
|
||||
this.bindKeys();
|
||||
}
|
||||
}
|
||||
|
||||
async refresh() {
|
||||
return await this.run();
|
||||
}
|
||||
|
||||
async run() {
|
||||
this.makeBody();
|
||||
this.dirty = false;
|
||||
|
||||
let data = await this.getData();
|
||||
|
||||
for (let i=0; i< Math.min(this.pageLength, data.length); i++) {
|
||||
let row = this.getRow(this.start + i);
|
||||
this.renderRow(row, data[i]);
|
||||
}
|
||||
|
||||
if (this.start > 0) {
|
||||
this.data = this.data.concat(data);
|
||||
} else {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
this.clearEmptyRows();
|
||||
this.updateMore(data.length > this.pageLength);
|
||||
this.selectDefaultRow();
|
||||
this.setActiveListRow();
|
||||
this.trigger('state-change');
|
||||
}
|
||||
|
||||
async getData() {
|
||||
let fields = this.listSettings.getFields(this) || [];
|
||||
this.updateStandardFields(fields);
|
||||
return await frappe.db.getAll({
|
||||
doctype: this.doctype,
|
||||
fields: fields,
|
||||
filters: this.getFilters(),
|
||||
start: this.start,
|
||||
limit: this.pageLength + 1
|
||||
});
|
||||
}
|
||||
|
||||
updateStandardFields(fields) {
|
||||
if (!fields.includes('name')) fields.push('name');
|
||||
if (!fields.includes('modified')) fields.push('modified');
|
||||
if (this.meta.isSubmittable && !fields.includes('submitted')) fields.push('submitted');
|
||||
}
|
||||
|
||||
async append() {
|
||||
this.start += this.pageLength;
|
||||
await this.run();
|
||||
}
|
||||
|
||||
getFilters() {
|
||||
let filters = {};
|
||||
if (this.searchInput.value) {
|
||||
filters.keywords = ['like', '%' + this.searchInput.value + '%'];
|
||||
}
|
||||
return filters;
|
||||
}
|
||||
|
||||
renderRow(row, data) {
|
||||
row.innerHTML = this.getRowBodyHTML(data);
|
||||
row.docName = data.name;
|
||||
row.setAttribute('data-name', data.name);
|
||||
}
|
||||
|
||||
getRowBodyHTML(data) {
|
||||
return `<div class="col-1">
|
||||
<input class="checkbox" type="checkbox" data-name="${data.name}">
|
||||
</div>` + this.listSettings.getRowHTML(this, data);
|
||||
}
|
||||
|
||||
|
||||
getNameHTML(data) {
|
||||
return `<span class="indicator ${this.meta.getIndicatorColor(data)}">${data[this.meta.titleField]}</span>`;
|
||||
}
|
||||
|
||||
getRow(i) {
|
||||
if (!this.rows[i]) {
|
||||
let row = frappe.ui.add('div', 'list-row row no-gutters', this.body);
|
||||
|
||||
// open on click
|
||||
let me = this;
|
||||
row.addEventListener('click', async function(e) {
|
||||
if (!e.target.tagName !== 'input') {
|
||||
await me.showItem(this.docName);
|
||||
}
|
||||
});
|
||||
row.style.display = 'flex';
|
||||
|
||||
// make element focusable
|
||||
row.setAttribute('tabindex', -1);
|
||||
this.rows[i] = row;
|
||||
}
|
||||
return this.rows[i];
|
||||
}
|
||||
|
||||
refreshRow(doc) {
|
||||
let row = this.getRowByName(doc.name);
|
||||
if (row) {
|
||||
this.renderRow(row, doc);
|
||||
}
|
||||
}
|
||||
|
||||
async showItem(name) {
|
||||
if (this.meta.print) {
|
||||
await frappe.router.setRoute('print', this.doctype, name);
|
||||
} else {
|
||||
await frappe.router.setRoute('edit', this.doctype, name);
|
||||
}
|
||||
}
|
||||
|
||||
getCheckedRowNames() {
|
||||
return [...this.body.querySelectorAll('.checkbox:checked')].map(check => check.getAttribute('data-name'));
|
||||
}
|
||||
|
||||
clearEmptyRows() {
|
||||
if (this.rows.length > this.data.length) {
|
||||
for (let i=this.data.length; i < this.rows.length; i++) {
|
||||
let row = this.getRow(i);
|
||||
row.innerHTML = '';
|
||||
row.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selectDefaultRow() {
|
||||
if (!frappe.desk.body.activePage && this.rows.length) {
|
||||
this.showItem(this.rows[0].docName);
|
||||
}
|
||||
}
|
||||
|
||||
makeToolbar() {
|
||||
this.makeSearch();
|
||||
|
||||
this.btnNew = this.page.addButton(frappe._('New'), 'btn-primary', async () => {
|
||||
await frappe.router.setRoute('new', this.doctype);
|
||||
});
|
||||
|
||||
this.btnDelete = this.page.addButton(frappe._('Delete'), 'btn-secondary hide', async () => {
|
||||
await frappe.db.deleteMany(this.doctype, this.getCheckedRowNames());
|
||||
await this.refresh();
|
||||
});
|
||||
|
||||
this.btnReport = this.page.addButton(frappe._('Report'), 'btn-outline-secondary hide', async () => {
|
||||
await frappe.router.setRoute('table', this.doctype);
|
||||
});
|
||||
|
||||
this.on('state-change', () => {
|
||||
const checkedCount = this.getCheckedRowNames().length;
|
||||
this.btnDelete.classList.toggle('hide', checkedCount ? false : true);
|
||||
this.btnNew.classList.toggle('hide', checkedCount ? true : false);
|
||||
this.btnReport.classList.toggle('hide', checkedCount ? true : false);
|
||||
});
|
||||
|
||||
this.page.body.addEventListener('click', (event) => {
|
||||
if(event.target.classList.contains('checkbox')) {
|
||||
this.trigger('state-change');
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
makeSearch() {
|
||||
this.toolbar = frappe.ui.add('div', 'list-toolbar', this.parent);
|
||||
this.toolbar.innerHTML = `
|
||||
<div class="input-group list-search">
|
||||
<input class="form-control" type="text" placeholder="Search...">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-outline-secondary btn-search">Search</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.searchInput = this.toolbar.querySelector('input');
|
||||
this.searchInput.addEventListener('keypress', (event) => {
|
||||
if (event.keyCode===13) {
|
||||
this.refresh();
|
||||
}
|
||||
});
|
||||
|
||||
this.btnSearch = this.toolbar.querySelector('.btn-search');
|
||||
this.btnSearch.addEventListener('click', (event) => {
|
||||
this.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
bindKeys() {
|
||||
keyboard.bindKey(this.body, 'up', () => this.move('up'));
|
||||
keyboard.bindKey(this.body, 'down', () => this.move('down'))
|
||||
|
||||
keyboard.bindKey(this.body, 'right', () => {
|
||||
if (frappe.desk.body.activePage) {
|
||||
frappe.desk.body.activePage.body.querySelector('input').focus();
|
||||
}
|
||||
});
|
||||
|
||||
keyboard.bindKey(this.body, 'n', (e) => {
|
||||
frappe.router.setRoute('new', this.doctype);
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
keyboard.bindKey(this.body, 'x', async (e) => {
|
||||
let activeListRow = this.getActiveListRow();
|
||||
if (activeListRow && activeListRow.docName) {
|
||||
e.preventDefault();
|
||||
await frappe.db.delete(this.doctype, activeListRow.docName);
|
||||
frappe.desk.body.activePage.hide();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
async move(direction) {
|
||||
let elementRef = direction === 'up' ? 'previousSibling' : 'nextSibling';
|
||||
if (document.activeElement && document.activeElement.classList.contains('list-row')) {
|
||||
let next = document.activeElement[elementRef];
|
||||
if (next && next.docName) {
|
||||
await this.showItem(next.docName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
makeMoreBtn() {
|
||||
this.btnMore = frappe.ui.add('button', 'btn btn-secondary hide', this.parent, 'More');
|
||||
this.btnMore.addEventListener('click', () => {
|
||||
this.append();
|
||||
})
|
||||
}
|
||||
|
||||
updateMore(show) {
|
||||
if (show) {
|
||||
this.btnMore.classList.remove('hide');
|
||||
} else {
|
||||
this.btnMore.classList.add('hide');
|
||||
}
|
||||
}
|
||||
|
||||
setActiveListRow(name) {
|
||||
let activeListRow = this.getActiveListRow();
|
||||
if (activeListRow) {
|
||||
activeListRow.classList.remove('active');
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
// get name from active page
|
||||
name = frappe.desk.activeDoc && frappe.desk.activeDoc.name;
|
||||
}
|
||||
|
||||
if (name) {
|
||||
let row = this.getRowByName(name);
|
||||
if (row) {
|
||||
row.classList.add('active');
|
||||
row.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getRowByName(name) {
|
||||
return this.body.querySelector(`.list-row[data-name="${name}"]`);
|
||||
}
|
||||
|
||||
getActiveListRow() {
|
||||
return this.body.querySelector('.list-row.active');
|
||||
}
|
||||
};
|
@ -1,100 +0,0 @@
|
||||
const frappe = require('frappejs');
|
||||
const Observable = require('frappejs/utils/observable');
|
||||
const Dropdown = require('frappejs/client/ui/dropdown');
|
||||
|
||||
module.exports = class Page extends Observable {
|
||||
constructor({title, parent, hasRoute=true} = {}) {
|
||||
super();
|
||||
Object.assign(this, arguments[0]);
|
||||
if (!this.parent) {
|
||||
this.parent = frappe.desk.body;
|
||||
}
|
||||
this.make();
|
||||
this.dropdowns = {};
|
||||
if(this.title) {
|
||||
this.wrapper.setAttribute('title', this.title);
|
||||
this.setTitle(this.title);
|
||||
}
|
||||
}
|
||||
|
||||
make() {
|
||||
this.wrapper = frappe.ui.add('div', 'page hide', this.parent);
|
||||
this.head = frappe.ui.add('div', 'page-nav clearfix hide', this.wrapper);
|
||||
this.titleElement = frappe.ui.add('h3', 'page-title', this.wrapper);
|
||||
this.linksElement = frappe.ui.add('div', 'btn-group page-links hide', this.wrapper);
|
||||
this.body = frappe.ui.add('div', 'page-body', this.wrapper);
|
||||
}
|
||||
|
||||
setTitle(title) {
|
||||
this.titleElement.textContent = title;
|
||||
if (this.hasRoute) {
|
||||
document.title = title;
|
||||
}
|
||||
}
|
||||
|
||||
addTitleBadge(message, title='', style='secondary') {
|
||||
this.titleElement.innerHTML += ` <span class='badge badge-${style}' title='${title}'>
|
||||
${message}</span>`;
|
||||
}
|
||||
|
||||
clearLinks() {
|
||||
frappe.ui.empty(this.linksElement);
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.parent.activePage = null;
|
||||
this.wrapper.classList.add('hide');
|
||||
this.trigger('hide');
|
||||
}
|
||||
|
||||
addButton(label, className, action) {
|
||||
this.head.classList.remove('hide');
|
||||
this.button = frappe.ui.add('button', 'btn btn-sm float-right ' + this.getClassName(className), this.head);
|
||||
this.button.innerHTML = label;
|
||||
this.button.addEventListener('click', action);
|
||||
return this.button;
|
||||
}
|
||||
|
||||
getDropdown(label) {
|
||||
if (!this.dropdowns[label]) {
|
||||
this.dropdowns[label] = new Dropdown({parent: this.head, label: label,
|
||||
right: true, cssClass: 'btn-secondary btn-sm'});
|
||||
}
|
||||
return this.dropdowns[label];
|
||||
}
|
||||
|
||||
async show(params) {
|
||||
if (this.parent.activePage) {
|
||||
this.parent.activePage.hide();
|
||||
}
|
||||
|
||||
this.wrapper.classList.remove('hide');
|
||||
this.body.classList.remove('hide');
|
||||
|
||||
if (this.page_error) {
|
||||
this.page_error.classList.add('hide');
|
||||
}
|
||||
|
||||
this.parent.activePage = this;
|
||||
|
||||
frappe.desk.toggleCenter(this.fullPage ? false : true);
|
||||
}
|
||||
|
||||
renderError(title, message) {
|
||||
if (!this.page_error) {
|
||||
this.page_error = frappe.ui.add('div', 'page-error', this.wrapper);
|
||||
}
|
||||
this.body.classList.add('hide');
|
||||
this.page_error.classList.remove('hide');
|
||||
this.page_error.innerHTML = `<h3 class="text-extra-muted">${title ? title : ""}</h3><p class="text-muted">${message ? message : ""}</p>`;
|
||||
}
|
||||
|
||||
getClassName(className) {
|
||||
const newName = {
|
||||
'primary': 'btn-primary',
|
||||
'secondary': 'btn-outline-secondary'
|
||||
}[className];
|
||||
|
||||
return newName || className;
|
||||
}
|
||||
}
|
@ -1,180 +0,0 @@
|
||||
const frappe = require('frappejs');
|
||||
const BaseList = require('./list');
|
||||
const Tree = require('frappejs/client/components/tree');
|
||||
// const keyboard = require('frappejs/client/ui/keyboard');
|
||||
|
||||
module.exports = class BaseTree extends BaseList {
|
||||
|
||||
init() {
|
||||
this.meta = frappe.getMeta(this.doctype);
|
||||
|
||||
this.body = null;
|
||||
this.data = [];
|
||||
|
||||
this.setupTreeSettings();
|
||||
|
||||
frappe.db.on(`change:${this.doctype}`, (params) => {
|
||||
this.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
setupTreeSettings() {
|
||||
// tree settings that can be overridden by meta
|
||||
this.treeSettings = {
|
||||
parentField: `parent${this.doctype}`
|
||||
}
|
||||
|
||||
if (this.meta.treeSettings) {
|
||||
Object.assign(this.treeSettings, this.meta.treeSettings);
|
||||
}
|
||||
}
|
||||
|
||||
async refresh() {
|
||||
return await this.run();
|
||||
}
|
||||
|
||||
async run() {
|
||||
this.makeBody();
|
||||
this.body.innerHTML = '';
|
||||
this.dirty = false;
|
||||
|
||||
const rootLabel = this.treeSettings.getRootLabel ?
|
||||
await this.treeSettings.getRootLabel() :
|
||||
this.doctype;
|
||||
|
||||
this.renderTree(rootLabel);
|
||||
this.trigger('state-change');
|
||||
}
|
||||
|
||||
makeBody() {
|
||||
if (!this.body) {
|
||||
this.makeToolbar();
|
||||
this.parent.classList.add('tree-page');
|
||||
this.body = frappe.ui.add('div', 'tree-body', this.parent);
|
||||
this.body.setAttribute('data-doctype', this.doctype);
|
||||
this.bindKeys();
|
||||
}
|
||||
}
|
||||
|
||||
renderTree(rootLabel) {
|
||||
this.rootNode = {
|
||||
label: rootLabel,
|
||||
value: rootLabel,
|
||||
isRoot: true,
|
||||
isGroup: true,
|
||||
children: null
|
||||
}
|
||||
|
||||
this.treeWrapper = frappe.ui.create(`
|
||||
<f-tree>
|
||||
${this.getTreeNodeHTML(this.rootNode)}
|
||||
</f-tree>
|
||||
`);
|
||||
|
||||
const rootNode = this.treeWrapper.querySelector('f-tree-node[is-root]');
|
||||
rootNode.props = this.rootNode;
|
||||
|
||||
this.body.appendChild(this.treeWrapper);
|
||||
|
||||
frappe.ui.on(this.treeWrapper, 'tree-node-expand', 'f-tree-node', async (e, treeNode) => {
|
||||
if (!treeNode.expanded) return;
|
||||
|
||||
if (!treeNode.props.children) {
|
||||
const data = await this.getData(treeNode.props);
|
||||
const children = data.map(d => ({
|
||||
label: d.name,
|
||||
value: d.name,
|
||||
isGroup: d.isGroup,
|
||||
doc: d
|
||||
}));
|
||||
treeNode.props.children = children;
|
||||
|
||||
for (let child of children) {
|
||||
const childNode = frappe.ui.create(this.getTreeNodeHTML(child));
|
||||
childNode.props = child;
|
||||
treeNode.appendChild(childNode);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.on(this.treeWrapper, 'tree-node-action', 'f-tree-node', (e, treeNode) => {
|
||||
if (treeNode.isRoot) return;
|
||||
|
||||
const button = e.detail.actionEl;
|
||||
const action = button.getAttribute('data-action');
|
||||
|
||||
if (action === 'edit') {
|
||||
this.edit(treeNode.props.doc.name);
|
||||
} else if (action === 'addChild') {
|
||||
this.addChildNode(treeNode.props.doc.name);
|
||||
}
|
||||
});
|
||||
|
||||
rootNode.click(); // open the root node
|
||||
}
|
||||
|
||||
edit(name) {
|
||||
frappe.desk.showFormModal(this.doctype, name);
|
||||
}
|
||||
|
||||
async addChildNode(name) {
|
||||
const newDoc = await frappe.getNewDoc(this.doctype);
|
||||
const formModal = await frappe.desk.showFormModal(this.doctype, newDoc.name);
|
||||
const parentField = this.treeSettings.parentField;
|
||||
if (formModal.form.doc.meta.hasField(parentField)) {
|
||||
formModal.form.doc.set(parentField, name);
|
||||
}
|
||||
}
|
||||
|
||||
async getData(node) {
|
||||
let fields = this.getFields();
|
||||
let filters = {};
|
||||
|
||||
if (node.isRoot) {
|
||||
filters[this.treeSettings.parentField] = '';
|
||||
} else {
|
||||
filters[this.treeSettings.parentField] = node.value;
|
||||
}
|
||||
|
||||
return await frappe.db.getAll({
|
||||
doctype: this.doctype,
|
||||
fields,
|
||||
filters,
|
||||
order_by: 'name',
|
||||
order: 'asc'
|
||||
});
|
||||
}
|
||||
|
||||
getTreeNodeHTML(node) {
|
||||
return (
|
||||
`<f-tree-node
|
||||
label="${node.label}"
|
||||
value="${node.value}"
|
||||
${node.expanded ? 'expanded' : ''}
|
||||
${node.isRoot ? 'is-root' : ''}
|
||||
${node.isGroup ? '' : 'is-leaf'}
|
||||
>
|
||||
${this.getActionButtonsHTML()}
|
||||
</f-tree-node>`
|
||||
);
|
||||
}
|
||||
|
||||
getActionButtonsHTML() {
|
||||
return [
|
||||
{ id: 'edit', label: frappe._('Edit') },
|
||||
{ id: 'addChild', label: frappe._('Add Child') },
|
||||
// { id: 'delete', label: frappe._('Delete') },
|
||||
].map(button => {
|
||||
return `<button class="btn btn-link btn-sm m-0" slot="actions" data-action="${button.id}">
|
||||
${button.label}
|
||||
</button>`;
|
||||
})
|
||||
.join('');
|
||||
}
|
||||
|
||||
getFields() {
|
||||
let fields = [this.treeSettings.parentField, 'isGroup']
|
||||
this.updateStandardFields(fields);
|
||||
return fields;
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue
Block a user