2
0
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:
Rushabh Mehta 2018-02-13 16:12:44 +05:30
parent 16932cd4f9
commit c7c115452a
13 changed files with 257 additions and 50 deletions

44
client/desk/formmodal.js Normal file
View 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();
}
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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
View 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'
},
}

View File

@ -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">&times;</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');
}

View File

@ -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);
})
}
});
}

View File

@ -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();
});

View File

@ -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();

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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"