mirror of
https://github.com/frappe/books.git
synced 2025-01-22 22:58:28 +00:00
multi-delete
This commit is contained in:
parent
ed4dbb496c
commit
449df1dbca
@ -18,6 +18,7 @@
|
||||
"window": false,
|
||||
"describe": true,
|
||||
"before": true,
|
||||
"after": true,
|
||||
"it": true
|
||||
},
|
||||
|
||||
|
@ -189,6 +189,12 @@ module.exports = class Database {
|
||||
return values;
|
||||
}
|
||||
|
||||
async deleteMany(doctype, names) {
|
||||
for (const name of names) {
|
||||
await this.delete(doctype, name);
|
||||
}
|
||||
}
|
||||
|
||||
async delete(doctype, name) {
|
||||
await this.deleteOne(doctype, name);
|
||||
|
||||
@ -208,10 +214,10 @@ module.exports = class Database {
|
||||
}
|
||||
|
||||
async exists(doctype, name) {
|
||||
return (await this.get_value(doctype, name)) ? true : false;
|
||||
return (await this.getValue(doctype, name)) ? true : false;
|
||||
}
|
||||
|
||||
async get_value(doctype, filters, fieldname = 'name') {
|
||||
async getValue(doctype, filters, fieldname = 'name') {
|
||||
if (typeof filters === 'string') {
|
||||
filters = { name: filters };
|
||||
}
|
||||
|
@ -2,16 +2,11 @@ const frappe = require('frappejs');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = class RESTClient {
|
||||
constructor({server, protocol='http'}) {
|
||||
constructor({ server, protocol = 'http' }) {
|
||||
this.server = server;
|
||||
this.protocol = protocol;
|
||||
|
||||
this.initTypeMap();
|
||||
|
||||
this.json_headers = {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
|
||||
connect() {
|
||||
@ -20,29 +15,25 @@ module.exports = class RESTClient {
|
||||
|
||||
async insert(doctype, doc) {
|
||||
doc.doctype = doctype;
|
||||
let url = this.protocol + '://' + path.join(this.server, `/api/resource/${frappe.slug(doctype)}`);
|
||||
let response = await frappe.fetch(url, {
|
||||
let url = this.getURL('/api/resource', frappe.slug(doctype));
|
||||
return await this.fetch(url, {
|
||||
method: 'POST',
|
||||
headers: this.json_headers,
|
||||
body: JSON.stringify(doc)
|
||||
});
|
||||
|
||||
return await response.json();
|
||||
})
|
||||
}
|
||||
|
||||
async get(doctype, name) {
|
||||
let url = this.protocol + '://' + path.join(this.server, `/api/resource/${frappe.slug(doctype)}/${name}`);
|
||||
let response = await frappe.fetch(url, {
|
||||
let url = this.getURL('/api/resource', frappe.slug(doctype), name);
|
||||
return await this.fetch(url, {
|
||||
method: 'GET',
|
||||
headers: this.json_headers
|
||||
});
|
||||
return await response.json();
|
||||
headers: this.getHeaders()
|
||||
})
|
||||
}
|
||||
|
||||
async getAll({doctype, fields, filters, start, limit, sort_by, order}) {
|
||||
let url = this.protocol + '://' + path.join(this.server, `/api/resource/${frappe.slug(doctype)}`);
|
||||
async getAll({ doctype, fields, filters, start, limit, sort_by, order }) {
|
||||
let url = this.getURL('/api/resource', frappe.slug(doctype));
|
||||
|
||||
url = url + "?" + this.get_query_string({
|
||||
url = url + "?" + this.getQueryString({
|
||||
fields: JSON.stringify(fields),
|
||||
filters: JSON.stringify(filters),
|
||||
start: start,
|
||||
@ -51,80 +42,101 @@ module.exports = class RESTClient {
|
||||
order: order
|
||||
});
|
||||
|
||||
let response = await frappe.fetch(url, {
|
||||
return await this.fetch(url, {
|
||||
method: 'GET',
|
||||
headers: this.json_headers
|
||||
});
|
||||
return await response.json();
|
||||
|
||||
}
|
||||
|
||||
async update(doctype, doc) {
|
||||
doc.doctype = doctype;
|
||||
let url = this.protocol + '://' + path.join(this.server, `/api/resource/${frappe.slug(doctype)}/${doc.name}`);
|
||||
let response = await frappe.fetch(url, {
|
||||
let url = this.getURL('/api/resource', frappe.slug(doctype), doc.name);
|
||||
|
||||
return await this.fetch(url, {
|
||||
method: 'PUT',
|
||||
headers: this.json_headers,
|
||||
body: JSON.stringify(doc)
|
||||
});
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
async delete(doctype, name) {
|
||||
let url = this.protocol + '://' + path.join(this.server, `/api/resource/${frappe.slug(doctype)}/${name}`);
|
||||
let url = this.getURL('/api/resource', frappe.slug(doctype), name);
|
||||
|
||||
let response = await frappe.fetch(url, {
|
||||
return await this.fetch(url, {
|
||||
method: 'DELETE',
|
||||
headers: this.json_headers
|
||||
});
|
||||
}
|
||||
|
||||
async deleteMany(doctype, names) {
|
||||
let url = this.getURL('/api/resource', frappe.slug(doctype));
|
||||
|
||||
return await this.fetch(url, {
|
||||
method: 'DELETE',
|
||||
body: JSON.stringify(names)
|
||||
});
|
||||
}
|
||||
|
||||
async exists(doctype, name) {
|
||||
return (await this.getValue(doctype, name, 'name')) ? true : false;
|
||||
}
|
||||
|
||||
async getValue(doctype, name, fieldname) {
|
||||
let url = this.getURL('/api/resource', frappe.slug(doctype), name, fieldname);
|
||||
|
||||
return (await this.fetch(url, {
|
||||
method: 'GET',
|
||||
})).value;
|
||||
}
|
||||
|
||||
async fetch(url, args) {
|
||||
args.headers = this.getHeaders();
|
||||
let response = await frappe.fetch(url, args);
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
get_query_string(params) {
|
||||
getURL(...parts) {
|
||||
return this.protocol + '://' + path.join(this.server, ...parts);
|
||||
}
|
||||
|
||||
getQueryString(params) {
|
||||
return Object.keys(params)
|
||||
.map(k => params[k] != null ? encodeURIComponent(k) + '=' + encodeURIComponent(params[k]) : null)
|
||||
.filter(v => v)
|
||||
.join('&');
|
||||
}
|
||||
|
||||
async get_value(doctype, name, fieldname) {
|
||||
let url = this.protocol + '://' + path.join(this.server, `/api/resource/${frappe.slug(doctype)}/${name}/${fieldname}`);
|
||||
let response = await frappe.fetch(url, {
|
||||
method: 'GET',
|
||||
headers: this.json_headers
|
||||
});
|
||||
return (await response.json()).value;
|
||||
getHeaders() {
|
||||
return {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
|
||||
initTypeMap() {
|
||||
this.type_map = {
|
||||
'Currency': true
|
||||
,'Int': true
|
||||
,'Float': true
|
||||
,'Percent': true
|
||||
,'Check': true
|
||||
,'Small Text': true
|
||||
,'Long Text': true
|
||||
,'Code': true
|
||||
,'Text Editor': true
|
||||
,'Date': true
|
||||
,'Datetime': true
|
||||
,'Time': true
|
||||
,'Text': true
|
||||
,'Data': true
|
||||
,'Link': true
|
||||
,'Dynamic Link':true
|
||||
,'Password': true
|
||||
,'Select': true
|
||||
,'Read Only': true
|
||||
,'Attach': true
|
||||
,'Attach Image':true
|
||||
,'Signature': true
|
||||
,'Color': true
|
||||
,'Barcode': true
|
||||
,'Geolocation': true
|
||||
'Currency': true
|
||||
, 'Int': true
|
||||
, 'Float': true
|
||||
, 'Percent': true
|
||||
, 'Check': true
|
||||
, 'Small Text': true
|
||||
, 'Long Text': true
|
||||
, 'Code': true
|
||||
, 'Text Editor': true
|
||||
, 'Date': true
|
||||
, 'Datetime': true
|
||||
, 'Time': true
|
||||
, 'Text': true
|
||||
, 'Data': true
|
||||
, 'Link': true
|
||||
, 'Dynamic Link': true
|
||||
, 'Password': true
|
||||
, 'Select': true
|
||||
, 'Read Only': true
|
||||
, 'Attach': true
|
||||
, 'Attach Image': true
|
||||
, 'Signature': true
|
||||
, 'Color': true
|
||||
, 'Barcode': true
|
||||
, 'Geolocation': true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
const Page = require('frappejs/client/view/page');
|
||||
const view = require('frappejs/client/view');
|
||||
const frappejs = require('frappejs');
|
||||
const frappe = require('frappejs');
|
||||
|
||||
module.exports = class FormPage extends Page {
|
||||
constructor(doctype) {
|
||||
@ -10,7 +10,8 @@ module.exports = class FormPage extends Page {
|
||||
|
||||
this.form = new (view.get_form_class(doctype))({
|
||||
doctype: doctype,
|
||||
parent: this.body
|
||||
parent: this.body,
|
||||
page: this
|
||||
});
|
||||
|
||||
this.on('show', async (params) => {
|
||||
@ -21,13 +22,13 @@ module.exports = class FormPage extends Page {
|
||||
this.form.on('submit', async (params) => {
|
||||
let route = frappe.router.get_route();
|
||||
if (this.form.doc.name && !(route && route[2] === this.form.doc.name)) {
|
||||
await frappe.router.set_route('edit', this.form.doc.doctype, this.form.doc.name);
|
||||
this.form.show_alert('Added', 'success');
|
||||
await frappe.router.setRoute('edit', this.form.doc.doctype, this.form.doc.name);
|
||||
this.form.showAlert('Added', 'success');
|
||||
}
|
||||
});
|
||||
|
||||
this.form.on('delete', async (params) => {
|
||||
await frappe.router.set_route('list', this.form.doctype);
|
||||
await frappe.router.setRoute('list', this.form.doctype);
|
||||
});
|
||||
}
|
||||
|
||||
@ -36,7 +37,7 @@ module.exports = class FormPage extends Page {
|
||||
this.doc = await frappe.get_doc(doctype, name);
|
||||
this.form.use(this.doc);
|
||||
} catch (e) {
|
||||
this.render_error(e.status_code, e.message);
|
||||
this.renderError(e.status_code, e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
const frappe = require('frappejs');
|
||||
const Search = require('./search');
|
||||
// const Search = require('./search');
|
||||
const Router = require('frappejs/common/router');
|
||||
const Page = require('frappejs/client/view/page');
|
||||
const FormPage = require('frappejs/client/desk/formpage');
|
||||
@ -16,7 +16,7 @@ module.exports = class Desk {
|
||||
this.container = frappe.ui.add('div', 'container-fluid', body);
|
||||
|
||||
this.container_row = frappe.ui.add('div', 'row', this.container)
|
||||
this.sidebar = frappe.ui.add('div', 'col-md-2 p-3 sidebar', this.container_row);
|
||||
this.sidebar = frappe.ui.add('div', 'col-md-2 p-3 sidebar d-none d-md-block', this.container_row);
|
||||
this.body = frappe.ui.add('div', 'col-md-10 p-3 main', this.container_row);
|
||||
|
||||
this.sidebar_items = [];
|
||||
@ -35,11 +35,11 @@ module.exports = class Desk {
|
||||
this.not_found_page = new Page('Not Found');
|
||||
}
|
||||
await this.not_found_page.show();
|
||||
this.not_found_page.render_error('Not Found', params ? params.route : '');
|
||||
this.not_found_page.renderError('Not Found', params ? params.route : '');
|
||||
})
|
||||
|
||||
frappe.router.add('list/:doctype', async (params) => {
|
||||
let page = this.get_list_page(params.doctype);
|
||||
let page = this.getList_page(params.doctype);
|
||||
await page.show(params);
|
||||
});
|
||||
|
||||
@ -51,13 +51,13 @@ module.exports = class Desk {
|
||||
frappe.router.add('new/:doctype', async (params) => {
|
||||
let doc = await frappe.get_new_doc(params.doctype);
|
||||
// unset the name, its local
|
||||
await frappe.router.set_route('edit', doc.doctype, doc.name);
|
||||
await frappe.router.setRoute('edit', doc.doctype, doc.name);
|
||||
await doc.set('name', '');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
get_list_page(doctype) {
|
||||
getList_page(doctype) {
|
||||
if (!this.pages.lists[doctype]) {
|
||||
this.pages.lists[doctype] = new ListPage(doctype);
|
||||
}
|
||||
|
@ -1,13 +1,15 @@
|
||||
const frappe = require('frappejs');
|
||||
const Page = require('frappejs/client/view/page');
|
||||
const view = require('frappejs/client/view');
|
||||
|
||||
module.exports = class FormPage extends Page {
|
||||
module.exports = class ListPage extends Page {
|
||||
constructor(doctype) {
|
||||
let meta = frappe.getMeta(doctype);
|
||||
super(`List ${meta.name}`);
|
||||
this.list = new (view.get_list_class(doctype))({
|
||||
super(frappe._("List: {0}", meta.name));
|
||||
this.list = new (view.getList_class(doctype))({
|
||||
doctype: doctype,
|
||||
parent: this.body
|
||||
parent: this.body,
|
||||
page: this
|
||||
});
|
||||
this.on('show', async () => {
|
||||
await this.list.run();
|
||||
|
@ -1,97 +0,0 @@
|
||||
@import "node_modules/bootstrap/scss/bootstrap";
|
||||
@import "node_modules/awesomplete/awesomplete";
|
||||
|
||||
$spacer-1: 0.25rem;
|
||||
$spacer-2: 0.5rem;
|
||||
$spacer-3: 1rem;
|
||||
$spacer-4: 2rem;
|
||||
|
||||
html {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.page-error {
|
||||
text-align: center;
|
||||
padding: 200px 0px;
|
||||
}
|
||||
|
||||
.form-body {
|
||||
max-width: 600px;
|
||||
|
||||
.form-toolbar {
|
||||
height: $spacer-4;
|
||||
margin-bottom: $spacer-3;
|
||||
}
|
||||
|
||||
.alert {
|
||||
margin-top: $spacer-3;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu-right {
|
||||
right: 0;
|
||||
left: auto;
|
||||
}
|
||||
|
||||
.awesomplete {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.awesomplete > ul > li {
|
||||
padding: .75rem .375rem;
|
||||
}
|
||||
|
||||
.awesomplete > ul > li:hover {
|
||||
background: $gray-300;
|
||||
color: $body-color;
|
||||
}
|
||||
|
||||
.awesomplete > ul > li[aria-selected="true"] {
|
||||
background: $gray-300;
|
||||
color: $body-color;
|
||||
}
|
||||
|
||||
.awesomplete > ul > li[aria-selected="true"]:hover {
|
||||
background: $gray-300;
|
||||
color: $body-color;
|
||||
}
|
||||
|
||||
mark {
|
||||
padding: none;
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
.awesomplete li[aria-selected="true"] mark, .awesomplete li[aria-selected="false"] mark {
|
||||
background: inherit;
|
||||
color: inherit;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
margin-bottom: $spacer-2;
|
||||
}
|
||||
|
||||
.table-toolbar {
|
||||
margin-top: $spacer-2;
|
||||
}
|
||||
|
||||
.data-table-col .edit-cell {
|
||||
padding: 0px !important;
|
||||
|
||||
input, textarea {
|
||||
border-radius: none;
|
||||
margin: none;
|
||||
padding: $spacer-1;
|
||||
}
|
||||
|
||||
.awesomplete > ul {
|
||||
position: fixed;
|
||||
left: auto;
|
||||
width: auto;
|
||||
min-width: 120px;
|
||||
}
|
||||
}
|
@ -70,7 +70,7 @@ class Dropdown {
|
||||
if (typeof action === 'string') {
|
||||
item.src = action;
|
||||
item.addEventListener('click', async () => {
|
||||
await frappe.router.set_route(action);
|
||||
await frappe.router.setRoute(action);
|
||||
this.toggle();
|
||||
});
|
||||
} else {
|
||||
|
@ -19,17 +19,17 @@ class BaseControl {
|
||||
|
||||
bind(doc) {
|
||||
this.doc = doc;
|
||||
this.set_doc_value();
|
||||
this.setDocValue();
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.make();
|
||||
this.set_doc_value();
|
||||
this.setDocValue();
|
||||
}
|
||||
|
||||
set_doc_value() {
|
||||
setDocValue() {
|
||||
if (this.doc) {
|
||||
this.set_input_value(this.doc.get(this.fieldname));
|
||||
this.setInputValue(this.doc.get(this.fieldname));
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,7 +77,7 @@ class BaseControl {
|
||||
}
|
||||
}
|
||||
|
||||
set_input_value(value) {
|
||||
setInputValue(value) {
|
||||
this.input.value = this.format(value);
|
||||
}
|
||||
|
||||
@ -92,7 +92,7 @@ class BaseControl {
|
||||
return await this.parse(this.input.value);
|
||||
}
|
||||
|
||||
get_input_value() {
|
||||
getInputValue() {
|
||||
return this.input.value;
|
||||
}
|
||||
|
||||
@ -109,14 +109,14 @@ class BaseControl {
|
||||
}
|
||||
|
||||
async handle_change(e) {
|
||||
let value = await this.parse(this.get_input_value());
|
||||
let value = await this.parse(this.getInputValue());
|
||||
value = await this.validate(value);
|
||||
if (this.doc[this.fieldname] !== value) {
|
||||
if (this.doc.set) {
|
||||
await this.doc.set(this.fieldname, value);
|
||||
}
|
||||
if (this.parent_control) {
|
||||
await this.parent_control.doc.set(this.fieldname, this.parent_control.get_input_value());
|
||||
await this.parent_control.doc.set(this.fieldname, this.parent_control.getInputValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,11 +10,11 @@ const control_classes = {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
get_control_class(fieldtype) {
|
||||
getControlClass(fieldtype) {
|
||||
return control_classes[fieldtype];
|
||||
},
|
||||
make_control({field, form, parent}) {
|
||||
const control_class = this.get_control_class(field.fieldtype);
|
||||
makeControl({field, form, parent}) {
|
||||
const control_class = this.getControlClass(field.fieldtype);
|
||||
let control = new control_class({field:field, form:form, parent:parent});
|
||||
control.make();
|
||||
return control;
|
||||
|
@ -14,19 +14,19 @@ class LinkControl extends BaseControl {
|
||||
|
||||
// rebuild the list on input
|
||||
this.input.addEventListener('input', async (event) => {
|
||||
this.awesomplete.list = await this.get_list(this.input.value);
|
||||
this.awesomplete.list = await this.getList(this.input.value);
|
||||
});
|
||||
}
|
||||
|
||||
async get_list(query) {
|
||||
async getList(query) {
|
||||
return (await frappe.db.getAll({
|
||||
doctype: this.target,
|
||||
filters: this.get_filters(query),
|
||||
filters: this.getFilters(query),
|
||||
limit: 50
|
||||
})).map(d => d.name);
|
||||
}
|
||||
|
||||
get_filters(query) {
|
||||
getFilters(query) {
|
||||
return { keywords: ["like", query] }
|
||||
}
|
||||
};
|
||||
|
@ -40,11 +40,11 @@ class TableControl extends BaseControl {
|
||||
}
|
||||
}
|
||||
|
||||
get_input_value() {
|
||||
getInputValue() {
|
||||
return this.doc[this.fieldname];
|
||||
}
|
||||
|
||||
set_input_value(value) {
|
||||
setInputValue(value) {
|
||||
this.datatable.refresh(this.get_table_data(value));
|
||||
}
|
||||
|
||||
@ -64,20 +64,20 @@ class TableControl extends BaseControl {
|
||||
|
||||
get_control(field, parent) {
|
||||
field.only_input = true;
|
||||
const control = controls.make_control({field: field, parent: parent});
|
||||
const control = controls.makeControl({field: field, parent: parent});
|
||||
|
||||
return {
|
||||
initValue: (value, rowIndex, column) => {
|
||||
control.parent_control = this;
|
||||
control.doc = this.doc[this.fieldname][rowIndex];
|
||||
control.set_focus();
|
||||
return control.set_input_value(value);
|
||||
return control.setInputValue(value);
|
||||
},
|
||||
setValue: (value, rowIndex, column) => {
|
||||
return control.set_input_value(value);
|
||||
return control.setInputValue(value);
|
||||
},
|
||||
getValue: () => {
|
||||
return control.get_input_value();
|
||||
return control.getInputValue();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,14 +3,11 @@ const controls = require('./controls');
|
||||
const Observable = require('frappejs/utils/observable');
|
||||
|
||||
module.exports = class BaseForm extends Observable {
|
||||
constructor({doctype, parent, submit_label='Submit'}) {
|
||||
constructor({doctype, parent, submit_label='Submit', page}) {
|
||||
super();
|
||||
this.parent = parent;
|
||||
this.doctype = doctype;
|
||||
this.submit_label = submit_label;
|
||||
|
||||
Object.assign(this, arguments[0]);
|
||||
this.controls = {};
|
||||
this.controls_list = [];
|
||||
this.controlList = [];
|
||||
|
||||
this.meta = frappe.getMeta(this.doctype);
|
||||
if (this.setup) {
|
||||
@ -25,35 +22,26 @@ module.exports = class BaseForm extends Observable {
|
||||
}
|
||||
|
||||
this.body = frappe.ui.add('div', 'form-body', this.parent);
|
||||
this.make_toolbar();
|
||||
this.makeToolbar();
|
||||
|
||||
this.form = frappe.ui.add('div', 'form-container', this.body);
|
||||
for(let field of this.meta.fields) {
|
||||
if (controls.get_control_class(field.fieldtype)) {
|
||||
let control = controls.make_control({field: field, form: this});
|
||||
this.controls_list.push(control);
|
||||
if (controls.getControlClass(field.fieldtype)) {
|
||||
let control = controls.makeControl({field: field, form: this});
|
||||
this.controlList.push(control);
|
||||
this.controls[field.fieldname] = control;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
make_toolbar() {
|
||||
this.toolbar = frappe.ui.add('div', 'form-toolbar text-right', this.body);
|
||||
this.toolbar.innerHTML = `
|
||||
<button class="btn btn-outline-secondary btn-delete">Delete</button>
|
||||
<button class="btn btn-primary btn-submit">Save</button>
|
||||
`
|
||||
|
||||
this.btn_submit = this.toolbar.querySelector('.btn-submit');;
|
||||
this.btn_submit.addEventListener('click', async (event) => {
|
||||
this.submit();
|
||||
event.preventDefault();
|
||||
makeToolbar() {
|
||||
this.btnSubmit = this.page.addButton(frappe._("Save"), 'btn-primary', async () => {
|
||||
await this.submit();
|
||||
})
|
||||
|
||||
this.btn_delete = this.toolbar.querySelector('.btn-delete');
|
||||
this.btn_delete.addEventListener('click', async () => {
|
||||
this.btnDelete = this.page.addButton(frappe._("Delete"), 'btn-outline-secondary', async () => {
|
||||
await this.doc.delete();
|
||||
this.show_alert('Deleted', 'success');
|
||||
this.showAlert('Deleted', 'success');
|
||||
this.trigger('delete');
|
||||
});
|
||||
}
|
||||
@ -61,20 +49,20 @@ module.exports = class BaseForm extends Observable {
|
||||
async use(doc, is_new = false) {
|
||||
if (this.doc) {
|
||||
// clear handlers of outgoing doc
|
||||
this.doc.clear_handlers();
|
||||
this.doc.clearHandlers();
|
||||
}
|
||||
this.clear_alert();
|
||||
this.clearAlert();
|
||||
this.doc = doc;
|
||||
this.is_new = is_new;
|
||||
for (let control of this.controls_list) {
|
||||
for (let control of this.controlList) {
|
||||
control.bind(this.doc);
|
||||
}
|
||||
|
||||
// refresh value in control
|
||||
this.doc.add_handler('change', (params) => {
|
||||
this.doc.addHandler('change', (params) => {
|
||||
let control = this.controls[params.fieldname];
|
||||
if (control && control.get_input_value() !== control.format(params.fieldname)) {
|
||||
control.set_doc_value();
|
||||
if (control && control.getInputValue() !== control.format(params.fieldname)) {
|
||||
control.setDocValue();
|
||||
}
|
||||
});
|
||||
|
||||
@ -89,29 +77,29 @@ module.exports = class BaseForm extends Observable {
|
||||
await this.doc.update();
|
||||
}
|
||||
await this.refresh();
|
||||
this.show_alert('Saved', 'success');
|
||||
this.showAlert('Saved', 'success');
|
||||
} catch (e) {
|
||||
this.show_alert('Failed', 'danger');
|
||||
this.showAlert('Failed', 'danger');
|
||||
}
|
||||
await this.trigger('submit');
|
||||
}
|
||||
|
||||
refresh() {
|
||||
for(let control of this.controls_list) {
|
||||
for(let control of this.controlList) {
|
||||
control.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
show_alert(message, type, clear_after = 5) {
|
||||
this.clear_alert();
|
||||
showAlert(message, type, clear_after = 5) {
|
||||
this.clearAlert();
|
||||
this.alert = frappe.ui.add('div', `alert alert-${type}`, this.body);
|
||||
this.alert.textContent = message;
|
||||
setTimeout(() => {
|
||||
this.clear_alert();
|
||||
this.clearAlert();
|
||||
}, clear_after * 1000);
|
||||
}
|
||||
|
||||
clear_alert() {
|
||||
clearAlert() {
|
||||
if (this.alert) {
|
||||
frappe.ui.remove(this.alert);
|
||||
this.alert = null;
|
||||
|
@ -5,7 +5,7 @@ module.exports = {
|
||||
get_form_class(doctype) {
|
||||
return this.get_view_class(doctype, 'Form', BaseForm);
|
||||
},
|
||||
get_list_class(doctype) {
|
||||
getList_class(doctype) {
|
||||
return this.get_view_class(doctype, 'List', BaseList);
|
||||
},
|
||||
get_view_class(doctype, class_name, default_class) {
|
||||
|
@ -1,28 +1,30 @@
|
||||
const frappe = require('frappejs');
|
||||
|
||||
module.exports = class BaseList {
|
||||
constructor({doctype, parent, fields}) {
|
||||
this.doctype = doctype;
|
||||
this.parent = parent;
|
||||
this.fields = fields;
|
||||
constructor({doctype, parent, fields, page}) {
|
||||
Object.assign(this, arguments[0]);
|
||||
|
||||
this.meta = frappe.getMeta(this.doctype);
|
||||
|
||||
this.start = 0;
|
||||
this.page_length = 20;
|
||||
this.pageLength = 20;
|
||||
|
||||
this.body = null;
|
||||
this.rows = [];
|
||||
this.data = [];
|
||||
}
|
||||
|
||||
async refresh() {
|
||||
return await this.run();
|
||||
}
|
||||
|
||||
async run() {
|
||||
this.make_body();
|
||||
this.makeBody();
|
||||
|
||||
let data = await this.get_data();
|
||||
let data = await this.getData();
|
||||
|
||||
for (let i=0; i< Math.min(this.page_length, data.length); i++) {
|
||||
this.render_row(this.start + i, data[i]);
|
||||
for (let i=0; i< Math.min(this.pageLength, data.length); i++) {
|
||||
this.renderRow(this.start + i, data[i]);
|
||||
}
|
||||
|
||||
if (this.start > 0) {
|
||||
@ -31,47 +33,62 @@ module.exports = class BaseList {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
this.clear_empty_rows();
|
||||
this.update_more(data.length > this.page_length);
|
||||
this.clearEmptyRows();
|
||||
this.updateMore(data.length > this.pageLength);
|
||||
}
|
||||
|
||||
async get_data() {
|
||||
async getData() {
|
||||
return await frappe.db.getAll({
|
||||
doctype: this.doctype,
|
||||
fields: this.get_fields(),
|
||||
filters: this.get_filters(),
|
||||
fields: this.getFields(),
|
||||
filters: this.getFilters(),
|
||||
start: this.start,
|
||||
limit: this.page_length + 1
|
||||
limit: this.pageLength + 1
|
||||
});
|
||||
}
|
||||
|
||||
get_fields() {
|
||||
getFields() {
|
||||
return ['name'];
|
||||
}
|
||||
|
||||
async append() {
|
||||
this.start += this.page_length;
|
||||
this.start += this.pageLength;
|
||||
await this.run();
|
||||
}
|
||||
|
||||
get_filters() {
|
||||
getFilters() {
|
||||
let filters = {};
|
||||
if (this.search_input.value) {
|
||||
filters.keywords = ['like', '%' + this.search_input.value + '%'];
|
||||
if (this.searchInput.value) {
|
||||
filters.keywords = ['like', '%' + this.searchInput.value + '%'];
|
||||
}
|
||||
return filters;
|
||||
}
|
||||
|
||||
make_body() {
|
||||
makeBody() {
|
||||
if (!this.body) {
|
||||
this.make_toolbar();
|
||||
//this.make_new();
|
||||
this.makeToolbar();
|
||||
this.body = frappe.ui.add('div', 'list-body', this.parent);
|
||||
this.make_more_btn();
|
||||
this.makeMoreBtn();
|
||||
}
|
||||
}
|
||||
|
||||
make_toolbar() {
|
||||
makeToolbar() {
|
||||
this.makeSearch();
|
||||
this.btnNew = this.page.addButton(frappe._('New'), 'btn-outline-primary', async () => {
|
||||
await frappe.router.setRoute('new', frappe.slug(this.doctype));
|
||||
})
|
||||
this.btnDelete = this.page.addButton(frappe._('Delete'), 'btn-outline-secondary hide', async () => {
|
||||
await frappe.db.deleteMany(this.doctype, this.getCheckedRowNames());
|
||||
await this.refresh();
|
||||
});
|
||||
this.page.body.addEventListener('click', (event) => {
|
||||
if(event.target.classList.contains('checkbox')) {
|
||||
this.btnDelete.classList.toggle('hide', this.getCheckedRowNames().length===0);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
makeSearch() {
|
||||
this.toolbar = frappe.ui.add('div', 'list-toolbar', this.parent);
|
||||
this.toolbar.innerHTML = `
|
||||
<div class="row">
|
||||
@ -83,67 +100,70 @@ module.exports = class BaseList {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 col-3">
|
||||
<a href="#new/${frappe.slug(this.doctype)}" class="btn btn-outline-primary">
|
||||
New
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.search_input = this.toolbar.querySelector('input');
|
||||
this.search_input.addEventListener('keypress', (event) => {
|
||||
this.searchInput = this.toolbar.querySelector('input');
|
||||
this.searchInput.addEventListener('keypress', (event) => {
|
||||
if (event.keyCode===13) {
|
||||
this.run();
|
||||
this.refresh();
|
||||
}
|
||||
});
|
||||
|
||||
this.search_button = this.toolbar.querySelector('.btn-search');
|
||||
this.search_button.addEventListener('click', (event) => {
|
||||
this.run();
|
||||
this.btnSearch = this.toolbar.querySelector('.btn-search');
|
||||
this.btnSearch.addEventListener('click', (event) => {
|
||||
this.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
make_more_btn() {
|
||||
this.more_btn = frappe.ui.add('button', 'btn btn-secondary hide', this.parent);
|
||||
this.more_btn.textContent = 'More';
|
||||
this.more_btn.addEventListener('click', () => {
|
||||
makeMoreBtn() {
|
||||
this.btnMore = frappe.ui.add('button', 'btn btn-secondary hide', this.parent);
|
||||
this.btnMore.textContent = 'More';
|
||||
this.btnMore.addEventListener('click', () => {
|
||||
this.append();
|
||||
})
|
||||
}
|
||||
|
||||
render_row(i, data) {
|
||||
let row = this.get_row(i);
|
||||
row.innerHTML = this.get_row_html(data);
|
||||
renderRow(i, data) {
|
||||
let row = this.getRow(i);
|
||||
row.innerHTML = this.getRowBodyHTML(data);
|
||||
row.style.display = 'block';
|
||||
}
|
||||
|
||||
get_row_html(data) {
|
||||
getRowBodyHTML(data) {
|
||||
return `<input class="checkbox" type="checkbox" data-name="${data.name}"> ` + this.getRowHTML(data);
|
||||
}
|
||||
|
||||
getRowHTML(data) {
|
||||
return `<a href="#edit/${this.doctype}/${data.name}">${data.name}</a>`;
|
||||
}
|
||||
|
||||
get_row(i) {
|
||||
getRow(i) {
|
||||
if (!this.rows[i]) {
|
||||
this.rows[i] = frappe.ui.add('div', 'list-row py-2', this.body);
|
||||
}
|
||||
return this.rows[i];
|
||||
}
|
||||
|
||||
clear_empty_rows() {
|
||||
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.get_row(i);
|
||||
let row = this.getRow(i);
|
||||
row.innerHTML = '';
|
||||
row.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update_more(show) {
|
||||
updateMore(show) {
|
||||
if (show) {
|
||||
this.more_btn.classList.remove('hide');
|
||||
this.btnMore.classList.remove('hide');
|
||||
} else {
|
||||
this.more_btn.classList.add('hide');
|
||||
this.btnMore.classList.add('hide');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,10 @@ module.exports = class Page extends Observable {
|
||||
|
||||
make() {
|
||||
this.wrapper = frappe.ui.add('div', 'page hide', frappe.desk.body);
|
||||
this.body = frappe.ui.add('div', 'page-body', this.wrapper);
|
||||
this.wrapper.innerHTML = `<div class="page-head hide"></div>
|
||||
<div class="page-body"></div>`
|
||||
this.head = this.wrapper.querySelector('.page-head');
|
||||
this.body = this.wrapper.querySelector('.page-body');
|
||||
}
|
||||
|
||||
hide() {
|
||||
@ -18,6 +21,14 @@ module.exports = class Page extends Observable {
|
||||
this.trigger('hide');
|
||||
}
|
||||
|
||||
addButton(label, cssClass, action) {
|
||||
this.head.classList.remove('hide');
|
||||
this.button = frappe.ui.add('button', 'btn btn-sm ' + cssClass, this.head);
|
||||
this.button.innerHTML = label;
|
||||
this.button.addEventListener('click', action);
|
||||
return this.button;
|
||||
}
|
||||
|
||||
async show(params) {
|
||||
if (frappe.router.current_page) {
|
||||
frappe.router.current_page.hide();
|
||||
@ -35,7 +46,7 @@ module.exports = class Page extends Observable {
|
||||
await this.trigger('show', params);
|
||||
}
|
||||
|
||||
render_error(title, message) {
|
||||
renderError(title, message) {
|
||||
if (!this.page_error) {
|
||||
this.page_error = frappe.ui.add('div', 'page-error', this.wrapper);
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ module.exports = class Router {
|
||||
}
|
||||
}
|
||||
|
||||
async set_route(...parts) {
|
||||
async setRoute(...parts) {
|
||||
const route = parts.join('/');
|
||||
|
||||
// setting this first, does not trigger show via hashchange,
|
||||
|
@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
input: './node_modules/frappejs/client/style/index.scss',
|
||||
input: './node_modules/frappejs/client/style/style.scss',
|
||||
output: {
|
||||
file: './dist/css/style.css',
|
||||
format: 'cjs'
|
||||
|
@ -11,11 +11,11 @@ module.exports = class BaseDocument {
|
||||
// add handlers
|
||||
}
|
||||
|
||||
clear_handlers() {
|
||||
clearHandlers() {
|
||||
this.handlers = {};
|
||||
}
|
||||
|
||||
add_handler(key, method) {
|
||||
addHandler(key, method) {
|
||||
if (!this.handlers[key]) {
|
||||
this.handlers[key] = [];
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ class ToDoMeta extends BaseMeta {
|
||||
|
||||
class ToDo extends BaseDocument {
|
||||
setup() {
|
||||
this.add_handler('validate');
|
||||
this.addHandler('validate');
|
||||
}
|
||||
validate() {
|
||||
if (!this.status) {
|
||||
|
@ -1,10 +1,10 @@
|
||||
const BaseList = require('frappejs/client/view/list');
|
||||
|
||||
class ToDoList extends BaseList {
|
||||
get_fields() {
|
||||
getFields() {
|
||||
return ['name', 'subject', 'status'];
|
||||
}
|
||||
get_row_html(data) {
|
||||
getRowHTML(data) {
|
||||
let symbol = data.status=="Closed" ? "✔" : "";
|
||||
return `<a href="#edit/todo/${data.name}">${symbol} ${data.subject}</a>`;
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ module.exports = {
|
||||
|
||||
// create
|
||||
app.post('/api/resource/:doctype', frappe.async_handler(async function(request, response) {
|
||||
data = request.body;
|
||||
let data = request.body;
|
||||
data.doctype = request.params.doctype;
|
||||
let doc = frappe.new_doc(data);
|
||||
await doc.insert();
|
||||
@ -35,7 +35,7 @@ module.exports = {
|
||||
|
||||
// update
|
||||
app.put('/api/resource/:doctype/:name', frappe.async_handler(async function(request, response) {
|
||||
data = request.body;
|
||||
let data = request.body;
|
||||
let doc = await frappe.get_doc(request.params.doctype, request.params.name);
|
||||
Object.assign(doc, data);
|
||||
await doc.update();
|
||||
@ -52,7 +52,7 @@ module.exports = {
|
||||
|
||||
// get value
|
||||
app.get('/api/resource/:doctype/:name/:fieldname', frappe.async_handler(async function(request, response) {
|
||||
let value = await frappe.db.get_value(request.params.doctype, request.params.name, request.params.fieldname);
|
||||
let value = await frappe.db.getValue(request.params.doctype, request.params.name, request.params.fieldname);
|
||||
return response.json({value: value});
|
||||
}));
|
||||
|
||||
@ -62,5 +62,16 @@ module.exports = {
|
||||
await doc.delete();
|
||||
return response.json({});
|
||||
}));
|
||||
|
||||
// delete many
|
||||
app.delete('/api/resource/:doctype', frappe.async_handler(async function(request, response) {
|
||||
let names = request.body;
|
||||
for (let name of names) {
|
||||
let doc = await frappe.get_doc(request.params.doctype, name);
|
||||
await doc.delete();
|
||||
}
|
||||
return response.json({});
|
||||
}));
|
||||
|
||||
}
|
||||
};
|
||||
|
@ -27,12 +27,12 @@ describe('Document', () => {
|
||||
let doc = test_doc();
|
||||
await doc.insert();
|
||||
|
||||
assert.notEqual(await frappe.db.get_value(doc.doctype, doc.name, 'subject'), 'subject 2');
|
||||
assert.notEqual(await frappe.db.getValue(doc.doctype, doc.name, 'subject'), 'subject 2');
|
||||
|
||||
doc.subject = 'subject 2'
|
||||
await doc.update();
|
||||
|
||||
assert.equal(await frappe.db.get_value(doc.doctype, doc.name, 'subject'), 'subject 2');
|
||||
assert.equal(await frappe.db.getValue(doc.doctype, doc.name, 'subject'), 'subject 2');
|
||||
})
|
||||
|
||||
it('should get a value', async () => {
|
||||
@ -60,11 +60,11 @@ describe('Document', () => {
|
||||
let doc = test_doc();
|
||||
await doc.insert();
|
||||
|
||||
assert.equal(await frappe.db.get_value(doc.doctype, doc.name), doc.name);
|
||||
assert.equal(await frappe.db.getValue(doc.doctype, doc.name), doc.name);
|
||||
|
||||
await doc.delete();
|
||||
|
||||
assert.equal(await frappe.db.get_value(doc.doctype, doc.name), null);
|
||||
assert.equal(await frappe.db.getValue(doc.doctype, doc.name), null);
|
||||
});
|
||||
|
||||
it('should add, fetch and delete documents with children', async() => {
|
||||
|
@ -62,5 +62,28 @@ describe('REST', () => {
|
||||
assert.ok(subjects.includes('all test 2'));
|
||||
});
|
||||
|
||||
it('should delete a document', async () => {
|
||||
let doc = frappe.new_doc({doctype:'ToDo', subject:'test rest insert 1'});
|
||||
|
||||
await doc.insert();
|
||||
assert.equal(await frappe.db.exists(doc.doctype, doc.name), true);
|
||||
|
||||
await doc.delete();
|
||||
assert.equal(await frappe.db.exists(doc.doctype, doc.name), false);
|
||||
});
|
||||
|
||||
it('should delete multiple documents', async () => {
|
||||
let doc1 = frappe.new_doc({doctype:'ToDo', subject:'test rest insert 5'});
|
||||
let doc2 = frappe.new_doc({doctype:'ToDo', subject:'test rest insert 6'});
|
||||
|
||||
await doc1.insert();
|
||||
await doc2.insert();
|
||||
assert.equal(await frappe.db.exists(doc1.doctype, doc1.name), true);
|
||||
assert.equal(await frappe.db.exists(doc2.doctype, doc2.name), true);
|
||||
|
||||
await frappe.db.deleteMany(doc1.doctype, [doc1.name, doc2.name]);
|
||||
assert.equal(await frappe.db.exists(doc1.doctype, doc1.name), false);
|
||||
assert.equal(await frappe.db.exists(doc2.doctype, doc2.name), false);
|
||||
});
|
||||
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user