2
0
mirror of https://github.com/frappe/books.git synced 2025-01-10 18:24:40 +00:00

added modal

This commit is contained in:
Rushabh Mehta 2018-02-07 18:53:52 +05:30
parent 78799cb8d9
commit 2a2f1fb6f3
11 changed files with 224 additions and 27 deletions

View File

@ -1,6 +1,6 @@
const frappe = require('frappejs'); const frappe = require('frappejs');
const sqlite3 = require('sqlite3').verbose(); const sqlite3 = require('sqlite3').verbose();
const debug = true; const debug = false;
class sqliteDatabase { class sqliteDatabase {
constructor({ db_path }) { constructor({ db_path }) {
@ -164,10 +164,11 @@ class sqliteDatabase {
// delete other children // delete other children
// `delete from doctype where parent = ? and name not in (?, ?, ?)}` // `delete from doctype where parent = ? and name not in (?, ?, ?)}`
await this.run(`delete from ${frappe.slug(field.childtype)} await this.run(`delete from ${frappe.slug(field.childtype)}
where where
parent = ? and 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; return doc;
} }
@ -185,6 +186,9 @@ class sqliteDatabase {
} }
prepare_child(parenttype, parent, child, field, idx) { prepare_child(parenttype, parent, child, field, idx) {
if (!child.name) {
child.name = frappe.get_random_name();
}
child.parent = parent; child.parent = parent;
child.parenttype = parenttype; child.parenttype = parenttype;
child.parentfield = field.fieldname; child.parentfield = field.fieldname;

View File

@ -26,7 +26,6 @@ module.exports = class Desk {
}; };
this.init_routes(); this.init_routes();
// this.search = new Search(this.nav); // this.search = new Search(this.nav);
} }

View File

@ -1,6 +1,11 @@
@import "node_modules/bootstrap/scss/bootstrap"; @import "node_modules/bootstrap/scss/bootstrap";
@import "node_modules/awesomplete/awesomplete"; @import "node_modules/awesomplete/awesomplete";
$spacer-1: 0.25rem;
$spacer-2: 0.5rem;
$spacer-3: 1rem;
$spacer-4: 2rem;
html { html {
font-size: 14px; font-size: 14px;
} }
@ -18,12 +23,12 @@ html {
max-width: 600px; max-width: 600px;
.form-toolbar { .form-toolbar {
height: 2rem; height: $spacer-4;
margin-bottom: 1rem; margin-bottom: $spacer-3;
} }
.alert { .alert {
margin-top: 1rem; margin-top: $spacer-3;
} }
} }
@ -66,13 +71,21 @@ mark {
padding: 0px; padding: 0px;
} }
.table-wrapper {
margin-bottom: $spacer-2;
}
.table-toolbar {
margin-top: $spacer-2;
}
.data-table-col .edit-cell { .data-table-col .edit-cell {
padding: 0px !important; padding: 0px !important;
input, textarea { input, textarea {
border-radius: none; border-radius: none;
margin: none; margin: none;
padding: 0.25rem; padding: $spacer-1;
} }
.awesomplete > ul { .awesomplete > ul {

59
client/ui/modal.js Normal file
View 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">&times;</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);
}
}

View File

@ -4,7 +4,12 @@ class FloatControl extends BaseControl {
make() { make() {
super.make(); super.make();
this.input.setAttribute('type', 'text'); 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);
})
} }
}; };

View File

@ -2,20 +2,41 @@ const frappe = require('frappejs');
const BaseControl = require('./base'); const BaseControl = require('./base');
const DataTable = require('frappe-datatable'); const DataTable = require('frappe-datatable');
const controls = require('./index'); const controls = require('./index');
const Modal = require('frappejs/client/ui/modal');
class TableControl extends BaseControl { class TableControl extends BaseControl {
make() { make() {
if (!this.datatable) { if (!this.datatable) {
this.wrapper = frappe.ui.add('div', 'datatable-wrapper', this.get_input_parent()); this.wrapper = frappe.ui.add('div', 'table-wrapper', this.get_input_parent());
this.datatable = new DataTable(this.wrapper, { 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(), columns: this.get_columns(),
data: this.get_table_data(), data: this.get_table_data(),
takeAvailableSpace: true, takeAvailableSpace: true,
enableClusterize: true, enableClusterize: true,
addCheckboxColumn: true,
editing: this.get_table_input.bind(this), 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) { get_table_input(colIndex, rowIndex, value, parent) {
let field = this.datatable.getColumn(colIndex).field; 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; field.only_input = true;
const control = controls.make_control({field: field, parent: parent}); const control = controls.make_control({field: field, parent: parent});
@ -50,6 +80,23 @@ class TableControl extends BaseControl {
return control.get_input_value(); 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() { get_columns() {

View File

@ -27,7 +27,7 @@ module.exports = class BaseForm extends Observable {
this.body = frappe.ui.add('div', 'form-body', this.parent); this.body = frappe.ui.add('div', 'form-body', this.parent);
this.make_toolbar(); 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) { for(let field of this.meta.fields) {
if (controls.get_control_class(field.fieldtype)) { if (controls.get_control_class(field.fieldtype)) {
let control = controls.make_control({field: field, form: this}); let control = controls.make_control({field: field, form: this});

View File

@ -1,4 +1,5 @@
const utils = require('../utils'); const utils = require('../utils');
const number_format = require('../utils/number_format');
const model = require('../model'); const model = require('../model');
const BaseDocument = require('../model/document'); const BaseDocument = require('../model/document');
const BaseMeta = require('../model/meta'); const BaseMeta = require('../model/meta');
@ -9,6 +10,7 @@ const errors = require('./errors');
module.exports = { module.exports = {
init_libs(frappe) { init_libs(frappe) {
Object.assign(frappe, utils); Object.assign(frappe, utils);
Object.assign(frappe, number_format);
frappe.model = model; frappe.model = model;
frappe.BaseDocument = BaseDocument; frappe.BaseDocument = BaseDocument;
frappe.BaseMeta = BaseMeta; frappe.BaseMeta = BaseMeta;

View File

@ -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_name();
this.set_standard_values(); this.set_standard_values();
this.set_keywords(); this.set_keywords();
this.set_child_idx();
await this.trigger('validate'); await this.trigger('validate');
await this.trigger('commit');
}
async insert() {
await this.commit();
await this.trigger('before_insert'); await this.trigger('before_insert');
this.sync_values(await frappe.db.insert(this.doctype, this.get_valid_dict())); this.sync_values(await frappe.db.insert(this.doctype, this.get_valid_dict()));
await this.trigger('after_insert'); await this.trigger('after_insert');
@ -134,10 +152,8 @@ module.exports = class BaseDocument {
return this; return this;
} }
async update() { async update() {
this.set_standard_values(); await this.commit();
this.set_keywords();
await this.trigger('validate');
await this.trigger('before_update'); await this.trigger('before_update');
this.sync_values(await frappe.db.update(this.doctype, this.get_valid_dict())); this.sync_values(await frappe.db.update(this.doctype, this.get_valid_dict()));
await this.trigger('after_update'); await this.trigger('after_update');

View File

@ -17,6 +17,10 @@ describe('Document', () => {
let doc2 = await frappe.get_doc(doc1.doctype, doc1.name); let doc2 = await frappe.get_doc(doc1.doctype, doc1.name);
assert.equal(doc1.subject, doc2.subject); assert.equal(doc1.subject, doc2.subject);
assert.equal(doc1.description, doc2.description); 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 () => { it('should update a doc', async () => {
@ -80,6 +84,7 @@ describe('Document', () => {
await user.insert(); await user.insert();
assert.equal(user.roles.length, 1); 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].parent, user.name);
assert.equal(user.roles[0].parenttype, user.doctype); assert.equal(user.roles[0].parenttype, user.doctype);
assert.equal(user.roles[0].parentfield, 'roles'); assert.equal(user.roles[0].parentfield, 'roles');
@ -87,6 +92,27 @@ describe('Document', () => {
// add another role // add another role
user.roles.push({role: 'Test Role 1'}); 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');
}); });
}); });

View File

@ -1,15 +1,16 @@
let utils = {}; module.exports = {
Object.assign(utils, require('./number_format'));
Object.assign(utils, {
format(value, field) { format(value, field) {
if (field.fieldtype==='Currency') { if (field.fieldtype==='Currency') {
return frappe.format_number(value); return frappe.format_number(value);
} else { } else {
return value + ''; if (value===null || value===undefined) {
return '';
} else {
return value + '';
}
} }
}, },
slug(text) { slug(text) {
return text.toLowerCase().replace(/ /g, '_'); return text.toLowerCase().replace(/ /g, '_');
}, },
@ -31,7 +32,32 @@ Object.assign(utils, {
return new Promise(resolve => { return new Promise(resolve => {
setTimeout(resolve, seconds * 1000); 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;
}
});
}
};