2
0
mirror of https://github.com/frappe/books.git synced 2024-11-13 00:46:28 +00:00

- Custom doc for Report Filters

- frappe.showModal
- Custom textarea rows
This commit is contained in:
thefalconx33 2019-07-30 16:31:06 +05:30
parent eddcaa084c
commit 79b99055e0
5 changed files with 672 additions and 606 deletions

526
index.js
View File

@ -1,261 +1,285 @@
const Observable = require('./utils/observable');
module.exports = {
async init() {
if (this._initialized) return;
this.initConfig();
this.initGlobals();
this.docs = new Observable();
this.events = new Observable();
this._initialized = true;
},
async init() {
if (this._initialized) return;
this.initConfig();
this.initGlobals();
this.docs = new Observable();
this.events = new Observable();
this._initialized = true;
},
initConfig() {
this.config = {
serverURL: '',
backend: 'sqlite',
port: 8000
};
},
initConfig() {
this.config = {
serverURL: '',
backend: 'sqlite',
port: 8000
};
},
initGlobals() {
this.metaCache = {};
this.models = {};
this.forms = {};
this.views = {};
this.flags = {};
this.methods = {};
// temp params while calling routes
this.params = {};
},
initGlobals() {
this.metaCache = {};
this.models = {};
this.forms = {};
this.views = {};
this.flags = {};
this.methods = {};
// temp params while calling routes
this.params = {};
},
registerLibs(common) {
// add standard libs and utils to frappe
common.initLibs(this);
},
registerLibs(common) {
// add standard libs and utils to frappe
common.initLibs(this);
},
registerModels(models, type) {
// register models from app/models/index.js
const toAdd = Object.assign({}, models.models);
registerModels(models, type) {
// register models from app/models/index.js
const toAdd = Object.assign({}, models.models);
// post process based on type
if (models[type]) {
models[type](toAdd);
}
Object.assign(this.models, toAdd);
},
registerView(view, name, module) {
if (!this.views[view]) this.views[view] = {};
this.views[view][name] = module;
},
registerMethod({method, handler}) {
this.methods[method] = handler;
if (this.app) {
// add to router if client-server
this.app.post(`/api/method/${method}`, this.asyncHandler(async function(request, response) {
let data = await handler(request.body);
if (data === undefined) {
data = {}
}
return response.json(data);
}));
}
},
async call({method, args}) {
if (this.isServer) {
if (this.methods[method]) {
return await this.methods[method](args);
} else {
throw `${method} not found`;
}
}
let url = `/api/method/${method}`;
let response = await fetch(url, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(args || {})
});
return await response.json();
},
addToCache(doc) {
if (!this.docs) return;
// add to `docs` cache
if (doc.doctype && doc.name) {
if (!this.docs[doc.doctype]) {
this.docs[doc.doctype] = {};
}
this.docs[doc.doctype][doc.name] = doc;
// singles available as first level objects too
if (doc.doctype === doc.name) {
this[doc.name] = doc;
}
// propogate change to `docs`
doc.on('change', params => {
this.docs.trigger('change', params);
});
}
},
isDirty(doctype, name) {
return (this.docs && this.docs[doctype] && this.docs[doctype][name]
&& this.docs[doctype][name]._dirty) || false;
},
getDocFromCache(doctype, name) {
if (this.docs && this.docs[doctype] && this.docs[doctype][name]) {
return this.docs[doctype][name];
}
},
getMeta(doctype) {
if (!this.metaCache[doctype]) {
let model = this.models[doctype];
if (!model) {
throw `${doctype} is not a registered doctype`;
}
let metaClass = model.metaClass || this.BaseMeta;
this.metaCache[doctype] = new metaClass(model);
}
return this.metaCache[doctype];
},
async getDoc(doctype, name) {
let doc = this.getDocFromCache(doctype, name);
if (!doc) {
doc = new (this.getDocumentClass(doctype))({doctype:doctype, name: name});
await doc.load();
this.addToCache(doc);
}
return doc;
},
getDocumentClass(doctype) {
const meta = this.getMeta(doctype);
return meta.documentClass || this.BaseDocument;
},
async getSingle(doctype) {
return await this.getDoc(doctype, doctype);
},
async getDuplicate(doc) {
const newDoc = await this.getNewDoc(doc.doctype);
for (let field of this.getMeta(doc.doctype).getValidFields()) {
if (['name', 'submitted'].includes(field.fieldname)) continue;
if (field.fieldtype === 'Table') {
newDoc[field.fieldname] = (doc[field.fieldname] || []).map(d => {
let newd = Object.assign({}, d);
newd.name = '';
return newd;
});
} else {
newDoc[field.fieldname] = doc[field.fieldname];
}
}
return newDoc;
},
async getNewDoc(doctype) {
let doc = this.newDoc({doctype: doctype});
doc._notInserted = true;
doc.name = this.getRandomString();
this.addToCache(doc);
return doc;
},
newDoc(data) {
let doc = new (this.getDocumentClass(data.doctype))(data);
doc.setDefaults();
return doc;
},
async insert(data) {
return await (this.newDoc(data)).insert();
},
async syncDoc(data) {
let doc;
if (await this.db.exists(data.doctype, data.name)) {
doc = await this.getDoc(data.doctype, data.name);
Object.assign(doc, data);
await doc.update();
} else {
doc = this.newDoc(data);
await doc.insert();
}
},
// only for client side
async login(email, password) {
if (email === 'Administrator') {
this.session = {
user: 'Administrator'
}
return;
}
let response = await fetch(this.getServerURL() + '/api/login', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({ email, password })
});
if (response.status === 200) {
const res = await response.json();
this.session = {
user: email,
token: res.token
}
return res;
}
return response;
},
async signup(email, fullName, password) {
let response = await fetch(this.getServerURL() + '/api/signup', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({ email, fullName, password })
});
if (response.status === 200) {
return await response.json();
}
return response;
},
getServerURL() {
return this.config.serverURL || '';
},
close() {
this.db.close();
if (this.server) {
this.server.close();
}
// post process based on type
if (models[type]) {
models[type](toAdd);
}
Object.assign(this.models, toAdd);
},
registerView(view, name, module) {
if (!this.views[view]) this.views[view] = {};
this.views[view][name] = module;
},
registerMethod({ method, handler }) {
this.methods[method] = handler;
if (this.app) {
// add to router if client-server
this.app.post(
`/api/method/${method}`,
this.asyncHandler(async function(request, response) {
let data = await handler(request.body);
if (data === undefined) {
data = {};
}
return response.json(data);
})
);
}
},
async call({ method, args }) {
if (this.isServer) {
if (this.methods[method]) {
return await this.methods[method](args);
} else {
throw `${method} not found`;
}
}
let url = `/api/method/${method}`;
let response = await fetch(url, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(args || {})
});
return await response.json();
},
addToCache(doc) {
if (!this.docs) return;
// add to `docs` cache
if (doc.doctype && doc.name) {
if (!this.docs[doc.doctype]) {
this.docs[doc.doctype] = {};
}
this.docs[doc.doctype][doc.name] = doc;
// singles available as first level objects too
if (doc.doctype === doc.name) {
this[doc.name] = doc;
}
// propogate change to `docs`
doc.on('change', params => {
this.docs.trigger('change', params);
});
}
},
isDirty(doctype, name) {
return (
(this.docs &&
this.docs[doctype] &&
this.docs[doctype][name] &&
this.docs[doctype][name]._dirty) ||
false
);
},
getDocFromCache(doctype, name) {
if (this.docs && this.docs[doctype] && this.docs[doctype][name]) {
return this.docs[doctype][name];
}
},
getMeta(doctype) {
if (!this.metaCache[doctype]) {
let model = this.models[doctype];
if (!model) {
throw `${doctype} is not a registered doctype`;
}
let metaClass = model.metaClass || this.BaseMeta;
this.metaCache[doctype] = new metaClass(model);
}
return this.metaCache[doctype];
},
async getDoc(doctype, name) {
let doc = this.getDocFromCache(doctype, name);
if (!doc) {
doc = new (this.getDocumentClass(doctype))({
doctype: doctype,
name: name
});
await doc.load();
this.addToCache(doc);
}
return doc;
},
getDocumentClass(doctype) {
const meta = this.getMeta(doctype);
return meta.documentClass || this.BaseDocument;
},
async getSingle(doctype) {
return await this.getDoc(doctype, doctype);
},
async getDuplicate(doc) {
const newDoc = await this.getNewDoc(doc.doctype);
for (let field of this.getMeta(doc.doctype).getValidFields()) {
if (['name', 'submitted'].includes(field.fieldname)) continue;
if (field.fieldtype === 'Table') {
newDoc[field.fieldname] = (doc[field.fieldname] || []).map(d => {
let newd = Object.assign({}, d);
newd.name = '';
return newd;
});
} else {
newDoc[field.fieldname] = doc[field.fieldname];
}
}
return newDoc;
},
async getNewDoc(doctype) {
let doc = this.newDoc({ doctype: doctype });
doc._notInserted = true;
doc.name = this.getRandomString();
this.addToCache(doc);
return doc;
},
async newCustomDoc(fields) {
let doc = new this.BaseDocument({ isCustom: 1, fields });
doc._notInserted = true;
doc.name = this.getRandomString();
this.addToCache(doc);
return doc;
},
createMeta(fields) {
let meta = new this.BaseMeta({ isCustom: 1, fields });
return meta;
},
newDoc(data) {
let doc = new (this.getDocumentClass(data.doctype))(data);
doc.setDefaults();
return doc;
},
async insert(data) {
return await this.newDoc(data).insert();
},
async syncDoc(data) {
let doc;
if (await this.db.exists(data.doctype, data.name)) {
doc = await this.getDoc(data.doctype, data.name);
Object.assign(doc, data);
await doc.update();
} else {
doc = this.newDoc(data);
await doc.insert();
}
},
// only for client side
async login(email, password) {
if (email === 'Administrator') {
this.session = {
user: 'Administrator'
};
return;
}
let response = await fetch(this.getServerURL() + '/api/login', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({ email, password })
});
if (response.status === 200) {
const res = await response.json();
this.session = {
user: email,
token: res.token
};
return res;
}
return response;
},
async signup(email, fullName, password) {
let response = await fetch(this.getServerURL() + '/api/signup', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({ email, fullName, password })
});
if (response.status === 200) {
return await response.json();
}
return response;
},
getServerURL() {
return this.config.serverURL || '';
},
close() {
this.db.close();
if (this.server) {
this.server.close();
}
}
};

View File

@ -3,354 +3,375 @@ const Observable = require('frappejs/utils/observable');
const naming = require('./naming');
module.exports = class BaseDocument extends Observable {
constructor(data) {
super();
this.fetchValuesCache = {};
this.flags = {};
this.setup();
Object.assign(this, data);
constructor(data) {
super();
this.fetchValuesCache = {};
this.flags = {};
this.setup();
Object.assign(this, data);
// clear fetch-values cache
frappe.db.on('change', (params) => this.fetchValuesCache[`${params.doctype}:${params.name}`] = {});
// clear fetch-values cache
frappe.db.on(
'change',
params => (this.fetchValuesCache[`${params.doctype}:${params.name}`] = {})
);
}
setup() {
// add listeners
}
get meta() {
if (this.isCustom) {
this._meta = frappe.createMeta(this.fields);
}
if (!this._meta) {
this._meta = frappe.getMeta(this.doctype);
}
return this._meta;
}
async getSettings() {
if (!this._settings) {
this._settings = await frappe.getSingle(this.meta.settings);
}
return this._settings;
}
// set value and trigger change
async set(fieldname, value) {
if (typeof fieldname === 'object') {
const valueDict = fieldname;
for (let fieldname in valueDict) {
await this.set(fieldname, valueDict[fieldname]);
}
return;
}
setup() {
// add listeners
if (this[fieldname] !== value) {
this._dirty = true;
this[fieldname] = await this.validateField(fieldname, value);
await this.applyChange(fieldname);
}
}
get meta() {
if (!this._meta) {
this._meta = frappe.getMeta(this.doctype);
async applyChange(fieldname) {
if (await this.applyFormula()) {
// multiple changes
await this.trigger('change', {
doc: this
});
} else {
// no other change, trigger control refresh
await this.trigger('change', {
doc: this,
fieldname: fieldname
});
}
}
setDefaults() {
for (let field of this.meta.fields) {
if (
this[field.fieldname] === null ||
this[field.fieldname] === undefined
) {
let defaultValue = null;
if (field.fieldtype === 'Table') {
defaultValue = [];
}
return this._meta;
}
async getSettings() {
if (!this._settings) {
this._settings = await frappe.getSingle(this.meta.settings);
}
return this._settings;
}
// set value and trigger change
async set(fieldname, value) {
if (typeof fieldname === 'object') {
const valueDict = fieldname;
for (let fieldname in valueDict) {
await this.set(fieldname, valueDict[fieldname]);
}
return;
if (field.default) {
defaultValue = field.default;
}
if (this[fieldname] !== value) {
this._dirty = true;
this[fieldname] = await this.validateField(fieldname, value);
await this.applyChange(fieldname);
}
this[field.fieldname] = defaultValue;
}
}
}
async applyChange(fieldname) {
if (await this.applyFormula()) {
// multiple changes
await this.trigger('change', {
doc: this
});
} else {
// no other change, trigger control refresh
await this.trigger('change', {
doc: this,
fieldname: fieldname
});
}
setKeywords() {
let keywords = [];
for (let fieldname of this.meta.getKeywordFields()) {
keywords.push(this[fieldname]);
}
this.keywords = keywords.join(', ');
}
setDefaults() {
for (let field of this.meta.fields) {
if (this[field.fieldname] === null || this[field.fieldname] === undefined) {
let defaultValue = null;
if (field.fieldtype === 'Table') {
defaultValue = [];
}
if (field.default) {
defaultValue = field.default;
}
this[field.fieldname] = defaultValue;
}
}
append(key, document) {
if (!this[key]) {
this[key] = [];
}
this[key].push(this.initDoc(document));
}
setKeywords() {
let keywords = [];
for (let fieldname of this.meta.getKeywordFields()) {
keywords.push(this[fieldname]);
}
this.keywords = keywords.join(', ');
initDoc(data) {
if (data.prototype instanceof Document) {
return data;
} else {
return new Document(data);
}
}
append(key, document) {
if (!this[key]) {
this[key] = [];
}
this[key].push(this.initDoc(document));
async validateField(key, value) {
let field = this.meta.getField(key);
if (field && field.fieldtype == 'Select') {
return this.meta.validateSelect(field, value);
}
return value;
}
initDoc(data) {
if (data.prototype instanceof Document) {
return data;
} else {
return new Document(data);
}
getValidDict() {
let data = {};
for (let field of this.meta.getValidFields()) {
data[field.fieldname] = this[field.fieldname];
}
return data;
}
async validateField(key, value) {
let field = this.meta.getField(key);
if (field && field.fieldtype == 'Select') {
return this.meta.validateSelect(field, value);
}
return value;
}
getFullDict() {
let data = this.getValidDict();
return data;
}
getValidDict() {
let data = {};
for (let field of this.meta.getValidFields()) {
data[field.fieldname] = this[field.fieldname];
}
return data;
}
getFullDict() {
let data = this.getValidDict();
return data;
}
setStandardValues() {
// set standard values on server-side only
if (frappe.isServer) {
let now = (new Date()).toISOString();
if (!this.submitted) {
this.submitted = 0;
}
if (!this.owner) {
this.owner = frappe.session.user;
}
if (!this.creation) {
this.creation = now;
}
if (!this.modifiedBy) {
this.modifiedBy = frappe.session.user;
}
this.modified = now;
}
}
async load() {
let data = await frappe.db.get(this.doctype, this.name);
if (data.name) {
this.syncValues(data);
if (this.meta.isSingle) {
this.setDefaults();
}
} else {
throw new frappe.errors.NotFound(`Not Found: ${this.doctype} ${this.name}`);
}
}
syncValues(data) {
this.clearValues();
Object.assign(this, data);
this._dirty = false;
this.trigger('change', {
doc: this
});
}
clearValues() {
for (let field of this.meta.getValidFields()) {
if (this[field.fieldname]) {
delete this[field.fieldname];
}
}
}
setChildIdx() {
// renumber children
for (let field of this.meta.getValidFields()) {
if (field.fieldtype === 'Table') {
for (let i = 0; i < (this[field.fieldname] || []).length; i++) {
this[field.fieldname][i].idx = i;
}
}
}
}
async compareWithCurrentDoc() {
if (frappe.isServer && !this.isNew()) {
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.flags.submitAction = true;
}
if (currentDoc.submitted && !this.submitted) {
this.flags.revertAction = true;
}
}
}
async applyFormula() {
if (!this.meta.hasFormula()) {
return false;
}
let doc = this;
// children
for (let tablefield of this.meta.getTableFields()) {
let formulaFields = frappe.getMeta(tablefield.childtype).getFormulaFields();
if (formulaFields.length) {
// for each row
for (let row of this[tablefield.fieldname]) {
for (let field of formulaFields) {
if (shouldApplyFormula(field, row)) {
const val = await field.formula(row, doc);
if (val !== false && val !== undefined) {
row[field.fieldname] = val;
}
}
}
}
}
}
// parent
for (let field of this.meta.getFormulaFields()) {
if (shouldApplyFormula(field, doc)) {
const val = await field.formula(doc);
if (val !== false && val !== undefined) {
doc[field.fieldname] = val;
}
}
}
return true;
function shouldApplyFormula(field, doc) {
if (field.readOnly) {
return true;
}
if (!frappe.isServer || frappe.isElectron) {
if (doc[field.fieldname] == null || doc[field.fieldname] == '') {
return true;
}
}
return false;
}
}
async commit() {
// re-run triggers
this.setStandardValues();
this.setKeywords();
this.setChildIdx();
await this.applyFormula();
await this.trigger('validate');
}
async insert() {
await naming.setName(this);
await this.commit();
await this.trigger('beforeInsert');
const data = await frappe.db.insert(this.doctype, this.getValidDict());
this.syncValues(data);
await this.trigger('afterInsert');
await this.trigger('afterSave');
return this;
}
async update() {
await this.compareWithCurrentDoc();
await this.commit();
await this.trigger('beforeUpdate');
// before submit
if (this.flags.submitAction) await this.trigger('beforeSubmit');
if (this.flags.revertAction) await this.trigger('beforeRevert');
const data = await frappe.db.update(this.doctype, this.getValidDict());
this.syncValues(data);
await this.trigger('afterUpdate');
await this.trigger('afterSave');
// after submit
if (this.flags.submitAction) await this.trigger('afterSubmit');
if (this.flags.revertAction) await this.trigger('afterRevert');
return this;
}
async delete() {
await this.trigger('beforeDelete');
await frappe.db.delete(this.doctype, this.name);
await this.trigger('afterDelete');
}
async submit() {
this.submitted = 1;
this.update();
}
async revert() {
setStandardValues() {
// set standard values on server-side only
if (frappe.isServer) {
let now = new Date().toISOString();
if (!this.submitted) {
this.submitted = 0;
this.update();
}
}
// trigger methods on the class if they match
// with the trigger name
async trigger(event, params) {
if (this[event]) {
await this[event](params);
if (!this.owner) {
this.owner = frappe.session.user;
}
if (!this.creation) {
this.creation = now;
}
if (!this.modifiedBy) {
this.modifiedBy = frappe.session.user;
}
this.modified = now;
}
}
async load() {
let data = await frappe.db.get(this.doctype, this.name);
if (data.name) {
this.syncValues(data);
if (this.meta.isSingle) {
this.setDefaults();
}
} else {
throw new frappe.errors.NotFound(
`Not Found: ${this.doctype} ${this.name}`
);
}
}
syncValues(data) {
this.clearValues();
Object.assign(this, data);
this._dirty = false;
this.trigger('change', {
doc: this
});
}
clearValues() {
for (let field of this.meta.getValidFields()) {
if (this[field.fieldname]) {
delete this[field.fieldname];
}
}
}
setChildIdx() {
// renumber children
for (let field of this.meta.getValidFields()) {
if (field.fieldtype === 'Table') {
for (let i = 0; i < (this[field.fieldname] || []).length; i++) {
this[field.fieldname][i].idx = i;
}
await super.trigger(event, params);
}
}
}
async compareWithCurrentDoc() {
if (frappe.isServer && !this.isNew()) {
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.flags.submitAction = true;
}
if (currentDoc.submitted && !this.submitted) {
this.flags.revertAction = true;
}
}
}
async applyFormula() {
if (!this.meta.hasFormula()) {
return false;
}
// helper functions
getSum(tablefield, childfield) {
return this[tablefield].map(d => (d[childfield] || 0)).reduce((a, b) => a + b, 0);
}
let doc = this;
async getFrom(doctype, name, fieldname) {
if (!name) return '';
let _values = this.fetchValuesCache[`${doctype}:${name}`] || (this.fetchValuesCache[`${doctype}:${name}`] = {});
if (!_values[fieldname]) {
_values[fieldname] = await frappe.db.getValue(doctype, name, fieldname);
// children
for (let tablefield of this.meta.getTableFields()) {
let formulaFields = frappe
.getMeta(tablefield.childtype)
.getFormulaFields();
if (formulaFields.length) {
// for each row
for (let row of this[tablefield.fieldname]) {
for (let field of formulaFields) {
if (shouldApplyFormula(field, row)) {
const val = await field.formula(row, doc);
if (val !== false && val !== undefined) {
row[field.fieldname] = val;
}
}
}
}
return _values[fieldname];
}
}
isNew() {
return this._notInserted;
// parent
for (let field of this.meta.getFormulaFields()) {
if (shouldApplyFormula(field, doc)) {
const val = await field.formula(doc);
if (val !== false && val !== undefined) {
doc[field.fieldname] = val;
}
}
}
};
return true;
function shouldApplyFormula(field, doc) {
if (field.readOnly) {
return true;
}
if (!frappe.isServer || frappe.isElectron) {
if (doc[field.fieldname] == null || doc[field.fieldname] == '') {
return true;
}
}
return false;
}
}
async commit() {
// re-run triggers
this.setStandardValues();
this.setKeywords();
this.setChildIdx();
await this.applyFormula();
await this.trigger('validate');
}
async insert() {
await naming.setName(this);
await this.commit();
await this.trigger('beforeInsert');
const data = await frappe.db.insert(this.doctype, this.getValidDict());
this.syncValues(data);
await this.trigger('afterInsert');
await this.trigger('afterSave');
return this;
}
async update() {
await this.compareWithCurrentDoc();
await this.commit();
await this.trigger('beforeUpdate');
// before submit
if (this.flags.submitAction) await this.trigger('beforeSubmit');
if (this.flags.revertAction) await this.trigger('beforeRevert');
const data = await frappe.db.update(this.doctype, this.getValidDict());
this.syncValues(data);
await this.trigger('afterUpdate');
await this.trigger('afterSave');
// after submit
if (this.flags.submitAction) await this.trigger('afterSubmit');
if (this.flags.revertAction) await this.trigger('afterRevert');
return this;
}
async delete() {
await this.trigger('beforeDelete');
await frappe.db.delete(this.doctype, this.name);
await this.trigger('afterDelete');
}
async submit() {
this.submitted = 1;
this.update();
}
async revert() {
this.submitted = 0;
this.update();
}
// trigger methods on the class if they match
// with the trigger name
async trigger(event, params) {
if (this[event]) {
await this[event](params);
}
await super.trigger(event, params);
}
// helper functions
getSum(tablefield, childfield) {
return this[tablefield]
.map(d => d[childfield] || 0)
.reduce((a, b) => a + b, 0);
}
async getFrom(doctype, name, fieldname) {
if (!name) return '';
let _values =
this.fetchValuesCache[`${doctype}:${name}`] ||
(this.fetchValuesCache[`${doctype}:${name}`] = {});
if (!_values[fieldname]) {
_values[fieldname] = await frappe.db.getValue(doctype, name, fieldname);
}
return _values[fieldname];
}
isNew() {
return this._notInserted;
}
};

View File

@ -1,8 +1,8 @@
import ModalContainer from './ModalContainer';
import frappe from 'frappejs';
const Plugin = {
install (Vue) {
install(Vue) {
this.event = new Vue();
Vue.prototype.$modal = {
@ -13,13 +13,15 @@ const Plugin = {
hide(id) {
Plugin.event.$emit('hide', id);
}
}
};
frappe.showModal = Vue.prototype.$modal.show;
// create modal container
const div = document.createElement('div');
document.body.appendChild(div);
new Vue({ render: h => h(ModalContainer) }).$mount(div);
}
}
};
export default Plugin;
export default Plugin;

View File

@ -1,24 +1,24 @@
<script>
import Base from './Base';
export default {
extends: Base,
methods: {
getInputTag() {
return 'textarea';
},
getInputAttrs() {
return {
id: this.id,
required: this.docfield.required,
rows: 3,
disabled: this.disabled
};
},
getDomProps() {
return {
value: this.value
}
}
extends: Base,
methods: {
getInputTag() {
return 'textarea';
},
getInputAttrs() {
return {
id: this.id,
required: this.docfield.required,
rows: this.docfield.rows || 3,
disabled: this.disabled
};
},
getDomProps() {
return {
value: this.value
};
}
}
}
};
</script>

View File

@ -1,24 +1,24 @@
<template>
<div class="row pb-4">
<frappe-control
class="col-3"
v-for="docfield in filters"
:key="docfield.fieldname"
:docfield="docfield"
:value="$data.filterValues[docfield.fieldname]"
:doc="$data.filterValues"
@change="updateValue(docfield.fieldname, $event)"
/>
<div class="col-3" v-for="docfield in filterFields" :key="docfield.fieldname">
<frappe-control
v-if="shouldRenderField(docfield)"
:docfield="docfield"
:value="$data.filterValues[docfield.fieldname]"
:doc="filterDoc"
@change="updateValue(docfield.fieldname, $event)"
/>
</div>
</div>
</template>
<script>
import FrappeControl from 'frappejs/ui/components/controls/FrappeControl';
export default {
props: ['filters', 'filterDefaults'],
props: ['filterFields', 'filterDoc', 'filterDefaults'],
data() {
const filterValues = {};
for (let filter of this.filters) {
for (let filter of this.filterFields) {
filterValues[filter.fieldname] =
this.filterDefaults[filter.fieldname] || null;
}
@ -34,7 +34,26 @@ export default {
}
},
methods: {
shouldRenderField(field) {
let hidden;
try {
hidden = Boolean(field.hidden(this.filterDoc));
} catch (e) {
hidden = Boolean(field.hidden) || false;
}
if (hidden) {
return false;
}
if (field.fieldname === 'name' && !this.doc.isNew()) {
return false;
}
return true;
},
updateValue(fieldname, value) {
this.filterDoc.set(fieldname, value);
this.filterValues[fieldname] = value;
this.$emit('change', this.filterValues);
}