diff --git a/index.js b/index.js index 3afb4ecb..abf366da 100644 --- a/index.js +++ b/index.js @@ -7,11 +7,12 @@ client.start({ server: 'localhost:8000', container: document.querySelector('.wrapper'), }).then(() => { - frappe.todo_module = require('frappejs/models/doctype/todo/todo.js'); - frappe.account_module = require('./models/doctype/account/account.js'); - frappe.init_controller('account', frappe.account_module); - frappe.init_controller('todo', frappe.todo_module); + // require modules + frappe.modules.todo = require('frappejs/models/doctype/todo/todo.js'); + frappe.modules.account = require('./models/doctype/account/account.js'); + frappe.modules.todo_client = require('frappejs/models/doctype/todo/todo_client.js'); + frappe.modules.account_client = require('./models/doctype/account/account_client.js'); frappe.desk.add_sidebar_item('ToDo', '#list/todo'); frappe.desk.add_sidebar_item('Accounts', '#list/account'); diff --git a/js/bundle.js b/js/bundle.js index 66ded3ea..695f5ddc 100644 --- a/js/bundle.js +++ b/js/bundle.js @@ -60,7 +60,7 @@ /******/ __webpack_require__.p = "/"; /******/ /******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 4); +/******/ return __webpack_require__(__webpack_require__.s = 6); /******/ }) /************************************************************************/ /******/ ([ @@ -84,6 +84,7 @@ module.exports = { init_globals() { this.meta_cache = {}; + this.modules = {}; this.docs = {}; this.flags = { cache_docs: false @@ -110,34 +111,46 @@ module.exports = { get_meta(doctype) { if (!this.meta_cache[doctype]) { - this.meta_cache[doctype] = new (this.models.get_meta_class(doctype))(this.models.get('DocType', doctype)); + this.meta_cache[doctype] = new (this.get_meta_class(doctype))(); } return this.meta_cache[doctype]; }, - init_controller(doctype, module) { + get_meta_class(doctype) { doctype = this.slug(doctype); - this.models.controllers[doctype] = module[doctype]; - this.models.meta_classes[doctype] = module[doctype + '_meta']; + if (this.modules[doctype] && this.modules[doctype].Meta) { + return this.modules[doctype].Meta; + } else { + return this.BaseMeta; + } }, async get_doc(data, name) { if (typeof data==='string' && typeof name==='string') { let doc = this.get_doc_from_cache(data, name); if (!doc) { - let controller_class = this.models.get_controller(data); + let controller_class = this.get_controller_class(data); doc = new controller_class({doctype:data, name: name}); await doc.load(); this.add_to_cache(doc); } return doc; } else { - let controller_class = this.models.get_controller(data.doctype); + let controller_class = this.get_controller_class(data.doctype); var doc = new controller_class(data); } return doc; }, + get_controller_class(doctype) { + doctype = this.slug(doctype); + if (this.modules[doctype] && this.modules[doctype].Document) { + return this.modules[doctype].Document; + } else { + return this.BaseDocument; + } + }, + async get_new_doc(doctype) { let doc = await frappe.get_doc({doctype: doctype}); doc.set_name(); @@ -174,304 +187,7 @@ module.exports = { const frappe = __webpack_require__(0); -class BaseControl { - constructor(docfield, form) { - Object.assign(this, docfield); - this.form = form; - if (!this.fieldname) { - this.fieldname = frappe.slug(this.label); - } - this.parent = form.form; - if (this.setup) { - this.setup(); - } - } - - bind(doc) { - this.doc = doc; - - this.doc.add_handler(this.fieldname, () => { - this.set_doc_value(); - }); - - this.set_doc_value(); - } - - refresh() { - this.make(); - this.set_doc_value(); - } - - set_doc_value() { - if (this.doc) { - this.set_input_value(this.doc.get(this.fieldname)); - } - } - - make() { - if (!this.form_group) { - this.make_form_group(); - this.make_label(); - this.make_input(); - this.set_input_name(); - this.make_description(); - this.bind_change_event(); - } - } - - make_form_group() { - this.form_group = frappe.ui.add('div', 'form-group', this.parent); - } - - make_label() { - this.label_element = frappe.ui.add('label', null, this.form_group); - this.label_element.textContent = this.label; - } - - make_input() { - this.input = frappe.ui.add('input', 'form-control', this.form_group); - } - - set_input_name() { - this.input.setAttribute('name', this.fieldname); - } - - make_description() { - if (this.description) { - this.description_element = frappe.ui.add('small', 'form-text text-muted', this.form_group); - this.description_element.textContent = this.description; - } - } - - set_input_value(value) { - if (value === undefined || value === null) { - value = ''; - } - this.input.value = value; - } - - async get_input_value() { - return await this.parse(this.input.value); - } - - async parse(value) { - return value; - } - - async validate(value) { - return value; - } - - bind_change_event() { - this.input.addEventListener('change', (e) => this.handle_change(e)); - } - - async handle_change(e) { - let value = await this.get_input_value(); - value = await this.validate(value); - await this.doc.set(this.fieldname, value); - } -} - -module.exports = BaseControl; - -/***/ }), -/* 2 */ -/***/ (function(module, exports) { - -// shim for using process in browser -var process = module.exports = {}; - -// cached from whatever global is present so that test runners that stub it -// don't break things. But we need to wrap it in a try catch in case it is -// wrapped in strict mode code which doesn't define any globals. It's inside a -// function because try/catches deoptimize in certain engines. - -var cachedSetTimeout; -var cachedClearTimeout; - -function defaultSetTimout() { - throw new Error('setTimeout has not been defined'); -} -function defaultClearTimeout () { - throw new Error('clearTimeout has not been defined'); -} -(function () { - try { - if (typeof setTimeout === 'function') { - cachedSetTimeout = setTimeout; - } else { - cachedSetTimeout = defaultSetTimout; - } - } catch (e) { - cachedSetTimeout = defaultSetTimout; - } - try { - if (typeof clearTimeout === 'function') { - cachedClearTimeout = clearTimeout; - } else { - cachedClearTimeout = defaultClearTimeout; - } - } catch (e) { - cachedClearTimeout = defaultClearTimeout; - } -} ()) -function runTimeout(fun) { - if (cachedSetTimeout === setTimeout) { - //normal enviroments in sane situations - return setTimeout(fun, 0); - } - // if setTimeout wasn't available but was latter defined - if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { - cachedSetTimeout = setTimeout; - return setTimeout(fun, 0); - } - try { - // when when somebody has screwed with setTimeout but no I.E. maddness - return cachedSetTimeout(fun, 0); - } catch(e){ - try { - // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally - return cachedSetTimeout.call(null, fun, 0); - } catch(e){ - // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error - return cachedSetTimeout.call(this, fun, 0); - } - } - - -} -function runClearTimeout(marker) { - if (cachedClearTimeout === clearTimeout) { - //normal enviroments in sane situations - return clearTimeout(marker); - } - // if clearTimeout wasn't available but was latter defined - if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { - cachedClearTimeout = clearTimeout; - return clearTimeout(marker); - } - try { - // when when somebody has screwed with setTimeout but no I.E. maddness - return cachedClearTimeout(marker); - } catch (e){ - try { - // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally - return cachedClearTimeout.call(null, marker); - } catch (e){ - // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. - // Some versions of I.E. have different rules for clearTimeout vs setTimeout - return cachedClearTimeout.call(this, marker); - } - } - - - -} -var queue = []; -var draining = false; -var currentQueue; -var queueIndex = -1; - -function cleanUpNextTick() { - if (!draining || !currentQueue) { - return; - } - draining = false; - if (currentQueue.length) { - queue = currentQueue.concat(queue); - } else { - queueIndex = -1; - } - if (queue.length) { - drainQueue(); - } -} - -function drainQueue() { - if (draining) { - return; - } - var timeout = runTimeout(cleanUpNextTick); - draining = true; - - var len = queue.length; - while(len) { - currentQueue = queue; - queue = []; - while (++queueIndex < len) { - if (currentQueue) { - currentQueue[queueIndex].run(); - } - } - queueIndex = -1; - len = queue.length; - } - currentQueue = null; - draining = false; - runClearTimeout(timeout); -} - -process.nextTick = function (fun) { - var args = new Array(arguments.length - 1); - if (arguments.length > 1) { - for (var i = 1; i < arguments.length; i++) { - args[i - 1] = arguments[i]; - } - } - queue.push(new Item(fun, args)); - if (queue.length === 1 && !draining) { - runTimeout(drainQueue); - } -}; - -// v8 likes predictible objects -function Item(fun, array) { - this.fun = fun; - this.array = array; -} -Item.prototype.run = function () { - this.fun.apply(null, this.array); -}; -process.title = 'browser'; -process.browser = true; -process.env = {}; -process.argv = []; -process.version = ''; // empty string to avoid regexp issues -process.versions = {}; - -function noop() {} - -process.on = noop; -process.addListener = noop; -process.once = noop; -process.off = noop; -process.removeListener = noop; -process.removeAllListeners = noop; -process.emit = noop; -process.prependListener = noop; -process.prependOnceListener = noop; - -process.listeners = function (name) { return [] } - -process.binding = function (name) { - throw new Error('process.binding is not supported'); -}; - -process.cwd = function () { return '/' }; -process.chdir = function (dir) { - throw new Error('process.chdir is not supported'); -}; -process.umask = function() { return 0; }; - - -/***/ }), -/* 3 */ -/***/ (function(module, exports, __webpack_require__) { - -const frappe = __webpack_require__(0); - -class Document { +module.exports = class BaseDocument { constructor(data) { this.handlers = {}; this.setup(); @@ -616,26 +332,513 @@ class Document { } }; -module.exports = { Document: Document }; +/***/ }), +/* 2 */ +/***/ (function(module, exports, __webpack_require__) { + +const frappe = __webpack_require__(0); + +class BaseControl { + constructor(docfield, form) { + Object.assign(this, docfield); + this.form = form; + if (!this.fieldname) { + this.fieldname = frappe.slug(this.label); + } + this.parent = form.form; + if (this.setup) { + this.setup(); + } + } + + bind(doc) { + this.doc = doc; + + this.doc.add_handler(this.fieldname, () => { + this.set_doc_value(); + }); + + this.set_doc_value(); + } + + refresh() { + this.make(); + this.set_doc_value(); + } + + set_doc_value() { + if (this.doc) { + this.set_input_value(this.doc.get(this.fieldname)); + } + } + + make() { + if (!this.form_group) { + this.make_form_group(); + this.make_label(); + this.make_input(); + this.set_input_name(); + this.make_description(); + this.bind_change_event(); + } + } + + make_form_group() { + this.form_group = frappe.ui.add('div', 'form-group', this.parent); + } + + make_label() { + this.label_element = frappe.ui.add('label', null, this.form_group); + this.label_element.textContent = this.label; + } + + make_input() { + this.input = frappe.ui.add('input', 'form-control', this.form_group); + } + + set_input_name() { + this.input.setAttribute('name', this.fieldname); + } + + make_description() { + if (this.description) { + this.description_element = frappe.ui.add('small', 'form-text text-muted', this.form_group); + this.description_element.textContent = this.description; + } + } + + set_input_value(value) { + if (value === undefined || value === null) { + value = ''; + } + this.input.value = value; + } + + async get_input_value() { + return await this.parse(this.input.value); + } + + async parse(value) { + return value; + } + + async validate(value) { + return value; + } + + bind_change_event() { + this.input.addEventListener('change', (e) => this.handle_change(e)); + } + + async handle_change(e) { + let value = await this.get_input_value(); + value = await this.validate(value); + await this.doc.set(this.fieldname, value); + } + + disable() { + this.input.setAttribute('disabled', 'disabled'); + } + + enable() { + this.input.removeAttribute('disabled'); + } +} + +module.exports = BaseControl; + +/***/ }), +/* 3 */ +/***/ (function(module, exports, __webpack_require__) { + +const BaseDocument = __webpack_require__(1); +const frappe = __webpack_require__(0); + +module.exports = class BaseMeta extends BaseDocument { + constructor(data) { + super(data); + this.event_handlers = {}; + this.list_options = { + fields: ['name', 'modified'] + }; + if (this.setup_meta) { + this.setup_meta(); + } + } + + get_field(fieldname) { + if (!this.field_map) { + this.field_map = {}; + for (let df of this.fields) { + this.field_map[df.fieldname] = df; + } + } + return this.field_map[fieldname]; + } + + on(key, fn) { + if (!this.event_handlers[key]) { + this.event_handlers[key] = []; + } + this.event_handlers[key].push(fn); + } + + async set(fieldname, value) { + this[fieldname] = value; + await this.trigger(fieldname); + } + + get(fieldname) { + return this[fieldname]; + } + + get_valid_fields() { + if (!this._valid_fields) { + this._valid_fields = []; + + const doctype_fields = this.fields.map((df) => df.fieldname); + + // standard fields + for (let df of frappe.model.standard_fields) { + if (frappe.db.type_map[df.fieldtype] && !doctype_fields.includes(df.fieldname)) { + this._valid_fields.push(df); + } + } + + // parent fields + if (this.istable) { + for (let df of frappe.model.child_fields) { + if (frappe.db.type_map[df.fieldtype] && !doctype_fields.includes(df.fieldname)) { + this._valid_fields.push(df); + } + } + } + + // doctype fields + for (let df of this.fields) { + if (frappe.db.type_map[df.fieldtype]) { + this._valid_fields.push(df); + } + } + } + + return this._valid_fields; + } + + get_keyword_fields() { + return this.keyword_fields || this.meta.fields.filter(df => df.reqd).map(df => df.fieldname); + } + + validate_select(df, value) { + let options = df.options; + if (typeof options === 'string') { + // values given as string + options = df.options.split('\n'); + } + if (!options.includes(value)) { + throw new frappe.errors.ValueError(`${value} must be one of ${options.join(", ")}`); + } + } + + async trigger(key, event = {}) { + + Object.assign(event, { + doc: this, + name: key + }); + + if (this.event_handlers[key]) { + for (var handler of this.event_handlers[key]) { + await handler(event); + } + } + } +} /***/ }), /* 4 */ /***/ (function(module, exports, __webpack_require__) { -__webpack_require__(5); +const frappe = __webpack_require__(0); -const client = __webpack_require__(10); +module.exports = class BaseList { + constructor({doctype, parent, fields}) { + this.doctype = doctype; + this.parent = parent; + this.fields = fields; + + this.meta = frappe.get_meta(this.doctype); + + this.start = 0; + this.page_length = 20; + + this.body = null; + this.rows = []; + this.data = []; + } + + async run() { + this.make_body(); + + let data = await this.get_data(); + + for (let i=0; i< Math.min(this.page_length, data.length); i++) { + this.render_row(this.start + i, data[i]); + } + + if (this.start > 0) { + this.data = this.data.concat(data); + } else { + this.data = data; + } + + this.clear_empty_rows(); + this.update_more(data.length > this.page_length); + } + + async get_data() { + return await frappe.db.get_all({ + doctype: this.doctype, + fields: this.get_fields(), + filters: this.get_filters(), + start: this.start, + limit: this.page_length + 1 + }); + } + + get_fields() { + return ['name']; + } + + async append() { + this.start += this.page_length; + await this.run(); + } + + get_filters() { + let filters = {}; + if (this.search_input.value) { + filters.keywords = ['like', '%' + this.search_input.value + '%']; + } + return filters; + } + + make_body() { + if (!this.body) { + this.make_toolbar(); + //this.make_new(); + this.body = frappe.ui.add('div', 'list-body', this.parent); + this.make_more_btn(); + } + } + + make_toolbar() { + this.toolbar = frappe.ui.add('div', 'list-toolbar', this.parent); + this.toolbar.innerHTML = ` +
+
+ +
+
+ + New + +
+
+ `; + + this.search_input = this.toolbar.querySelector('input'); + this.search_input.addEventListener('keypress', (event) => { + if (event.keyCode===13) { + this.run(); + } + }); + + this.search_button = this.toolbar.querySelector('.btn-search'); + this.search_button.addEventListener('click', (event) => { + this.run(); + }); + } + + make_more_btn() { + this.more_btn = frappe.ui.add('button', 'btn btn-secondary hide', this.parent); + this.more_btn.textContent = 'More'; + this.more_btn.addEventListener('click', () => { + this.append(); + }) + } + + render_row(i, data) { + let row = this.get_row(i); + row.innerHTML = this.get_row_html(data); + row.style.display = 'block'; + } + + get_row_html(data) { + return `Delete + + ` + + this.btn_submit = this.toolbar.querySelector('.btn-submit');; + this.btn_submit.addEventListener('click', async (event) => { + this.submit(); + event.preventDefault(); + }) + + this.btn_delete = this.toolbar.querySelector('.btn-delete'); + this.btn_delete.addEventListener('click', async () => { + await this.doc.delete(); + this.show_alert('Deleted', 'success'); + }); + } + + + show_alert(message, type) { + this.clear_alert(); + this.alert = frappe.ui.add('div', `alert alert-${type}`, this.body); + this.alert.textContent = message; + } + + clear_alert() { + if (this.alert) { + frappe.ui.remove(this.alert); + this.alert = null; + } + } + + async use(doc, is_new = false) { + if (this.doc) { + // clear handlers of outgoing doc + this.doc.clear_handlers(); + } + this.clear_alert(); + this.doc = doc; + this.is_new = is_new; + for (let control of this.controls_list) { + control.bind(this.doc); + } + } + + async submit() { + try { + if (this.is_new || this.doc.__not_inserted) { + await this.doc.insert(); + } else { + await this.doc.update(); + } + await this.refresh(); + this.show_alert('Saved', 'success'); + } catch (e) { + this.show_alert('Failed', 'danger'); + } + } + + refresh() { + for(let control of this.controls_list) { + control.refresh(); + } + } + +} + +/***/ }), +/* 6 */ +/***/ (function(module, exports, __webpack_require__) { + +__webpack_require__(7); + +const client = __webpack_require__(12); // start server client.start({ server: 'localhost:8000', container: document.querySelector('.wrapper'), }).then(() => { - frappe.todo_module = __webpack_require__(35); - frappe.account_module = __webpack_require__(37); - frappe.init_controller('account', frappe.account_module); - frappe.init_controller('todo', frappe.todo_module); + // require modules + frappe.modules.todo = __webpack_require__(34); + frappe.modules.account = __webpack_require__(36); + frappe.modules.todo_client = __webpack_require__(38); + frappe.modules.account_client = __webpack_require__(39); frappe.desk.add_sidebar_item('ToDo', '#list/todo'); frappe.desk.add_sidebar_item('Accounts', '#list/account'); @@ -646,13 +849,13 @@ client.start({ }); /***/ }), -/* 5 */ +/* 7 */ /***/ (function(module, exports, __webpack_require__) { // style-loader: Adds some css to the DOM by adding a