mirror of
https://github.com/frappe/books.git
synced 2024-12-23 03:19:01 +00:00
added modal
This commit is contained in:
parent
78799cb8d9
commit
2a2f1fb6f3
@ -1,6 +1,6 @@
|
||||
const frappe = require('frappejs');
|
||||
const sqlite3 = require('sqlite3').verbose();
|
||||
const debug = true;
|
||||
const debug = false;
|
||||
|
||||
class sqliteDatabase {
|
||||
constructor({ db_path }) {
|
||||
@ -164,10 +164,11 @@ class sqliteDatabase {
|
||||
|
||||
// delete other children
|
||||
// `delete from doctype where parent = ? and name not in (?, ?, ?)}`
|
||||
|
||||
await this.run(`delete from ${frappe.slug(field.childtype)}
|
||||
where
|
||||
parent = ? and
|
||||
name not in (${added_children.map(d => '?').join(', ')})`, added_children);
|
||||
name not in (${added_children.slice(1).map(d => '?').join(', ')})`, added_children);
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
@ -185,6 +186,9 @@ class sqliteDatabase {
|
||||
}
|
||||
|
||||
prepare_child(parenttype, parent, child, field, idx) {
|
||||
if (!child.name) {
|
||||
child.name = frappe.get_random_name();
|
||||
}
|
||||
child.parent = parent;
|
||||
child.parenttype = parenttype;
|
||||
child.parentfield = field.fieldname;
|
||||
|
@ -26,7 +26,6 @@ module.exports = class Desk {
|
||||
};
|
||||
|
||||
this.init_routes();
|
||||
|
||||
// this.search = new Search(this.nav);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,11 @@
|
||||
@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;
|
||||
}
|
||||
@ -18,12 +23,12 @@ html {
|
||||
max-width: 600px;
|
||||
|
||||
.form-toolbar {
|
||||
height: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
height: $spacer-4;
|
||||
margin-bottom: $spacer-3;
|
||||
}
|
||||
|
||||
.alert {
|
||||
margin-top: 1rem;
|
||||
margin-top: $spacer-3;
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,13 +71,21 @@ mark {
|
||||
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: 0.25rem;
|
||||
padding: $spacer-1;
|
||||
}
|
||||
|
||||
.awesomplete > ul {
|
||||
|
59
client/ui/modal.js
Normal file
59
client/ui/modal.js
Normal file
@ -0,0 +1,59 @@
|
||||
const $ = require('jquery');
|
||||
const bootstrap = require('bootstrap');
|
||||
|
||||
module.exports = class Modal {
|
||||
constructor({ title, body, primary_label, primary_action, secondary_label, secondary_action }) {
|
||||
Object.assign(this, arguments[0]);
|
||||
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">${title}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
${body}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`).appendTo(document.body);
|
||||
|
||||
if (this.primary_label) {
|
||||
this.add_primary(this.primary_label, this.primary_action);
|
||||
}
|
||||
if (this.secondary_label) {
|
||||
this.add_secondary(this.secondary_label, this.secondary_action);
|
||||
}
|
||||
this.show();
|
||||
}
|
||||
|
||||
add_primary(label, action) {
|
||||
this.$primary = $(`<button type="button" class="btn btn-primary">
|
||||
${label}</button>`)
|
||||
.appendTo(this.$modal.find('.modal-footer'))
|
||||
.on('click', () => action(this));
|
||||
}
|
||||
|
||||
add_secondary(label, action) {
|
||||
this.$primary = $(`<button type="button" class="btn btn-secondary">
|
||||
${label}</button>`)
|
||||
.appendTo(this.$modal.find('.modal-footer'))
|
||||
.on('click', () => action(this));
|
||||
}
|
||||
|
||||
show() {
|
||||
this.$modal.modal('show');
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.$modal.modal('hide');
|
||||
}
|
||||
|
||||
get_body() {
|
||||
return this.$modal.find('.modal-body').get(0);
|
||||
}
|
||||
}
|
@ -4,7 +4,12 @@ class FloatControl extends BaseControl {
|
||||
make() {
|
||||
super.make();
|
||||
this.input.setAttribute('type', 'text');
|
||||
this.input.classList.add('text-right');
|
||||
this.input.classList.add('text-right');
|
||||
this.input.addEventListener('focus', () => {
|
||||
setTimeout(() => {
|
||||
this.input.select();
|
||||
}, 100);
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -2,20 +2,41 @@ const frappe = require('frappejs');
|
||||
const BaseControl = require('./base');
|
||||
const DataTable = require('frappe-datatable');
|
||||
const controls = require('./index');
|
||||
const Modal = require('frappejs/client/ui/modal');
|
||||
|
||||
class TableControl extends BaseControl {
|
||||
make() {
|
||||
if (!this.datatable) {
|
||||
this.wrapper = frappe.ui.add('div', 'datatable-wrapper', this.get_input_parent());
|
||||
this.datatable = new DataTable(this.wrapper, {
|
||||
this.wrapper = frappe.ui.add('div', 'table-wrapper', this.get_input_parent());
|
||||
this.wrapper.innerHTML =
|
||||
`<div class="datatable-wrapper"></div>
|
||||
<div class="table-toolbar">
|
||||
<button class="btn btn-sm btn-outline-secondary btn-add">${frappe._("Add")}</button>
|
||||
<button class="btn btn-sm btn-outline-danger btn-remove">${frappe._("Remove")}</button>
|
||||
</div>`;
|
||||
|
||||
this.datatable = new DataTable(this.wrapper.querySelector('.datatable-wrapper'), {
|
||||
columns: this.get_columns(),
|
||||
data: this.get_table_data(),
|
||||
takeAvailableSpace: true,
|
||||
enableClusterize: true,
|
||||
addCheckboxColumn: true,
|
||||
editing: this.get_table_input.bind(this),
|
||||
|
||||
});
|
||||
this.datatable.datatableWrapper.style = 'height: 300px';
|
||||
|
||||
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.datatable.rowmanager.getCheckedRows();
|
||||
this.doc[this.fieldname] = this.doc[this.fieldname].filter(d => !checked.includes(d.idx));
|
||||
await this.doc.commit();
|
||||
this.refresh();
|
||||
this.datatable.rowmanager.checkAll(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,6 +54,15 @@ class TableControl extends BaseControl {
|
||||
|
||||
get_table_input(colIndex, rowIndex, value, parent) {
|
||||
let field = this.datatable.getColumn(colIndex).field;
|
||||
|
||||
if (field.fieldtype==='Text') {
|
||||
return this.get_text_control(field);
|
||||
} else {
|
||||
return this.get_control(field, parent);
|
||||
}
|
||||
}
|
||||
|
||||
get_control(field, parent) {
|
||||
field.only_input = true;
|
||||
const control = controls.make_control({field: field, parent: parent});
|
||||
|
||||
@ -50,6 +80,23 @@ class TableControl extends BaseControl {
|
||||
return control.get_input_value();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
get_text_control(field, parent) {
|
||||
|
||||
this.text_modal = new Modal({
|
||||
title: frappe._('Edit {0}', field.label),
|
||||
body: '',
|
||||
primary_label: frappe._('Submit'),
|
||||
primary_action: (modal) => {
|
||||
this.datatable.cellmanager.submitEditing();
|
||||
this.datatable.cellmanager.deactivateEditing();
|
||||
modal.hide();
|
||||
}
|
||||
});
|
||||
|
||||
return this.get_control(field, this.text_modal.get_body());
|
||||
}
|
||||
|
||||
get_columns() {
|
||||
|
@ -27,7 +27,7 @@ module.exports = class BaseForm extends Observable {
|
||||
this.body = frappe.ui.add('div', 'form-body', this.parent);
|
||||
this.make_toolbar();
|
||||
|
||||
this.form = frappe.ui.add('form', null, this.body);
|
||||
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});
|
||||
|
@ -1,4 +1,5 @@
|
||||
const utils = require('../utils');
|
||||
const number_format = require('../utils/number_format');
|
||||
const model = require('../model');
|
||||
const BaseDocument = require('../model/document');
|
||||
const BaseMeta = require('../model/meta');
|
||||
@ -9,6 +10,7 @@ const errors = require('./errors');
|
||||
module.exports = {
|
||||
init_libs(frappe) {
|
||||
Object.assign(frappe, utils);
|
||||
Object.assign(frappe, number_format);
|
||||
frappe.model = model;
|
||||
frappe.BaseDocument = BaseDocument;
|
||||
frappe.BaseMeta = BaseMeta;
|
||||
|
@ -121,11 +121,29 @@ module.exports = class BaseDocument {
|
||||
}
|
||||
}
|
||||
|
||||
async insert() {
|
||||
set_child_idx() {
|
||||
// renumber children
|
||||
for (let field of this.meta.get_valid_fields()) {
|
||||
if (field.fieldtype==='Table') {
|
||||
for(let i=0; i < (this[field.fieldname] || []).length; i++) {
|
||||
this[field.fieldname][i].idx = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async commit() {
|
||||
// re-run triggers
|
||||
this.set_name();
|
||||
this.set_standard_values();
|
||||
this.set_keywords();
|
||||
this.set_child_idx();
|
||||
await this.trigger('validate');
|
||||
await this.trigger('commit');
|
||||
}
|
||||
|
||||
async insert() {
|
||||
await this.commit();
|
||||
await this.trigger('before_insert');
|
||||
this.sync_values(await frappe.db.insert(this.doctype, this.get_valid_dict()));
|
||||
await this.trigger('after_insert');
|
||||
@ -134,10 +152,8 @@ module.exports = class BaseDocument {
|
||||
return this;
|
||||
}
|
||||
|
||||
async update() {
|
||||
this.set_standard_values();
|
||||
this.set_keywords();
|
||||
await this.trigger('validate');
|
||||
async update() {
|
||||
await this.commit();
|
||||
await this.trigger('before_update');
|
||||
this.sync_values(await frappe.db.update(this.doctype, this.get_valid_dict()));
|
||||
await this.trigger('after_update');
|
||||
|
@ -17,6 +17,10 @@ describe('Document', () => {
|
||||
let doc2 = await frappe.get_doc(doc1.doctype, doc1.name);
|
||||
assert.equal(doc1.subject, doc2.subject);
|
||||
assert.equal(doc1.description, doc2.description);
|
||||
|
||||
// test frappe.db.exists
|
||||
assert.ok(await frappe.db.exists(doc1.doctype, doc1.name));
|
||||
assert.equal(await frappe.db.exists(doc1.doctype, doc1.name + '---'), false);
|
||||
});
|
||||
|
||||
it('should update a doc', async () => {
|
||||
@ -80,6 +84,7 @@ describe('Document', () => {
|
||||
await user.insert();
|
||||
|
||||
assert.equal(user.roles.length, 1);
|
||||
assert.equal(user.roles[0].role, 'Test Role');
|
||||
assert.equal(user.roles[0].parent, user.name);
|
||||
assert.equal(user.roles[0].parenttype, user.doctype);
|
||||
assert.equal(user.roles[0].parentfield, 'roles');
|
||||
@ -87,6 +92,27 @@ describe('Document', () => {
|
||||
// add another role
|
||||
user.roles.push({role: 'Test Role 1'});
|
||||
|
||||
await user.update();
|
||||
|
||||
assert.equal(user.roles.length, 2);
|
||||
assert.equal(user.roles[1].role, 'Test Role 1');
|
||||
assert.equal(user.roles[1].parent, user.name);
|
||||
assert.equal(user.roles[1].parenttype, user.doctype);
|
||||
assert.equal(user.roles[1].parentfield, 'roles');
|
||||
|
||||
// remove the first row
|
||||
user.roles = user.roles.filter((d, i) => i === 1);
|
||||
|
||||
await user.update();
|
||||
|
||||
user = await frappe.get_doc('User', user.name);
|
||||
|
||||
assert.equal(user.roles.length, 1);
|
||||
assert.equal(user.roles[0].role, 'Test Role 1');
|
||||
assert.equal(user.roles[0].idx, 0);
|
||||
assert.equal(user.roles[0].parent, user.name);
|
||||
assert.equal(user.roles[0].parenttype, user.doctype);
|
||||
assert.equal(user.roles[0].parentfield, 'roles');
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -1,15 +1,16 @@
|
||||
let utils = {};
|
||||
|
||||
Object.assign(utils, require('./number_format'));
|
||||
|
||||
Object.assign(utils, {
|
||||
module.exports = {
|
||||
format(value, field) {
|
||||
if (field.fieldtype==='Currency') {
|
||||
return frappe.format_number(value);
|
||||
} else {
|
||||
return value + '';
|
||||
if (value===null || value===undefined) {
|
||||
return '';
|
||||
} else {
|
||||
return value + '';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
slug(text) {
|
||||
return text.toLowerCase().replace(/ /g, '_');
|
||||
},
|
||||
@ -31,7 +32,32 @@ Object.assign(utils, {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(resolve, seconds * 1000);
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
module.exports = utils;
|
||||
_(text, args) {
|
||||
// should return translated text
|
||||
return this.string_replace(text, args);
|
||||
},
|
||||
|
||||
string_replace(str, args) {
|
||||
if (!Array.isArray(args)) {
|
||||
args = [args];
|
||||
}
|
||||
|
||||
if(str==undefined) return str;
|
||||
|
||||
let unkeyed_index = 0;
|
||||
return str.replace(/\{(\w*)\}/g, (match, key) => {
|
||||
if (key === '') {
|
||||
key = unkeyed_index;
|
||||
unkeyed_index++
|
||||
}
|
||||
if (key == +key) {
|
||||
return args[key] !== undefined
|
||||
? args[key]
|
||||
: match;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
};
|
Loading…
Reference in New Issue
Block a user