2
0
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:
sahil28297 2018-10-10 15:24:33 +05:30 committed by Faris Ansari
parent 0a43015249
commit 2614ff8230
52 changed files with 0 additions and 3667 deletions

View File

@ -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;

View File

@ -1,11 +0,0 @@
<!-- styles -->
<style>
:host {
display: block;
}
</style>
<!-- template -->
<div class="tree">
<slot></slot>
</div>

View File

@ -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
}

View File

@ -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>

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
});
}
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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 || {}));
}
}

View File

@ -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();
}
}
})
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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');
}
};

View File

@ -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;
}
};

View File

@ -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
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -1,9 +0,0 @@
@import "./variables.scss";
.tree-body {
padding: $spacer-3 $spacer-4;
}
f-tree-node {
--tree-node-hover: $gray-100;
}

View File

@ -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;

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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'
},
}

View File

@ -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">&times;</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);
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -1,9 +0,0 @@
const LinkControl = require('./link');
class DynamicLinkControl extends LinkControl {
getTarget() {
return this.doc[this.references];
}
};
module.exports = DynamicLinkControl;

View File

@ -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;

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;

View File

@ -1,10 +0,0 @@
const BaseControl = require('./base');
class PasswordControl extends BaseControl {
make() {
super.make();
this.input.setAttribute('type', 'password');
}
};
module.exports = PasswordControl;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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'});
}
}
}

View File

@ -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]);
}
}
}
}

View File

@ -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);
}
}

View File

@ -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');
}
};

View File

@ -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;
}
}

View File

@ -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;
}
};