From b8d72944659f38f2dcf0e20a7f72ce10635b61ab Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 27 Feb 2018 21:59:14 +0530 Subject: [PATCH] added submit feature and other cleanups --- backends/database.js | 8 +-- backends/http.js | 2 +- backends/mysql.js | 8 +-- backends/sqlite.js | 4 +- client/desk/formmodal.js | 6 +- client/desk/formpage.js | 6 +- client/desk/printpage.js | 1 + client/style/style.scss | 16 +++++- client/ui/index.js | 8 ++- client/view/controls/base.js | 9 ++- client/view/controls/date.js | 7 +++ client/view/controls/link.js | 2 +- client/view/controls/table.js | 18 +++++- client/view/form.js | 93 +++++++++++++++++++------------ client/view/list.js | 12 +++- client/view/page.js | 5 ++ common/errors.js | 1 + index.js | 2 +- model/document.js | 62 ++++++++++++++++----- model/index.js | 9 +-- model/meta.js | 57 +++++++++++-------- server/index.js | 1 + utils/index.js | 4 +- utils/observable.js | 102 ++++++++++++++++++---------------- 24 files changed, 281 insertions(+), 162 deletions(-) diff --git a/backends/database.js b/backends/database.js index 1d2c87d2..9d96c201 100644 --- a/backends/database.js +++ b/backends/database.js @@ -36,7 +36,7 @@ module.exports = class Database extends Observable { let indexes = []; for (let field of meta.getValidFields({ withChildren: false })) { - if (this.type_map[field.fieldtype]) { + if (this.typeMap[field.fieldtype]) { this.updateColumnDefinition(field, columns, indexes); } } @@ -53,7 +53,7 @@ module.exports = class Database extends Observable { } updateColumnDefinition(field, columns, indexes) { - // return `${df.fieldname} ${this.type_map[df.fieldtype]} ${ ? "PRIMARY KEY" : ""} ${df.required && !df.default ? "NOT NULL" : ""} ${df.default ? `DEFAULT ${df.default}` : ""}` + // return `${df.fieldname} ${this.typeMap[df.fieldtype]} ${ ? "PRIMARY KEY" : ""} ${df.required && !df.default ? "NOT NULL" : ""} ${df.default ? `DEFAULT ${df.default}` : ""}` } async alterTable(doctype) { @@ -74,7 +74,7 @@ module.exports = class Database extends Observable { let meta = frappe.getMeta(doctype); let newColumns = []; for (let field of meta.getValidFields({ withChildren: false })) { - if (!tableColumns.includes(field.fieldname) && this.type_map[field.fieldtype]) { + if (!tableColumns.includes(field.fieldname) && this.typeMap[field.fieldtype]) { newColumns.push(field); } } @@ -399,7 +399,7 @@ module.exports = class Database extends Observable { } initTypeMap() { - this.type_map = { + this.typeMap = { 'Currency': 'real' , 'Int': 'integer' , 'Float': 'real' diff --git a/backends/http.js b/backends/http.js index dd22c367..4da249b6 100644 --- a/backends/http.js +++ b/backends/http.js @@ -119,7 +119,7 @@ module.exports = class HTTPClient extends Observable { } initTypeMap() { - this.type_map = { + this.typeMap = { 'Currency': true , 'Int': true , 'Float': true diff --git a/backends/mysql.js b/backends/mysql.js index 84fb5c14..9a004584 100644 --- a/backends/mysql.js +++ b/backends/mysql.js @@ -11,7 +11,7 @@ module.exports = class mysqlDatabase extends Database{ this.username = username; this.password = password; this.host = host; - this.init_type_map(); + this.init_typeMap(); } connect(db_name) { @@ -53,7 +53,7 @@ module.exports = class mysqlDatabase extends Database{ updateColumnDefinition(df, columns, indexes) { - columns.push(`${df.fieldname} ${this.type_map[df.fieldtype]} ${df.reqd && !df.default ? "not null" : ""} ${df.default ? `default '${df.default}'` : ""}`); + columns.push(`${df.fieldname} ${this.typeMap[df.fieldtype]} ${df.reqd && !df.default ? "not null" : ""} ${df.default ? `default '${df.default}'` : ""}`); } async getTableColumns(doctype) { @@ -176,8 +176,8 @@ module.exports = class mysqlDatabase extends Database{ } - init_type_map() { - this.type_map = { + init_typeMap() { + this.typeMap = { 'Currency': 'real' , 'Int': 'INT' , 'Float': 'decimal(18,6)' diff --git a/backends/sqlite.js b/backends/sqlite.js index 3aaf4240..ec191d2d 100644 --- a/backends/sqlite.js +++ b/backends/sqlite.js @@ -66,7 +66,7 @@ module.exports = class sqliteDatabase extends Database { } getColumnDefinition(field) { - let def = `${field.fieldname} ${this.type_map[field.fieldtype]}`; + let def = `${field.fieldname} ${this.typeMap[field.fieldtype]}`; if (field.fieldname==='name') { def += ' PRIMARY KEY NOT NULL'; } @@ -205,7 +205,7 @@ module.exports = class sqliteDatabase extends Database { } initTypeMap() { - this.type_map = { + this.typeMap = { 'Currency': 'real' , 'Int': 'integer' , 'Float': 'real' diff --git a/client/desk/formmodal.js b/client/desk/formmodal.js index e6d5c5f8..42cea488 100644 --- a/client/desk/formmodal.js +++ b/client/desk/formmodal.js @@ -27,11 +27,11 @@ module.exports = class FormModal extends Modal { doctype: this.doctype, parent: this.getBody(), container: this, - actions: ['submit'] + actions: ['save'] }); - this.form.on('submit', async () => { - await this.trigger('submit'); + this.form.on('save', async () => { + await this.trigger('save'); this.hide(); }); } diff --git a/client/desk/formpage.js b/client/desk/formpage.js index 823c30d8..0b359176 100644 --- a/client/desk/formpage.js +++ b/client/desk/formpage.js @@ -13,7 +13,7 @@ module.exports = class FormPage extends Page { doctype: doctype, parent: this.body, container: this, - actions: ['submit', 'delete', 'duplicate', 'settings', 'print'] + actions: ['save', 'delete', 'duplicate', 'settings', 'print'] }); this.on('show', async (params) => { @@ -21,11 +21,11 @@ module.exports = class FormPage extends Page { }); // if name is different after saving, change the route - this.form.on('submit', async (params) => { + this.form.on('save', async (params) => { let route = frappe.router.get_route(); if (this.form.doc.name && !(route && route[2] === this.form.doc.name)) { await frappe.router.setRoute('edit', this.form.doc.doctype, this.form.doc.name); - this.form.showAlert('Added', 'success'); + frappe.ui.showAlert({message: 'Added', color: 'green'}); } }); diff --git a/client/desk/printpage.js b/client/desk/printpage.js index d6c05ae0..3c98acd3 100644 --- a/client/desk/printpage.js +++ b/client/desk/printpage.js @@ -36,6 +36,7 @@ module.exports = class PrintPage extends Page { try { this.body.innerHTML = ``; this.setTitle(doc.name); + if (doc.submitted) this.addTitleBadge('✓', 'Submitted'); } catch (e) { this.renderError('Template Error', e); throw e; diff --git a/client/style/style.scss b/client/style/style.scss index f88a3384..682e4e46 100644 --- a/client/style/style.scss +++ b/client/style/style.scss @@ -3,6 +3,7 @@ @import "node_modules/flatpickr/dist/flatpickr"; @import "node_modules/flatpickr/dist/themes/airbnb"; @import "node_modules/codemirror/lib/codemirror"; +@import "node_modules/frappe-datatable/dist/frappe-datatable"; $spacer-1: 0.25rem; $spacer-2: 0.5rem; @@ -36,8 +37,8 @@ html { .page-head { padding: $spacer-2 $spacer-3; - background-color: $gray-800; - color: $gray-100; + background-color: $gray-100; + border-bottom: 1px solid $gray-300; .page-title { display: inline-block; @@ -56,7 +57,7 @@ html { } .form-body { - padding: $spacer-3; + padding: $spacer-4; .form-control.font-weight-bold { background-color: lightyellow; @@ -100,6 +101,15 @@ html { padding: $spacer-2 $spacer-3; } +.bottom-right-float { + position: fixed; + margin-bottom: 0px; + bottom: $spacer-3; + right: $spacer-3; + max-width: 200px; + padding: $spacer-2 $spacer-3; +} + .desk-menu { background-color: $gray-200; diff --git a/client/ui/index.js b/client/ui/index.js index 2a503c74..888c5ea4 100644 --- a/client/ui/index.js +++ b/client/ui/index.js @@ -41,6 +41,12 @@ module.exports = { make_dropdown(label, parent, btn_class = 'btn-secondary') { return new Dropdown({parent: parent, label:label, btn_class:btn_class}); - } + }, + showAlert({message, color='yellow', timeout=4}) { + let alert = this.add('div', 'alert alert-warning bottom-right-float', document.body); + alert.innerHTML = `${message}`; + frappe.sleep(timeout).then(() => alert.remove()); + return alert; + } } \ No newline at end of file diff --git a/client/view/controls/base.js b/client/view/controls/base.js index ff100a1d..691fd0ff 100644 --- a/client/view/controls/base.js +++ b/client/view/controls/base.js @@ -37,6 +37,7 @@ class BaseControl { } else { this.setDocValue(); } + this.setDisabled(); } renderTemplate() { @@ -86,10 +87,12 @@ class BaseControl { } + isDisabled() { + return this.disabled || this.formula || (this.doc && this.doc.submitted); + } + setDisabled() { - if (this.disabled) { - this.input.disabled = true; - } + this.input.disabled = this.isDisabled(); } getInputParent() { diff --git a/client/view/controls/date.js b/client/view/controls/date.js index f71f764e..60df660a 100644 --- a/client/view/controls/date.js +++ b/client/view/controls/date.js @@ -22,6 +22,13 @@ class DateControl extends BaseControl { }); } + setDisabled() { + this.input.disabled = this.isDisabled(); + if (this.flatpickr && this.flatpickr.altInput) { + this.flatpickr.altInput.disabled = this.isDisabled(); + } + } + setInputValue(value) { super.setInputValue(value); this.flatpickr.setDate(value); diff --git a/client/view/controls/link.js b/client/view/controls/link.js index 3bfb793c..36b7c0c0 100644 --- a/client/view/controls/link.js +++ b/client/view/controls/link.js @@ -39,7 +39,7 @@ class LinkControl extends BaseControl { formModal.form.doc.set('name', this.input.value); } - formModal.once('submit', async () => { + formModal.once('save', async () => { await this.updateDocValue(formModal.form.doc.name); }); } diff --git a/client/view/controls/table.js b/client/view/controls/table.js index 3d19aac5..2ee56518 100644 --- a/client/view/controls/table.js +++ b/client/view/controls/table.js @@ -55,11 +55,21 @@ class TableControl extends BaseControl { setInputValue(value) { this.datatable.refresh(this.getTableData(value)); + } + + setDisabled() { this.refreshToolbar(); } + getToolbar() { + return this.wrapper.querySelector('.table-toolbar'); + } + refreshToolbar() { - this.wrapper.querySelector('.table-toolbar').classList.toggle('hide', this.disabled ? true : false); + const toolbar = this.wrapper.querySelector('.table-toolbar'); + if (toolbar) { + toolbar.classList.toggle('hide', this.isDisabled() ? true : false); + } } getTableData(value) { @@ -68,6 +78,9 @@ class TableControl extends BaseControl { getTableInput(colIndex, rowIndex, value, parent) { let field = this.datatable.getColumn(colIndex).field; + if (field.disabled || field.forumla || this.isDisabled()) { + return false; + } if (field.fieldtype==='Text') { // text in modal @@ -92,6 +105,7 @@ class TableControl extends BaseControl { return control.setInputValue(control.doc[column.id]); }, setValue: async (value, rowIndex, column) => { + this.doc._dirty = true; control.handleChange(); }, getValue: () => { @@ -127,7 +141,7 @@ class TableControl extends BaseControl { id: field.fieldname, field: field, content: field.label, - editable: (this.disabled || field.disabled) ? false : true, + editable: true, sortable: false, resizable: true, dropdown: false, diff --git a/client/view/form.js b/client/view/form.js index 8c0e38a8..799c7b99 100644 --- a/client/view/form.js +++ b/client/view/form.js @@ -43,10 +43,12 @@ module.exports = class BaseForm extends Observable { } makeToolbar() { - if (this.actions.includes('submit')) { - this.container.addButton(frappe._("Save"), 'primary', async (event) => { - await this.submit(); - }) + if (this.actions.includes('save')) { + this.makeSaveButton(); + + if (this.meta.isSubmittable) { + this.makeSubmitButton(); + } } if (this.meta.print && this.actions.includes('print')) { @@ -67,7 +69,6 @@ module.exports = class BaseForm extends Observable { let menu = this.container.getDropdown(frappe._('Menu')); menu.addItem(frappe._('Duplicate'), async () => { let newDoc = await frappe.getDuplicate(this.doc); - console.log(newDoc); await frappe.router.setRoute('edit', newDoc.doctype, newDoc.name); newDoc.set('name', ''); }); @@ -82,13 +83,38 @@ module.exports = class BaseForm extends Observable { } + makeSaveButton() { + this.saveButton = this.container.addButton(frappe._("Save"), 'primary', async (event) => { + await this.save(); + }); + this.on('change', () => { + const show = this.doc._dirty && !this.doc.submitted; + this.saveButton.classList.toggle('hide', !show); + }); + } + + makeSubmitButton() { + this.submitButton = this.container.addButton(frappe._("Submit"), 'primary', async (event) => { + await this.submit(); + }); + this.on('change', () => { + const show = this.meta.isSubmittable && !this.doc._dirty && !this.doc.submitted; + this.submitButton.classList.toggle('hide', !show); + }); + + } + bindKeyboard() { keyboard.bindKey(this.form, 'ctrl+s', (e) => { if (document.activeElement) { document.activeElement.blur(); } e.preventDefault(); - this.submit(); + if (this.doc._notInserted || this.doc._dirty) { + this.save(); + } else { + if (this.meta.isSubmittable && !this.doc.submitted) this.submit(); + } }); } @@ -101,6 +127,7 @@ module.exports = class BaseForm extends Observable { await this.doc.set('name', ''); } this.setTitle(); + frappe._curFrm = this; } setTitle() { @@ -113,6 +140,7 @@ module.exports = class BaseForm extends Observable { } else { this.container.setTitle(this.doc.name); } + if (this.doc.submitted) this.container.addTitleBadge('✓', frappe._('Submitted')); } async bindEvents(doc) { @@ -120,17 +148,16 @@ module.exports = class BaseForm extends Observable { // stop listening to the old doc this.doc.off(this.docListener); } - this.clearAlert(); this.doc = doc; for (let control of this.controlList) { control.bind(this.doc); } - this.setupChangeListener(); + this.setupDocListener(); this.trigger('use', {doc:doc}); } - setupChangeListener() { + setupDocListener() { // refresh value in control this.docListener = (params) => { if (params.fieldname) { @@ -143,10 +170,12 @@ module.exports = class BaseForm extends Observable { // multiple values changed this.refresh(); } + this.trigger('change'); this.form.classList.remove('was-validated'); }; this.doc.on('change', this.docListener); + this.trigger('change'); } checkValidity() { @@ -165,7 +194,18 @@ module.exports = class BaseForm extends Observable { return validity; } + refresh() { + for(let control of this.controlList) { + control.refresh(); + } + } + async submit() { + this.doc.submitted = 1; + await this.save(); + } + + async save() { if (!this.checkValidity()) { this.form.classList.add('was-validated'); return; @@ -176,44 +216,23 @@ module.exports = class BaseForm extends Observable { } else { await this.doc.update(); } - this.showAlert('Saved', 'success'); + frappe.ui.showAlert({message: frappe._('Saved'), color: 'green'}); + this.refresh(); + this.trigger('change'); } catch (e) { - this.showAlert('Failed', 'danger'); + frappe.ui.showAlert({message: frappe._('Failed'), color: 'red'}); return; } - await this.trigger('submit'); + await this.trigger('save'); } async delete() { try { await this.doc.delete(); - this.showAlert('Deleted', 'success'); + frappe.ui.showAlert({message: frappe._('Deleted'), color: 'green'}); this.trigger('delete'); } catch (e) { - this.showAlert(e, 'danger'); + frappe.ui.showAlert({message: e, color: 'red'}); } } - - refresh() { - for(let control of this.controlList) { - control.refresh(); - } - } - - 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.clearAlert(); - }, clear_after * 1000); - } - - clearAlert() { - if (this.alert) { - frappe.ui.remove(this.alert); - this.alert = null; - } - } - } \ No newline at end of file diff --git a/client/view/list.js b/client/view/list.js index 7730e72c..aeb70409 100644 --- a/client/view/list.js +++ b/client/view/list.js @@ -58,9 +58,11 @@ module.exports = class BaseList { } async getData() { + let fields = this.getFields(); + this.updateStandardFields(fields); return await frappe.db.getAll({ doctype: this.doctype, - fields: this.getFields(), + fields: fields, filters: this.getFilters(), start: this.start, limit: this.pageLength + 1 @@ -68,7 +70,13 @@ module.exports = class BaseList { } getFields() { - return ['name']; + return []; + } + + updateStandardFields(fields) { + if (!fields.includes('name')) fields.push('name'); + if (!fields.includes('modified')) fields.push('modified'); + if (this.meta.isSubmittable && !fields.includes('submitted')) fields.push('submitted'); } async append() { diff --git a/client/view/page.js b/client/view/page.js index 0cd9b3cb..3768f20f 100644 --- a/client/view/page.js +++ b/client/view/page.js @@ -35,6 +35,11 @@ module.exports = class Page extends Observable { } } + addTitleBadge(message, title='', style='secondary') { + this.titleElement.innerHTML += ` + ${message}`; + } + hide() { this.parent.activePage = null; this.wrapper.classList.add('hide'); diff --git a/common/errors.js b/common/errors.js index 81e20654..a66113cf 100644 --- a/common/errors.js +++ b/common/errors.js @@ -12,6 +12,7 @@ class ValidationError extends BaseError { module.exports = { ValidationError: ValidationError, ValueError: class ValueError extends ValidationError { }, + Conflict: class Conflict extends ValidationError { }, NotFound: class NotFound extends BaseError { constructor(...params) { super(404, ...params); } }, diff --git a/index.js b/index.js index a1c1be50..176a1f32 100644 --- a/index.js +++ b/index.js @@ -104,7 +104,7 @@ module.exports = { async getDuplicate(doc) { const newDoc = await this.getNewDoc(doc.doctype); for (let field of this.getMeta(doc.doctype).getValidFields()) { - if (field.fieldname === 'name') continue; + if (['name', 'submitted'].includes(field.fieldname)) continue; if (field.fieldtype === 'Table') { newDoc[field.fieldname] = (doc[field.fieldname] || []).map(d => { let newd = Object.assign({}, d); diff --git a/model/document.js b/model/document.js index eb7381cc..0e6427b1 100644 --- a/model/document.js +++ b/model/document.js @@ -128,16 +128,17 @@ module.exports = class BaseDocument extends Observable { } setStandardValues() { - let now = new Date(); - if (this.docstatus === null || this.docstatus === undefined) { - this.docstatus = 0; + // set standard values on server-side only + if (frappe.isServer) { + let now = new Date(); + if (!this.submitted) this.submitted = 0; + if (!this.owner) { + this.owner = frappe.session.user; + this.creation = now; + } + this.modifieldBy = frappe.session.user; + this.modified = now; } - if (!this.owner) { - this.owner = frappe.session.user; - this.creation = now; - } - this.modifieldBy = frappe.session.user; - this.modified = now; } async load() { @@ -178,6 +179,30 @@ module.exports = class BaseDocument extends Observable { } } + async compareWithCurrentDoc() { + if (frappe.isServer && !this._notInserted) { + let currentDoc = await frappe.db.get(this.doctype, this.name); + + // check for conflict + if (currentDoc && this.modified != currentDoc.modified) { + throw new frappe.errors.Conflict(frappe._('Document {0} {1} has been modified after loading', [this.doctype, this.name])); + } + + if (this.submitted && !this.meta.isSubmittable) { + throw new frappe.errors.ValidationError(frappe._('Document type {1} is not submittable', [this.doctype])); + } + + // set submit action flag + if (this.submitted && !currentDoc.submitted) { + this.submitAction = true; + } + + if (currentDoc.submitted && !this.submitted) { + this.unSubmitAction = true; + } + } + } + async applyFormula() { if (!this.meta.hasFormula()) { return false; @@ -219,20 +244,25 @@ module.exports = class BaseDocument extends Observable { async insert() { await this.commit(); - await this.trigger('before_insert'); + await this.trigger('beforeInsert'); this.syncValues(await frappe.db.insert(this.doctype, this.getValidDict())); - await this.trigger('after_insert'); - await this.trigger('after_save'); + await this.trigger('afterInsert'); + await this.trigger('afterSave'); return this; } async update() { + await this.compareWithCurrentDoc(); await this.commit(); - await this.trigger('before_update'); + await this.trigger('beforeUpdate'); + if (this.submitAction) this.trigger('beforeSubmit'); + if (this.unSubmitAction) this.trigger('beforeUnSubmit'); this.syncValues(await frappe.db.update(this.doctype, this.getValidDict())); - await this.trigger('after_update'); - await this.trigger('after_save'); + await this.trigger('afterUpdate'); + await this.trigger('afterSave'); + if (this.submitAction) this.trigger('afterSubmit'); + if (this.unSubmitAction) this.trigger('afterUnSubmit'); return this; } @@ -243,6 +273,8 @@ module.exports = class BaseDocument extends Observable { await this.trigger('after_delete'); } + // trigger methods on the class if they match + // with the trigger name async trigger(event, params) { if (this[event]) { await this[event](params); diff --git a/model/index.js b/model/index.js index 1323b450..58a61b25 100644 --- a/model/index.js +++ b/model/index.js @@ -15,12 +15,12 @@ module.exports = { let next = await series.next() return prefix + next; }, - common_fields: [ + commonFields: [ { fieldname: 'name', fieldtype: 'Data', required: 1 } ], - parent_fields: [ + parentFields: [ { fieldname: 'owner', fieldtype: 'Data', required: 1 }, @@ -35,12 +35,9 @@ module.exports = { }, { fieldname: 'keywords', fieldtype: 'Text' - }, - { - fieldname: 'docstatus', fieldtype: 'Int', required: 1, default: 0 } ], - child_fields: [ + childFields: [ { fieldname: 'idx', fieldtype: 'Int', required: 1 }, diff --git a/model/meta.js b/model/meta.js index f9ff5b0c..14d232e1 100644 --- a/model/meta.js +++ b/model/meta.js @@ -69,36 +69,40 @@ module.exports = class BaseMeta extends BaseDocument { } getValidFields({ withChildren = true } = {}) { - if (!this._valid_fields) { + if (!this._validFields) { - this._valid_fields = []; - this._valid_fields_withChildren = []; + this._validFields = []; + this._validFieldsWithChildren = []; const _add = (field) => { - this._valid_fields.push(field); - this._valid_fields_withChildren.push(field); + this._validFields.push(field); + this._validFieldsWithChildren.push(field); } const doctype_fields = this.fields.map((field) => field.fieldname); // standard fields - for (let field of model.common_fields) { - if (frappe.db.type_map[field.fieldtype] && !doctype_fields.includes(field.fieldname)) { + for (let field of model.commonFields) { + if (frappe.db.typeMap[field.fieldtype] && !doctype_fields.includes(field.fieldname)) { _add(field); } } + if (this.isSubmittable) { + _add({fieldtype:'Check', fieldname: 'submitted', label: frappe._('Submitted')}) + } + if (this.isChild) { // child fields - for (let field of model.child_fields) { - if (frappe.db.type_map[field.fieldtype] && !doctype_fields.includes(field.fieldname)) { + for (let field of model.childFields) { + if (frappe.db.typeMap[field.fieldtype] && !doctype_fields.includes(field.fieldname)) { _add(field); } } } else { // parent fields - for (let field of model.parent_fields) { - if (frappe.db.type_map[field.fieldtype] && !doctype_fields.includes(field.fieldname)) { + for (let field of model.parentFields) { + if (frappe.db.typeMap[field.fieldtype] && !doctype_fields.includes(field.fieldname)) { _add(field); } } @@ -106,7 +110,7 @@ module.exports = class BaseMeta extends BaseDocument { // doctype fields for (let field of this.fields) { - let include = frappe.db.type_map[field.fieldtype]; + let include = frappe.db.typeMap[field.fieldtype]; if (include) { _add(field); @@ -114,15 +118,15 @@ module.exports = class BaseMeta extends BaseDocument { // include tables if (withChildren = True) if (!include && field.fieldtype === 'Table') { - this._valid_fields_withChildren.push(field); + this._validFieldsWithChildren.push(field); } } } if (withChildren) { - return this._valid_fields_withChildren; + return this._validFieldsWithChildren; } else { - return this._valid_fields; + return this._validFields; } } @@ -162,12 +166,13 @@ module.exports = class BaseMeta extends BaseDocument { setDefaultIndicators() { if (!this.indicators) { - this.indicators = { - key: 'docstatus', - colors: { - 0: 'gray', - 1: 'blue', - 2: 'red' + if (this.isSubmittable) { + this.indicators = { + key: 'submitted', + colors: { + 0: 'gray', + 1: 'blue' + } } } } @@ -177,9 +182,13 @@ module.exports = class BaseMeta extends BaseDocument { if (frappe.isDirty(this.name, doc.name)) { return 'orange'; } else { - let value = doc[this.indicators.key]; - if (value) { - return this.indicators.colors[value] || 'gray'; + if (this.indicators) { + let value = doc[this.indicators.key]; + if (value) { + return this.indicators.colors[value] || 'gray'; + } else { + return 'gray'; + } } else { return 'gray'; } diff --git a/server/index.js b/server/index.js index 7aae3d08..8b97f841 100644 --- a/server/index.js +++ b/server/index.js @@ -43,6 +43,7 @@ module.exports = { // listen frappe.app = app; frappe.server = server; + frappe.isServer = true; server.listen(frappe.config.port); }, diff --git a/utils/index.js b/utils/index.js index 13223326..035a8ce5 100644 --- a/utils/index.js +++ b/utils/index.js @@ -15,10 +15,10 @@ module.exports = { _(text, args) { // should return translated text - return this.string_replace(text, args); + return this.stringReplace(text, args); }, - string_replace(str, args) { + stringReplace(str, args) { if (!Array.isArray(args)) { args = [args]; } diff --git a/utils/observable.js b/utils/observable.js index 0cfd8ad9..a1a7fb1b 100644 --- a/utils/observable.js +++ b/utils/observable.js @@ -1,38 +1,32 @@ module.exports = class Observable { constructor() { - this._isHot = {}; - this._eventQueue = {}; + this._observable = { + isHot: {}, + eventQueue: {}, + listeners: {}, + onceListeners: {} + } } on(event, listener) { - this._addListener('_listeners', event, listener); - if (this._socketClient) { - this._socketClient.on(event, listener); + this._addListener('listeners', event, listener); + if (this._observable.socketClient) { + this._observable.socketClient.on(event, listener); } } // remove listener off(event, listener) { - for (let type of ['_listeners', '_onceListeners']) { - let index = this[type] && this[type][event] && this[type][event].indexOf(listener); + for (let type of ['listeners', 'onceListeners']) { + let index = this._observable[type][event] && this._observable[type][event].indexOf(listener); if (index) { - this[type][event].splice(index, 1); + this._observable[type][event].splice(index, 1); } } } once(event, listener) { - this._addListener('_onceListeners', event, listener); - } - - bindSocketClient(socket) { - // also send events with sockets - this._socketClient = socket; - } - - bindSocketServer(socket) { - // also send events with sockets - this._socketServer = socket; + this._addListener('onceListeners', event, listener); } async trigger(event, params, throttle=false) { @@ -45,38 +39,58 @@ module.exports = class Observable { } async _executeTriggers(event, params) { - await this._triggerEvent('_listeners', event, params); - await this._triggerEvent('_onceListeners', event, params); + let response = await this._triggerEvent('listeners', event, params); + if (response === false) return false; - if (this._socketServer) { - this._socketServer.emit(event, params); + response = await this._triggerEvent('onceListeners', event, params); + if (response === false) return false; + + // emit via socket + if (this._observable.socketServer) { + this._observable.socketServer.emit(event, params); } // clear once-listeners - if (this._onceListeners && this._onceListeners[event]) { - delete this._onceListeners[event]; + if (this._observable.onceListeners && this._observable.onceListeners[event]) { + delete this._observable.onceListeners[event]; } + + } + + clearListeners() { + this._observable.listeners = {}; + this._observable.onceListeners = {}; + } + + bindSocketClient(socket) { + // also send events with sockets + this._observable.socketClient = socket; + } + + bindSocketServer(socket) { + // also send events with sockets + this._observable.socketServer = socket; } _throttled(event, params, throttle) { - if (this._isHot[event]) { + if (this._observable.isHot[event]) { // hot, add to queue - if (!this._eventQueue[event]) this._eventQueue[event] = []; - this._eventQueue[event].push(params); + if (!this._observable.eventQueue[event]) this._observable.eventQueue[event] = []; + this._observable.eventQueue[event].push(params); // aleady hot, quit return true; } - this._isHot[event] = true; + this._observable.isHot[event] = true; // cool-off setTimeout(() => { - this._isHot[event] = false; + this._observable.isHot[event] = false; // flush queue - if (this._eventQueue[event]) { - let _queuedParams = this._eventQueue[event]; - this._eventQueue[event] = null; + if (this._observable.eventQueue[event]) { + let _queuedParams = this._observable.eventQueue[event]; + this._observable.eventQueue[event] = null; this._executeTriggers(event, _queuedParams); } }, throttle); @@ -84,26 +98,18 @@ module.exports = class Observable { return false; } - _addListener(name, event, listener) { - if (!this[name]) { - this[name] = {}; + _addListener(type, event, listener) { + if (!this._observable[type][event]) { + this._observable[type][event] = []; } - if (!this[name][event]) { - this[name][event] = []; - } - this[name][event].push(listener); + this._observable[type][event].push(listener); } - async _triggerEvent(name, event, params) { - if (this[name] && this[name][event]) { - for (let listener of this[name][event]) { + async _triggerEvent(type, event, params) { + if (this._observable[type][event]) { + for (let listener of this._observable[type][event]) { await listener(params); } } } - - clearListeners() { - this._listeners = {}; - this._onceListeners = {}; - } }