mirror of
https://github.com/frappe/books.git
synced 2024-12-23 03:19:01 +00:00
new documents from link created in modal
This commit is contained in:
parent
16932cd4f9
commit
c7c115452a
44
client/desk/formmodal.js
Normal file
44
client/desk/formmodal.js
Normal file
@ -0,0 +1,44 @@
|
||||
const Modal = require('frappejs/client/ui/modal');
|
||||
const view = require('frappejs/client/view');
|
||||
const frappe = require('frappejs');
|
||||
|
||||
module.exports = class FormModal extends Modal {
|
||||
constructor(doctype, name) {
|
||||
super({title: doctype});
|
||||
this.doctype = doctype;
|
||||
this.makeForm();
|
||||
this.showWith(doctype, name);
|
||||
}
|
||||
|
||||
makeForm() {
|
||||
this.form = new (view.getFormClass(this.doctype))({
|
||||
doctype: this.doctype,
|
||||
parent: this.getBody(),
|
||||
container: this,
|
||||
actions: ['submit']
|
||||
});
|
||||
|
||||
this.form.on('submit', () => {
|
||||
this.hide();
|
||||
});
|
||||
}
|
||||
|
||||
addButton(label, className, action) {
|
||||
if (className === 'primary') {
|
||||
this.addPrimary(label, action);
|
||||
} else {
|
||||
this.addSecondary(label, action);
|
||||
}
|
||||
}
|
||||
|
||||
async showWith(doctype, name) {
|
||||
await this.form.setDoc(doctype, name);
|
||||
if (this.form.doc._notInserted) {
|
||||
this.setTitle(frappe._('New {0}', doctype));
|
||||
} else {
|
||||
this.setTitle(`${doctype} ${name}`);
|
||||
}
|
||||
this.show();
|
||||
this.$modal.find('input:first').focus();
|
||||
}
|
||||
}
|
@ -8,14 +8,15 @@ module.exports = class FormPage extends Page {
|
||||
super(`Edit ${meta.name}`);
|
||||
this.meta = meta;
|
||||
|
||||
this.form = new (view.get_form_class(doctype))({
|
||||
this.form = new (view.getFormClass(doctype))({
|
||||
doctype: doctype,
|
||||
parent: this.body,
|
||||
page: this
|
||||
container: this,
|
||||
actions: ['submit', 'delete']
|
||||
});
|
||||
|
||||
this.on('show', async (params) => {
|
||||
await this.show_doc(params.doctype, params.name);
|
||||
await this.showDoc(params.doctype, params.name);
|
||||
});
|
||||
|
||||
// if name is different after saving, change the route
|
||||
@ -32,10 +33,9 @@ module.exports = class FormPage extends Page {
|
||||
});
|
||||
}
|
||||
|
||||
async show_doc(doctype, name) {
|
||||
async showDoc(doctype, name) {
|
||||
try {
|
||||
this.doc = await frappe.getDoc(doctype, name);
|
||||
this.form.use(this.doc);
|
||||
await this.form.setDoc(doctype, name);
|
||||
} catch (e) {
|
||||
this.renderError(e.status_code, e.message);
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ const Page = require('frappejs/client/view/page');
|
||||
const FormPage = require('frappejs/client/desk/formpage');
|
||||
const ListPage = require('frappejs/client/desk/listpage');
|
||||
const Navbar = require('./navbar');
|
||||
const FormModal = require('frappejs/client/desk/formmodal');
|
||||
|
||||
module.exports = class Desk {
|
||||
constructor() {
|
||||
@ -22,7 +23,8 @@ module.exports = class Desk {
|
||||
|
||||
this.pages = {
|
||||
lists: {},
|
||||
forms: {}
|
||||
forms: {},
|
||||
formModals: {}
|
||||
};
|
||||
|
||||
this.routeItems = {};
|
||||
@ -54,7 +56,6 @@ module.exports = class Desk {
|
||||
let doc = await frappe.getNewDoc(params.doctype);
|
||||
// unset the name, its local
|
||||
await frappe.router.setRoute('edit', doc.doctype, doc.name);
|
||||
await doc.set('name', '');
|
||||
});
|
||||
|
||||
frappe.router.on('change', () => {
|
||||
@ -79,6 +80,15 @@ module.exports = class Desk {
|
||||
return this.pages.forms[doctype];
|
||||
}
|
||||
|
||||
showFormModal(doctype, name) {
|
||||
if (!this.pages.formModals[doctype]) {
|
||||
this.pages.formModals[doctype] = new FormModal(doctype, name);
|
||||
} else {
|
||||
this.pages.formModals[doctype].showWith(doctype, name);
|
||||
}
|
||||
return this.pages.formModals[doctype];
|
||||
}
|
||||
|
||||
setActive(item) {
|
||||
let className = 'list-group-item-secondary';
|
||||
let activeItem = this.sidebarList.querySelector('.' + className);
|
||||
|
@ -60,6 +60,10 @@ html {
|
||||
.checkbox {
|
||||
margin-right: $spacer-2;
|
||||
}
|
||||
|
||||
a, a:hover, a:visited, a:active {
|
||||
color: $gray-800;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu-right {
|
||||
|
40
client/ui/keyboard.js
Normal file
40
client/ui/keyboard.js
Normal file
@ -0,0 +1,40 @@
|
||||
module.exports = {
|
||||
bindKey(element, key, handler) {
|
||||
element.addEventListener('keydown', (e) => {
|
||||
if (key === this.getKey(e)) {
|
||||
handler(e);
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
getKey(e) {
|
||||
var keycode = e.keyCode || e.which;
|
||||
var key = this.keyMap[keycode] || String.fromCharCode(keycode);
|
||||
|
||||
if(e.ctrlKey || e.metaKey) {
|
||||
// add ctrl+ the key
|
||||
key = 'ctrl+' + key;
|
||||
}
|
||||
if(e.shiftKey) {
|
||||
// add ctrl+ the key
|
||||
key = 'shift+' + key;
|
||||
}
|
||||
return key.toLowerCase();
|
||||
},
|
||||
|
||||
keyMap: {
|
||||
8: 'backspace',
|
||||
9: 'tab',
|
||||
13: 'enter',
|
||||
16: 'shift',
|
||||
17: 'ctrl',
|
||||
91: 'meta',
|
||||
18: 'alt',
|
||||
27: 'escape',
|
||||
37: 'left',
|
||||
39: 'right',
|
||||
38: 'up',
|
||||
40: 'down',
|
||||
32: 'space'
|
||||
},
|
||||
}
|
@ -1,20 +1,27 @@
|
||||
const $ = require('jquery');
|
||||
const bootstrap = require('bootstrap');
|
||||
const Observable = require('frappejs/utils/observable');
|
||||
|
||||
module.exports = class Modal {
|
||||
module.exports = class Modal extends Observable {
|
||||
constructor({ title, body, primary, secondary }) {
|
||||
super();
|
||||
Object.assign(this, arguments[0]);
|
||||
this.make();
|
||||
this.show();
|
||||
}
|
||||
|
||||
make() {
|
||||
this.$modal = $(`<div class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">${title}</h5>
|
||||
<h5 class="modal-title">${this.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}
|
||||
${this.getBodyHTML()}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
</div>
|
||||
@ -28,23 +35,33 @@ module.exports = class Modal {
|
||||
if (this.secondary) {
|
||||
this.addSecondary(this.secondary.label, this.secondary.action);
|
||||
}
|
||||
this.show();
|
||||
|
||||
this.$modal.on('hidden.bs.modal', () => this.trigger('hide'));
|
||||
this.$modal.on('shown.bs.modal', () => this.trigger('show'));
|
||||
}
|
||||
|
||||
getBodyHTML() {
|
||||
return this.body || '';
|
||||
}
|
||||
|
||||
addPrimary(label, action) {
|
||||
this.$primary = $(`<button type="button" class="btn btn-primary">
|
||||
return $(`<button type="button" class="btn btn-primary">
|
||||
${label}</button>`)
|
||||
.appendTo(this.$modal.find('.modal-footer'))
|
||||
.on('click', () => action(this));
|
||||
}
|
||||
|
||||
addSecondary(label, action) {
|
||||
this.$primary = $(`<button type="button" class="btn btn-secondary">
|
||||
return $(`<button type="button" class="btn btn-secondary">
|
||||
${label}</button>`)
|
||||
.appendTo(this.$modal.find('.modal-footer'))
|
||||
.on('click', () => action(this));
|
||||
}
|
||||
|
||||
setTitle(title) {
|
||||
this.$modal.find('.modal-title').text(title);
|
||||
}
|
||||
|
||||
show() {
|
||||
this.$modal.modal('show');
|
||||
}
|
||||
|
@ -9,12 +9,37 @@ class LinkControl extends BaseControl {
|
||||
this.awesomplete = new Awesomplete(this.input, {
|
||||
autoFirst: true,
|
||||
minChars: 0,
|
||||
maxItems: 99
|
||||
maxItems: 99,
|
||||
filter: function() {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// rebuild the list on input
|
||||
this.input.addEventListener('input', async (event) => {
|
||||
this.awesomplete.list = await this.getList(this.input.value);
|
||||
let list = await this.getList(this.input.value);
|
||||
|
||||
// action to add new item
|
||||
list.push({
|
||||
label:frappe._('+ New {0}', this.target),
|
||||
value: '__newItem',
|
||||
action: () => {
|
||||
}
|
||||
});
|
||||
|
||||
this.awesomplete.list = list;
|
||||
});
|
||||
|
||||
// new item action
|
||||
this.input.addEventListener('awesomplete-select', async (e) => {
|
||||
if (e.text && e.text.value === '__newItem') {
|
||||
e.preventDefault();
|
||||
const newDoc = await frappe.getNewDoc(this.target);
|
||||
const formModal = frappe.desk.showFormModal(this.target, newDoc.name);
|
||||
formModal.form.once('submit', () => {
|
||||
this.form.doc.set(this.fieldname, formModal.form.doc.name);
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -100,7 +100,7 @@ class TableControl extends BaseControl {
|
||||
}
|
||||
}
|
||||
});
|
||||
this.modal.$modal.on('hidden.bs.modal', () => {
|
||||
this.modal.on('hide', () => {
|
||||
this.datatable.cellmanager.deactivateEditing();
|
||||
this.datatable.cellmanager.$focusedCell.focus();
|
||||
});
|
||||
|
@ -1,9 +1,10 @@
|
||||
const frappe = require('frappejs');
|
||||
const controls = require('./controls');
|
||||
const Observable = require('frappejs/utils/observable');
|
||||
const keyboard = require('frappejs/client/ui/keyboard');
|
||||
|
||||
module.exports = class BaseForm extends Observable {
|
||||
constructor({doctype, parent, submit_label='Submit', page}) {
|
||||
constructor({doctype, parent, submit_label='Submit', container}) {
|
||||
super();
|
||||
Object.assign(this, arguments[0]);
|
||||
this.controls = {};
|
||||
@ -28,6 +29,7 @@ module.exports = class BaseForm extends Observable {
|
||||
this.form.onValidate = true;
|
||||
|
||||
this.makeControls();
|
||||
this.bindKeyboard();
|
||||
}
|
||||
|
||||
makeControls() {
|
||||
@ -41,18 +43,42 @@ module.exports = class BaseForm extends Observable {
|
||||
}
|
||||
|
||||
makeToolbar() {
|
||||
this.btnSubmit = this.page.addButton(frappe._("Save"), 'btn-primary', async (event) => {
|
||||
if (this.actions.includes('submit')) {
|
||||
this.btnSubmit = this.container.addButton(frappe._("Save"), 'primary', async (event) => {
|
||||
await this.submit();
|
||||
})
|
||||
}
|
||||
|
||||
this.btnDelete = this.page.addButton(frappe._("Delete"), 'btn-outline-secondary', async (e) => {
|
||||
if (this.actions.includes('delete')) {
|
||||
this.btnDelete = this.container.addButton(frappe._("Delete"), 'secondary', async (e) => {
|
||||
await this.doc.delete();
|
||||
this.showAlert('Deleted', 'success');
|
||||
this.trigger('delete');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async use(doc) {
|
||||
bindKeyboard() {
|
||||
keyboard.bindKey(this.form, 'ctrl+s', (e) => {
|
||||
if (document.activeElement) {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
e.preventDefault();
|
||||
this.submit();
|
||||
});
|
||||
}
|
||||
|
||||
async setDoc(doctype, name) {
|
||||
this.doc = await frappe.getDoc(doctype, name);
|
||||
this.bindEvents(this.doc);
|
||||
if (this.doc._notInserted && !this.doc._nameCleared) {
|
||||
this.doc._nameCleared = true;
|
||||
// flag so that name is cleared only once
|
||||
await this.doc.set('name', '');
|
||||
}
|
||||
}
|
||||
|
||||
async bindEvents(doc) {
|
||||
if (this.doc) {
|
||||
// clear handlers of outgoing doc
|
||||
this.doc.clearHandlers();
|
||||
|
@ -2,7 +2,7 @@ const BaseList = require('frappejs/client/view/list');
|
||||
const BaseForm = require('frappejs/client/view/form');
|
||||
|
||||
module.exports = {
|
||||
get_form_class(doctype) {
|
||||
getFormClass(doctype) {
|
||||
return this.get_view_class(doctype, 'Form', BaseForm);
|
||||
},
|
||||
getList_class(doctype) {
|
||||
|
@ -21,9 +21,9 @@ module.exports = class Page extends Observable {
|
||||
this.trigger('hide');
|
||||
}
|
||||
|
||||
addButton(label, cssClass, action) {
|
||||
addButton(label, className, action) {
|
||||
this.head.classList.remove('hide');
|
||||
this.button = frappe.ui.add('button', 'btn ' + cssClass, this.head);
|
||||
this.button = frappe.ui.add('button', 'btn ' + this.getClassName(className), this.head);
|
||||
this.button.innerHTML = label;
|
||||
this.button.addEventListener('click', action);
|
||||
return this.button;
|
||||
@ -54,4 +54,13 @@ module.exports = class Page extends Observable {
|
||||
this.page_error.classList.remove('hide');
|
||||
this.page_error.innerHTML = `<h3 class="text-extra-muted">${title ? title : ""}</h3><p class="text-muted">${message ? message : ""}</p>`;
|
||||
}
|
||||
|
||||
getClassName(className) {
|
||||
const newName = {
|
||||
'primary': 'btn-primary',
|
||||
'secondary': 'btn-outline-secondary'
|
||||
}[className];
|
||||
|
||||
return newName || className;
|
||||
}
|
||||
}
|
@ -1,18 +1,33 @@
|
||||
module.exports = class Observable {
|
||||
constructor() {
|
||||
this._handlers = {};
|
||||
on(event, handler) {
|
||||
this._addHandler('_handlers', event, handler);
|
||||
}
|
||||
|
||||
on(event, fn) {
|
||||
if (!this._handlers[event]) {
|
||||
this._handlers[event] = [];
|
||||
}
|
||||
this._handlers[event].push(fn);
|
||||
once(event, handler) {
|
||||
this._addHandler('_onceHandlers', event, handler);
|
||||
}
|
||||
|
||||
async trigger(event, params) {
|
||||
if (this._handlers[event]) {
|
||||
for (let handler of this._handlers[event]) {
|
||||
await this._triggerHandler('_handlers', event, params);
|
||||
await this._triggerHandler('_onceHandlers', event, params);
|
||||
if (this._onceHandlers && this._onceHandlers[event]) {
|
||||
delete this._onceHandlers[event];
|
||||
}
|
||||
}
|
||||
|
||||
_addHandler(name, event, handler) {
|
||||
if (!this[name]) {
|
||||
this[name] = {};
|
||||
}
|
||||
if (!this[name][event]) {
|
||||
this[name][event] = [];
|
||||
}
|
||||
this[name][event].push(handler);
|
||||
}
|
||||
|
||||
async _triggerHandler(name, event, params) {
|
||||
if (this[name] && this[name][event]) {
|
||||
for (let handler of this[name][event]) {
|
||||
await handler(params);
|
||||
}
|
||||
}
|
||||
|
39
yarn.lock
39
yarn.lock
@ -256,6 +256,10 @@ big.js@^3.1.3:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
|
||||
|
||||
bignumber.js@4.0.4:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-4.0.4.tgz#7c40f5abcd2d6623ab7b99682ee7db81b11889a4"
|
||||
|
||||
binary-extensions@^1.0.0:
|
||||
version "1.11.0"
|
||||
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205"
|
||||
@ -2448,7 +2452,7 @@ mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1:
|
||||
dependencies:
|
||||
minimist "0.0.8"
|
||||
|
||||
mocha@^4.0.1:
|
||||
mocha@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/mocha/-/mocha-4.1.0.tgz#7d86cfbcf35cb829e2754c32e17355ec05338794"
|
||||
dependencies:
|
||||
@ -2471,6 +2475,15 @@ mute-stream@0.0.7:
|
||||
version "0.0.7"
|
||||
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
|
||||
|
||||
mysql@^2.15.0:
|
||||
version "2.15.0"
|
||||
resolved "https://registry.yarnpkg.com/mysql/-/mysql-2.15.0.tgz#ea16841156343e8f2e47fc8985ec41cdd9573b5c"
|
||||
dependencies:
|
||||
bignumber.js "4.0.4"
|
||||
readable-stream "2.3.3"
|
||||
safe-buffer "5.1.1"
|
||||
sqlstring "2.3.0"
|
||||
|
||||
nan@^2.3.0, nan@^2.3.2:
|
||||
version "2.8.0"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a"
|
||||
@ -3444,16 +3457,7 @@ read-pkg@^1.0.0:
|
||||
normalize-package-data "^2.3.2"
|
||||
path-type "^1.0.0"
|
||||
|
||||
readable-stream@^1.0.26-4:
|
||||
version "1.1.14"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
|
||||
dependencies:
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.1"
|
||||
isarray "0.0.1"
|
||||
string_decoder "~0.10.x"
|
||||
|
||||
readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.2.2:
|
||||
readable-stream@2.3.3, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.2.2:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c"
|
||||
dependencies:
|
||||
@ -3465,6 +3469,15 @@ readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable
|
||||
string_decoder "~1.0.3"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
readable-stream@^1.0.26-4:
|
||||
version "1.1.14"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
|
||||
dependencies:
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.1"
|
||||
isarray "0.0.1"
|
||||
string_decoder "~0.10.x"
|
||||
|
||||
readable-stream@~1.0.26, readable-stream@~1.0.26-4:
|
||||
version "1.0.34"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
|
||||
@ -3968,6 +3981,10 @@ sqlite3@^3.1.13:
|
||||
nan "~2.7.0"
|
||||
node-pre-gyp "~0.6.38"
|
||||
|
||||
sqlstring@2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/sqlstring/-/sqlstring-2.3.0.tgz#525b8a4fd26d6f71aa61e822a6caf976d31ad2a8"
|
||||
|
||||
sshpk@^1.7.0:
|
||||
version "1.13.1"
|
||||
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3"
|
||||
|
Loading…
Reference in New Issue
Block a user