mirror of
https://github.com/frappe/books.git
synced 2024-11-08 23:00:56 +00:00
table filters, wip
This commit is contained in:
parent
9de539517f
commit
fb23980490
@ -396,7 +396,7 @@ module.exports = class Database extends Observable {
|
||||
const value = filters[key];
|
||||
if (value instanceof Array) {
|
||||
// if its like, we should add the wildcard "%" if the user has not added
|
||||
if (value[0].toLowerCase() === 'like' && !value[1].includes('%')) {
|
||||
if (['like', 'includes'].includes(value[0].toLowerCase()) && !value[1].includes('%')) {
|
||||
value[1] = `%${value[1]}%`;
|
||||
}
|
||||
conditions.push(`${key} ${value[0]} ?`);
|
||||
|
@ -6,6 +6,7 @@ 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;
|
||||
|
||||
@ -16,6 +17,10 @@ module.exports = class FormPage extends Page {
|
||||
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();
|
||||
|
@ -131,8 +131,8 @@ module.exports = class Desk {
|
||||
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);
|
||||
this.toggleCenter(page.fullPage ? false : true);
|
||||
await page.show(params);
|
||||
}
|
||||
|
||||
async showFormModal(doctype, name) {
|
||||
@ -163,8 +163,7 @@ module.exports = class Desk {
|
||||
}
|
||||
|
||||
addSidebarItem(label, action) {
|
||||
let item = frappe.ui.add('a', 'list-group-item list-group-item-action', this.sidebarList);
|
||||
item.textContent = label;
|
||||
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;
|
||||
|
@ -14,6 +14,8 @@ module.exports = class ListPage extends Page {
|
||||
hasRoute: hasRoute
|
||||
});
|
||||
|
||||
this.name = name;
|
||||
|
||||
this.list = new (view.getListClass(name))({
|
||||
doctype: name,
|
||||
parent: this.body,
|
||||
@ -29,7 +31,8 @@ module.exports = class ListPage extends Page {
|
||||
|
||||
async show(params) {
|
||||
super.show();
|
||||
this.setTitle(name===this.list.doctype ? (this.list.meta.label || this.list.meta.name) : name);
|
||||
|
||||
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();
|
||||
}
|
||||
|
@ -12,8 +12,7 @@ module.exports = class DeskMenu {
|
||||
}
|
||||
|
||||
addItem(label, action) {
|
||||
let item = frappe.ui.add('div', 'list-row', this.listGroup);
|
||||
item.textContent = label;
|
||||
let item = frappe.ui.add('div', 'list-row', this.listGroup, label);
|
||||
if (typeof action === 'string') {
|
||||
this.routeItems[action] = item;
|
||||
}
|
||||
|
@ -6,9 +6,8 @@ module.exports = class Navbar {
|
||||
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);
|
||||
this.brand = frappe.ui.add('a', 'navbar-brand', this.navbar, brand_label);
|
||||
this.brand.href = '#';
|
||||
this.brand.textContent = brand_label;
|
||||
|
||||
this.toggler = frappe.ui.add('button', 'navbar-toggler', this.navbar);
|
||||
this.toggler.setAttribute('type', 'button');
|
||||
@ -24,8 +23,7 @@ module.exports = class Navbar {
|
||||
|
||||
addItem(label, route) {
|
||||
let item = frappe.ui.add('li', 'nav-item', this.nav);
|
||||
item.link = frappe.ui.add('a', 'nav-link', item);
|
||||
item.link.textContent = label;
|
||||
item.link = frappe.ui.add('a', 'nav-link', item, label);
|
||||
item.link.href = route;
|
||||
this.items[label] = item;
|
||||
return item;
|
||||
|
@ -10,6 +10,7 @@ module.exports = class PrintPage extends Page {
|
||||
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)
|
||||
@ -36,8 +37,7 @@ module.exports = class PrintPage extends Page {
|
||||
|
||||
try {
|
||||
this.body.innerHTML = `<div class="print-page">${nunjucks.renderString(this.printFormat.template, context)}</div>`;
|
||||
this.setTitle(doc.name);
|
||||
if (doc.submitted) this.addTitleBadge('✓', 'Submitted');
|
||||
// this.setTitle(doc.name);
|
||||
} catch (e) {
|
||||
this.renderError('Template Error', e);
|
||||
throw e;
|
||||
|
@ -5,21 +5,45 @@ const ModelTable = require('frappejs/client/ui/modelTable');
|
||||
module.exports = class TablePage extends Page {
|
||||
constructor(doctype) {
|
||||
let meta = frappe.getMeta(doctype);
|
||||
super({title: `${meta.name}`, hasRoute: true});
|
||||
super({title: `${meta.label || meta.name}`, hasRoute: true});
|
||||
this.tableWrapper = frappe.ui.add('div', 'table-page-wrapper', this.body);
|
||||
this.doctype = doctype;
|
||||
this.fullPage = true;
|
||||
}
|
||||
|
||||
this.addButton('Set Filters', 'btn-secondary', async () => {
|
||||
const formModal = await frappe.desk.showFormModal('FilterSelector');
|
||||
formModal.form.once('apply-filters', () => {
|
||||
formModal.hide();
|
||||
this.run();
|
||||
})
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
async show(params) {
|
||||
super.show();
|
||||
if (!this.modelTable) {
|
||||
this.modelTable = new ModelTable({doctype: this.doctype, parent: this.body, layout: 'fixed'});
|
||||
|
||||
if (!this.filterSelector) {
|
||||
this.filterSelector = await frappe.getSingle('FilterSelector');
|
||||
this.filterSelector.reset(this.doctype);
|
||||
}
|
||||
|
||||
if (!this.modelTable) {
|
||||
this.modelTable = new ModelTable({
|
||||
doctype: this.doctype,
|
||||
parent: this.tableWrapper,
|
||||
layout: 'fluid'
|
||||
});
|
||||
}
|
||||
|
||||
this.run();
|
||||
}
|
||||
|
||||
async run() {
|
||||
const data = await frappe.db.getAll({
|
||||
doctype: this.doctype,
|
||||
fields: ['*'],
|
||||
filters: this.filterSelector.getFilters(),
|
||||
start: this.start,
|
||||
limit: 500
|
||||
});
|
||||
|
@ -35,21 +35,30 @@ html {
|
||||
.page {
|
||||
padding-bottom: $spacer-4;
|
||||
|
||||
.page-head {
|
||||
.page-nav {
|
||||
padding: $spacer-2 $spacer-3;
|
||||
background-color: $gray-100;
|
||||
border-bottom: 1px solid $gray-300;
|
||||
|
||||
.page-title {
|
||||
display: inline-block;
|
||||
margin-top: 0.3rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin-left: $spacer-2;
|
||||
}
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-weight: bold;
|
||||
padding: $spacer-4;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.page-links {
|
||||
padding: $spacer-3 $spacer-4;
|
||||
|
||||
.page-link {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.page-error {
|
||||
text-align: center;
|
||||
padding: 200px 0px;
|
||||
@ -57,7 +66,7 @@ html {
|
||||
}
|
||||
|
||||
.form-body {
|
||||
padding: $spacer-4;
|
||||
padding: $spacer-3 $spacer-4;
|
||||
|
||||
.form-check {
|
||||
margin-bottom: $spacer-2;
|
||||
@ -80,12 +89,12 @@ html {
|
||||
}
|
||||
|
||||
.list-search {
|
||||
padding: $spacer-3;
|
||||
padding: $spacer-3 $spacer-4;
|
||||
}
|
||||
|
||||
.list-body {
|
||||
.list-row {
|
||||
padding: $spacer-2 15px;
|
||||
padding: $spacer-2 $spacer-4;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
cursor: pointer;
|
||||
|
||||
@ -146,50 +155,11 @@ html {
|
||||
}
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
font-family: "SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace;
|
||||
border: 1px solid $gray-300;
|
||||
border-radius: 0.25rem;
|
||||
padding: $spacer-2;
|
||||
.table-page-wrapper {
|
||||
width: 100%;
|
||||
padding: $spacer-3 $spacer-4;
|
||||
}
|
||||
|
||||
.awesomplete {
|
||||
display: block;
|
||||
|
||||
> 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;
|
||||
}
|
||||
|
||||
|
||||
.table-wrapper {
|
||||
margin-top: $spacer-4;
|
||||
margin-bottom: $spacer-4;
|
||||
@ -232,3 +202,46 @@ mark {
|
||||
}
|
||||
}
|
||||
|
||||
.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 > 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;
|
||||
}
|
||||
|
||||
|
@ -31,8 +31,7 @@ class Dropdown {
|
||||
}
|
||||
|
||||
addItem(label, action) {
|
||||
let item = frappe.ui.add('button', 'dropdown-item', this.dropdownMenu);
|
||||
item.textContent = label;
|
||||
let item = frappe.ui.add('button', 'dropdown-item', this.dropdownMenu, label);
|
||||
item.setAttribute('type', 'button');
|
||||
if (typeof action === 'string') {
|
||||
item.addEventListener('click', async () => {
|
||||
|
@ -2,7 +2,7 @@ const frappe = require('frappejs');
|
||||
const Dropdown = require('./dropdown');
|
||||
|
||||
module.exports = {
|
||||
add(tag, className, parent) {
|
||||
add(tag, className, parent, textContent) {
|
||||
let element = document.createElement(tag);
|
||||
if (className) {
|
||||
for (let c of className.split(' ')) {
|
||||
@ -12,6 +12,9 @@ module.exports = {
|
||||
if (parent) {
|
||||
parent.appendChild(element);
|
||||
}
|
||||
if (textContent) {
|
||||
element.textContent = textContent;
|
||||
}
|
||||
return element;
|
||||
},
|
||||
|
||||
@ -19,6 +22,12 @@ module.exports = {
|
||||
element.parentNode.removeChild(element);
|
||||
},
|
||||
|
||||
empty(element) {
|
||||
while (element.firstChild) {
|
||||
element.removeChild(element.firstChild);
|
||||
}
|
||||
},
|
||||
|
||||
addClass(element, className) {
|
||||
if (element.classList) {
|
||||
element.classList.add(className);
|
||||
|
@ -4,7 +4,7 @@ const controls = require('frappejs/client/view/controls');
|
||||
const Modal = require('frappejs/client/ui/modal');
|
||||
|
||||
module.exports = class ModelTable {
|
||||
constructor({doctype, parent, layout='fixed', parentControl, getRowDoc,
|
||||
constructor({doctype, parent, layout, parentControl, getRowDoc,
|
||||
isDisabled, getTableData}) {
|
||||
Object.assign(this, arguments[0]);
|
||||
this.meta = frappe.getMeta(this.doctype);
|
||||
@ -15,12 +15,16 @@ module.exports = class ModelTable {
|
||||
this.datatable = new DataTable(this.parent, {
|
||||
columns: this.getColumns(),
|
||||
data: [],
|
||||
layout: this.meta.layout || 'fixed',
|
||||
layout: this.meta.layout || this.layout || 'fluid',
|
||||
addCheckboxColumn: true,
|
||||
getEditor: this.getTableInput.bind(this),
|
||||
});
|
||||
}
|
||||
|
||||
resize() {
|
||||
this.datatable.setDimensions();
|
||||
}
|
||||
|
||||
getColumns() {
|
||||
return this.getTableFields().map(field => {
|
||||
if (!field.width) {
|
||||
@ -51,7 +55,7 @@ module.exports = class ModelTable {
|
||||
|
||||
getTableInput(colIndex, rowIndex, value, parent) {
|
||||
let field = this.datatable.getColumn(colIndex).field;
|
||||
if (field.disabled || field.forumla || (this.isDisabled && this.isDisabled())) {
|
||||
if (field.disabled || (this.isDisabled && this.isDisabled())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -59,7 +63,8 @@ module.exports = class ModelTable {
|
||||
// text in modal
|
||||
parent = this.getControlModal(field).getBody();
|
||||
}
|
||||
return this.getControl(field, parent);
|
||||
const editor = this.getControl(field, parent);
|
||||
return editor;
|
||||
}
|
||||
|
||||
getControl(field, parent) {
|
||||
@ -75,12 +80,11 @@ module.exports = class ModelTable {
|
||||
column.activeControl = control;
|
||||
control.parentControl = this.parentControl;
|
||||
control.doc = doc;
|
||||
control.set_focus();
|
||||
return control.setInputValue(control.doc[column.id]);
|
||||
control.setFocus();
|
||||
control.setInputValue(control.doc[column.id]);
|
||||
return control;
|
||||
},
|
||||
setValue: async (value, rowIndex, column) => {
|
||||
if (this.doc) this.doc._dirty = true;
|
||||
control.doc._dirty = true;
|
||||
control.handleChange();
|
||||
},
|
||||
getValue: () => {
|
||||
|
@ -68,8 +68,7 @@ class BaseControl {
|
||||
}
|
||||
|
||||
makeLabel(labelClass = null) {
|
||||
this.labelElement = frappe.ui.add('label', labelClass, this.inputContainer);
|
||||
this.labelElement.textContent = this.label;
|
||||
this.labelElement = frappe.ui.add('label', labelClass, this.inputContainer, this.label);
|
||||
this.labelElement.setAttribute('for', this.id);
|
||||
}
|
||||
|
||||
@ -88,7 +87,17 @@ class BaseControl {
|
||||
}
|
||||
|
||||
isDisabled() {
|
||||
return this.disabled || this.formula || (this.doc && this.doc.submitted);
|
||||
let disabled = this.disabled;
|
||||
|
||||
if (this.doc && this.doc.submitted) {
|
||||
disabled = true;
|
||||
}
|
||||
|
||||
if (this.formula && this.fieldtype !== 'Table') {
|
||||
disabled = true;
|
||||
}
|
||||
|
||||
return disabled;
|
||||
}
|
||||
|
||||
setDisabled() {
|
||||
@ -112,8 +121,8 @@ class BaseControl {
|
||||
|
||||
makeDescription() {
|
||||
if (this.description) {
|
||||
this.description_element = frappe.ui.add('small', 'form-text text-muted', this.inputContainer);
|
||||
this.description_element.textContent = this.description;
|
||||
this.description_element = frappe.ui.add('small', 'form-text text-muted',
|
||||
this.inputContainer, this.description);
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,6 +171,7 @@ class BaseControl {
|
||||
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
|
||||
@ -178,7 +188,7 @@ class BaseControl {
|
||||
this.input.removeAttribute('disabled');
|
||||
}
|
||||
|
||||
set_focus() {
|
||||
setFocus() {
|
||||
this.input.focus();
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ class LinkControl extends BaseControl {
|
||||
async getList(query) {
|
||||
return (await frappe.db.getAll({
|
||||
doctype: this.target,
|
||||
filters: this.getFilters(query),
|
||||
filters: this.getFilters(query, this),
|
||||
limit: 50
|
||||
})).map(d => d.name);
|
||||
}
|
||||
|
@ -3,19 +3,46 @@ const frappe = require('frappejs');
|
||||
|
||||
class SelectControl extends BaseControl {
|
||||
makeInput() {
|
||||
this.input = frappe.ui.add('select', 'form-control', this.inputContainer);
|
||||
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');
|
||||
}
|
||||
|
||||
for (let value of options) {
|
||||
let option = frappe.ui.add('option', null, this.input);
|
||||
option.textContent = value;
|
||||
option.setAttribute('value', value);
|
||||
}
|
||||
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');
|
||||
|
@ -9,7 +9,7 @@ class TableControl extends BaseControl {
|
||||
doctype: this.childtype,
|
||||
parent: this.wrapper.querySelector('.datatable-wrapper'),
|
||||
parentControl: this,
|
||||
layout: this.layout || 'fixed',
|
||||
layout: this.layout || 'ratio',
|
||||
getRowDoc: (rowIndex) => this.doc[this.fieldname][rowIndex],
|
||||
isDisabled: () => this.isDisabled(),
|
||||
getTableData: () => this.getTableData()
|
||||
@ -20,7 +20,7 @@ class TableControl extends BaseControl {
|
||||
makeWrapper() {
|
||||
this.wrapper = frappe.ui.add('div', 'table-wrapper', this.getInputParent());
|
||||
this.wrapper.innerHTML =
|
||||
`<div class="datatable-wrapper"></div>
|
||||
`<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>
|
||||
@ -38,7 +38,7 @@ class TableControl extends BaseControl {
|
||||
|
||||
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));
|
||||
this.doc[this.fieldname] = this.doc[this.fieldname].filter(d => !checked.includes(d.idx + ''));
|
||||
await this.doc.commit();
|
||||
this.refresh();
|
||||
this.modelTable.checkAll(false);
|
||||
@ -69,7 +69,7 @@ class TableControl extends BaseControl {
|
||||
}
|
||||
|
||||
getTableData(value) {
|
||||
return value || this.getDefaultData();
|
||||
return (value && value.length) ? value : this.getDefaultData();
|
||||
}
|
||||
|
||||
getDefaultData() {
|
||||
@ -77,10 +77,15 @@ class TableControl extends BaseControl {
|
||||
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];
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ module.exports = class BaseForm extends Observable {
|
||||
this.setup();
|
||||
}
|
||||
this.make();
|
||||
this.bindFormEvents();
|
||||
}
|
||||
|
||||
make() {
|
||||
@ -33,6 +34,14 @@ module.exports = class BaseForm extends Observable {
|
||||
this.bindKeyboard();
|
||||
}
|
||||
|
||||
bindFormEvents() {
|
||||
if (this.meta.formEvents) {
|
||||
for (let key in this.meta.formEvents) {
|
||||
this.on(key, this.meta.formEvents[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
makeLayout() {
|
||||
if (this.meta.layout) {
|
||||
for (let section of this.meta.layout) {
|
||||
@ -194,6 +203,7 @@ module.exports = class BaseForm extends Observable {
|
||||
control.bind(this.doc);
|
||||
}
|
||||
|
||||
this.refresh();
|
||||
this.setupDocListener();
|
||||
this.trigger('use', {doc:doc});
|
||||
}
|
||||
@ -239,6 +249,7 @@ module.exports = class BaseForm extends Observable {
|
||||
for(let control of this.controlList) {
|
||||
control.refresh();
|
||||
}
|
||||
this.trigger('refresh', this);
|
||||
}
|
||||
|
||||
async submit() {
|
||||
|
@ -264,8 +264,7 @@ module.exports = class BaseList extends Observable {
|
||||
}
|
||||
|
||||
makeMoreBtn() {
|
||||
this.btnMore = frappe.ui.add('button', 'btn btn-secondary hide', this.parent);
|
||||
this.btnMore.textContent = 'More';
|
||||
this.btnMore = frappe.ui.add('button', 'btn btn-secondary hide', this.parent, 'More');
|
||||
this.btnMore.addEventListener('click', () => {
|
||||
this.append();
|
||||
})
|
||||
|
@ -19,13 +19,10 @@ module.exports = class Page extends Observable {
|
||||
|
||||
make() {
|
||||
this.wrapper = frappe.ui.add('div', 'page hide', this.parent);
|
||||
this.wrapper.innerHTML = `<div class="page-head clearfix hide">
|
||||
<span class="page-title font-weight-bold"></span>
|
||||
</div>
|
||||
<div class="page-body"></div>`
|
||||
this.head = this.wrapper.querySelector('.page-head');
|
||||
this.body = this.wrapper.querySelector('.page-body');
|
||||
this.titleElement = this.head.querySelector('.page-title');
|
||||
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', 'page-links hide', this.wrapper);
|
||||
this.body = frappe.ui.add('div', 'page-body', this.wrapper);
|
||||
}
|
||||
|
||||
setTitle(title) {
|
||||
@ -40,6 +37,15 @@ module.exports = class Page extends Observable {
|
||||
${message}</span>`;
|
||||
}
|
||||
|
||||
addLink(label, action, unhide = true) {
|
||||
const link = frappe.ui.add('a', 'page-link', this.linksElement, label);
|
||||
link.addEventListener('click', action);
|
||||
if (unhide) {
|
||||
this.linksElement.classList.remove('hide');
|
||||
}
|
||||
return link;
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.parent.activePage = null;
|
||||
this.wrapper.classList.add('hide');
|
||||
|
@ -7,7 +7,8 @@ module.exports = {
|
||||
fs.mkdirSync(`./models/doctype/${name}`);
|
||||
fs.writeFileSync(`./models/doctype/${name}/${name}.js`, `module.exports = {
|
||||
name: "${name}",
|
||||
doctype: "DocType",
|
||||
label: "${name}",
|
||||
naming: "name", // {random|autoincrement}
|
||||
isSingle: 0,
|
||||
isChild: 0,
|
||||
keywordFields: [],
|
||||
|
@ -201,7 +201,10 @@ module.exports = class BaseDocument extends Observable {
|
||||
// for each row
|
||||
for (let row of this[tablefield.fieldname]) {
|
||||
for (let field of formulaFields) {
|
||||
row[field.fieldname] = await field.formula(row, doc);
|
||||
const val = await field.formula(row, doc);
|
||||
if (val !== false) {
|
||||
row[field.fieldname] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -209,7 +212,10 @@ module.exports = class BaseDocument extends Observable {
|
||||
|
||||
// parent
|
||||
for (let field of this.meta.getFormulaFields()) {
|
||||
doc[field.fieldname] = await field.formula(doc);
|
||||
const val = await field.formula(doc);
|
||||
if (val !== false) {
|
||||
doc[field.fieldname] = val;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -134,7 +134,7 @@ module.exports = class BaseMeta extends BaseDocument {
|
||||
if (!this._keywordFields) {
|
||||
this._keywordFields = this.keywordFields;
|
||||
if (!(this._keywordFields && this._keywordFields.length && this.fields)) {
|
||||
this._keywordFields = this.fields.filter(field => field.required).map(field => field.fieldname);
|
||||
this._keywordFields = this.fields.filter(field => field.fieldtype !== 'Table' && field.required).map(field => field.fieldname);
|
||||
}
|
||||
if (!(this._keywordFields && this._keywordFields.length)) {
|
||||
this._keywordFields = ['name']
|
||||
@ -145,6 +145,8 @@ module.exports = class BaseMeta extends BaseDocument {
|
||||
|
||||
validateSelect(field, value) {
|
||||
let options = field.options;
|
||||
if (!options) return;
|
||||
|
||||
if (typeof options === 'string') {
|
||||
// values given as string
|
||||
options = field.options.split('\n');
|
||||
|
28
models/doctype/FilterGroup/FilterGroup.js
Normal file
28
models/doctype/FilterGroup/FilterGroup.js
Normal file
@ -0,0 +1,28 @@
|
||||
module.exports = {
|
||||
name: "FilterGroup",
|
||||
isSingle: 0,
|
||||
isChild: 0,
|
||||
keywordFields: [],
|
||||
fields: [
|
||||
{
|
||||
fieldname: "name",
|
||||
label: "Name",
|
||||
fieldtype: "Data",
|
||||
required: 1
|
||||
},
|
||||
{
|
||||
fieldname: "forDocType",
|
||||
label: "Document Type",
|
||||
fieldtype: "Data",
|
||||
required: 1,
|
||||
disabled: 1,
|
||||
},
|
||||
{
|
||||
fieldname: "items",
|
||||
fieldtype: "Table",
|
||||
childtype: "FilterItem",
|
||||
label: "Items",
|
||||
required: 1
|
||||
}
|
||||
]
|
||||
}
|
36
models/doctype/FilterItem/FilterItem.js
Normal file
36
models/doctype/FilterItem/FilterItem.js
Normal file
@ -0,0 +1,36 @@
|
||||
module.exports = {
|
||||
name: "FilterItem",
|
||||
doctype: "DocType",
|
||||
isSingle: 0,
|
||||
isChild: 1,
|
||||
keywordFields: [],
|
||||
fields: [
|
||||
{
|
||||
fieldname: "field",
|
||||
label: "Field",
|
||||
fieldtype: "Select",
|
||||
required: 1
|
||||
},
|
||||
{
|
||||
fieldname: "condition",
|
||||
label: "Condition",
|
||||
fieldtype: "Select",
|
||||
options: [
|
||||
'Equals',
|
||||
'>',
|
||||
'<',
|
||||
'>=',
|
||||
'<=',
|
||||
'Between',
|
||||
'Includes',
|
||||
'One Of'
|
||||
],
|
||||
required: 1
|
||||
},
|
||||
{
|
||||
fieldname: "value",
|
||||
label: "Value",
|
||||
fieldtype: "Data",
|
||||
}
|
||||
]
|
||||
}
|
79
models/doctype/FilterSelector/FilterSelector.js
Normal file
79
models/doctype/FilterSelector/FilterSelector.js
Normal file
@ -0,0 +1,79 @@
|
||||
const frappe = require('frappejs');
|
||||
|
||||
module.exports = {
|
||||
name: "FilterSelector",
|
||||
label: "Set Filters",
|
||||
documentClass: require('./FilterSelectorDocument'),
|
||||
isSingle: 1,
|
||||
isChild: 0,
|
||||
keywordFields: [],
|
||||
fields: [
|
||||
{
|
||||
fieldname: "forDocType",
|
||||
label: "Document Type",
|
||||
fieldtype: "Data",
|
||||
hidden: 1,
|
||||
},
|
||||
{
|
||||
fieldname: "filterGroup",
|
||||
label: "Saved Filters",
|
||||
fieldtype: "Link",
|
||||
target: "FilterGroup",
|
||||
getFilters: (query, control) => {
|
||||
return {
|
||||
forDocType: control.doc.forDocType,
|
||||
keywords: ["like", query]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldname: "filterGroupName",
|
||||
label: "New Filter Name",
|
||||
fieldtype: "Data",
|
||||
},
|
||||
{
|
||||
fieldname: "items",
|
||||
label: "Items",
|
||||
fieldtype: "Table",
|
||||
childtype: "FilterItem",
|
||||
neverEmpty: 1,
|
||||
|
||||
// copy items from saved filter group
|
||||
formula: async (doc) => {
|
||||
if (doc._lastFilterGroup !== doc.filterGroup) {
|
||||
// fitler changed
|
||||
|
||||
if (doc.filterGroup) {
|
||||
doc.items = [];
|
||||
const filterGroup = await frappe.getDoc('FilterGroup', doc.filterGroup);
|
||||
|
||||
// copy items
|
||||
for(let source of filterGroup.items) {
|
||||
const item = Object.assign({}, source);
|
||||
item.parent = item.name = '';
|
||||
doc.items.push(item);
|
||||
}
|
||||
} else {
|
||||
// no filter group selected
|
||||
doc.items = [{idx: 0}];
|
||||
}
|
||||
|
||||
doc._lastFilterGroup = doc.filterGroup;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
}
|
||||
],
|
||||
|
||||
formEvents: {
|
||||
// set the fields of the selected item in the 'select'
|
||||
refresh: (form) => {
|
||||
// override the `getOptions` method in the `field` property
|
||||
frappe.getMeta('FilterItem').getField('field').getOptions = () => {
|
||||
return frappe.getMeta(form.doc.forDocType).fields.map((f) => {
|
||||
return {label: f.label, value: f.fieldname};
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
55
models/doctype/FilterSelector/FilterSelectorDocument.js
Normal file
55
models/doctype/FilterSelector/FilterSelectorDocument.js
Normal file
@ -0,0 +1,55 @@
|
||||
const BaseDocument = require('frappejs/model/document');
|
||||
const frappe = require('frappejs');
|
||||
|
||||
module.exports = class FormSelector extends BaseDocument {
|
||||
reset(doctype) {
|
||||
this.forDocType = doctype;
|
||||
this.items = [];
|
||||
this.filterGroup = '';
|
||||
this.filterGroupName = '';
|
||||
}
|
||||
|
||||
getFilters() {
|
||||
const filters = {};
|
||||
for (let item of (this.items || [])) {
|
||||
if (item.condition === 'Equals') item.condition = '=';
|
||||
filters[item.field] = [item.condition, item.value];
|
||||
}
|
||||
return filters;
|
||||
}
|
||||
|
||||
async update() {
|
||||
// save new group filter
|
||||
if (frappe.isServer) {
|
||||
if (this.filterGroupName) {
|
||||
await this.makeFilterGroup();
|
||||
} else if (this.filterGroup) {
|
||||
await this.updateFilterGroup();
|
||||
}
|
||||
return this;
|
||||
} else {
|
||||
return super.update();
|
||||
}
|
||||
}
|
||||
|
||||
async makeFilterGroup() {
|
||||
const filterGroup = frappe.newDoc({doctype:'FilterGroup'});
|
||||
filterGroup.name = this.filterGroupName;
|
||||
this.updateFilterGroupValues(filterGroup);
|
||||
await filterGroup.insert();
|
||||
}
|
||||
|
||||
async updateFilterGroup() {
|
||||
const filterGroup = await frappe.getDoc('FilterGroup', this.filterGroup);
|
||||
this.updateFilterGroupValues(filterGroup);
|
||||
await filterGroup.update();
|
||||
}
|
||||
|
||||
updateFilterGroupValues(filterGroup) {
|
||||
filterGroup.forDocType = this.forDocType;
|
||||
filterGroup.items = [];
|
||||
for (let item of this.items) {
|
||||
filterGroup.items.push({field: item.field, condition: item.condition, value: item.value});
|
||||
}
|
||||
}
|
||||
}
|
14
models/doctype/FilterSelector/FilterSelectorForm.js
Normal file
14
models/doctype/FilterSelector/FilterSelectorForm.js
Normal file
@ -0,0 +1,14 @@
|
||||
const BaseForm = require('frappejs/client/view/form');
|
||||
const frappe = require('frappejs');
|
||||
|
||||
module.exports = class FilterSelectorForm extends BaseForm {
|
||||
makeSaveButton() {
|
||||
this.saveButton = this.container.addButton(frappe._("Apply"), 'primary', async (event) => {
|
||||
if (this.doc.filterGroupName || (this.doc.filterGroup && this.doc._dirty)) {
|
||||
// new filter, call update
|
||||
await this.save();
|
||||
}
|
||||
this.trigger('apply-filters');
|
||||
});
|
||||
}
|
||||
}
|
@ -1,7 +1,10 @@
|
||||
module.exports = {
|
||||
"naming": "autoincrement",
|
||||
"name": "ToDo",
|
||||
"doctype": "DocType",
|
||||
name: "ToDo",
|
||||
label: "To Do",
|
||||
naming: "autoincrement",
|
||||
pageSettings: {
|
||||
hideTitle: true
|
||||
},
|
||||
"isSingle": 0,
|
||||
"keywordFields": [
|
||||
"subject",
|
||||
|
@ -1,5 +1,8 @@
|
||||
module.exports = {
|
||||
models: {
|
||||
FilterItem: require('./doctype/FilterItem/FilterItem.js'),
|
||||
FilterGroup: require('./doctype/FilterGroup/FilterGroup.js'),
|
||||
FilterSelector: require('./doctype/FilterSelector/FilterSelector.js'),
|
||||
NumberSeries: require('./doctype/NumberSeries/NumberSeries.js'),
|
||||
PrintFormat: require('./doctype/PrintFormat/PrintFormat.js'),
|
||||
Role: require('./doctype/Role/Role.js'),
|
||||
|
Loading…
Reference in New Issue
Block a user