From 234eb9dea1fff8f4bae8f65c5bdd2a392a9faedc Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 10 Jan 2018 16:21:35 +0530 Subject: [PATCH] added text, select controls and fixes to form.js --- frappe/backends/rest_client.js | 33 +++++++++++ frappe/backends/sqlite.js | 63 +++++++++++++------- frappe/client/index.js | 1 + frappe/client/ui/index.js | 4 ++ frappe/client/view/controls/base.js | 15 ++++- frappe/client/view/controls/index.js | 4 +- frappe/client/view/controls/select.js | 24 ++++++++ frappe/client/view/controls/text.js | 13 +++++ frappe/client/view/form.js | 31 ++++++++-- frappe/models/doctype/todo/todo.js | 2 +- frappe/server/index.js | 8 ++- frappe/server/rest_api.js | 61 ++++++++++++++++++++ frappe/server/rest_server.js | 82 --------------------------- frappe/utils.js | 20 ++----- 14 files changed, 231 insertions(+), 130 deletions(-) create mode 100644 frappe/client/view/controls/select.js create mode 100644 frappe/client/view/controls/text.js create mode 100644 frappe/server/rest_api.js delete mode 100644 frappe/server/rest_server.js diff --git a/frappe/backends/rest_client.js b/frappe/backends/rest_client.js index 2f563097..9863970b 100644 --- a/frappe/backends/rest_client.js +++ b/frappe/backends/rest_client.js @@ -5,6 +5,9 @@ class RESTClient { constructor({server, protocol='http', fetch}) { this.server = server; this.protocol = protocol; + + this.init_type_map(); + frappe.fetch = fetch; this.json_headers = { 'Accept': 'application/json', @@ -78,6 +81,36 @@ class RESTClient { return await response.json(); } + init_type_map() { + this.type_map = { + 'Currency': true + ,'Int': true + ,'Float': true + ,'Percent': true + ,'Check': true + ,'Small Text': true + ,'Long Text': true + ,'Code': true + ,'Text Editor': true + ,'Date': true + ,'Datetime': true + ,'Time': true + ,'Text': true + ,'Data': true + ,'Link': true + ,'Dynamic Link':true + ,'Password': true + ,'Select': true + ,'Read Only': true + ,'Attach': true + ,'Attach Image':true + ,'Signature': true + ,'Color': true + ,'Barcode': true + ,'Geolocation': true + } + } + close() { } diff --git a/frappe/backends/sqlite.js b/frappe/backends/sqlite.js index 72fdd02c..d9162c0a 100644 --- a/frappe/backends/sqlite.js +++ b/frappe/backends/sqlite.js @@ -13,6 +13,8 @@ class sqliteDatabase { } return new Promise(resolve => { this.conn = new sqlite3.Database(this.db_path, () => { + // debug + // this.conn.on('trace', (trace) => console.log(trace)); resolve(); }); }); @@ -32,10 +34,14 @@ class sqliteDatabase { async create_table(doctype) { let meta = frappe.get_meta(doctype); let columns = []; + let values = []; for (let df of this.get_fields(meta)) { if (this.type_map[df.fieldtype]) { - columns.push(`${df.fieldname} ${this.type_map[df.fieldtype]} ${df.reqd ? "not null" : ""} ${df.default ? ("default " + frappe.sqlescape(df.default)) : ""}`); + columns.push(`${df.fieldname} ${this.type_map[df.fieldtype]} ${df.reqd ? "not null" : ""} ${df.default ? "default ?" : ""}`); + if (df.default) { + values.push(df.default); + } } } @@ -70,18 +76,29 @@ class sqliteDatabase { } async insert(doctype, doc) { + let placeholders = Object.keys(doc).map(d => '?').join(', '); return await this.run(`insert into ${frappe.slug(doctype)} (${Object.keys(doc).join(", ")}) - values (${Object.values(doc).map(d => frappe.db.escape(d)).join(", ")})`); + values (${placeholders})`, this.get_formatted_values(doc)); } async update(doctype, doc) { - let assigns = []; - for (let key in doc) { - assigns.push(`${key} = ${this.escape(doc[key])}`); - } + let assigns = Object.keys(doc).map(key => `${key} = ?`); + let values = this.get_formatted_values(doc); + values.push(doc.name); + return await this.run(`update ${frappe.slug(doctype)} - set ${assigns.join(", ")}`); + set ${assigns.join(", ")} where name=?`, values); + } + + get_formatted_values(doc) { + return Object.values(doc).map(value => { + if (value instanceof Date) { + return value.toISOString(); + } else { + return value; + } + }) } async delete(doctype, name) { @@ -89,14 +106,20 @@ class sqliteDatabase { } get_all({doctype, fields=['name'], filters, start, limit, order_by='modified', order='desc'} = {}) { - return new Promise(resolve => { + return new Promise((resolve, reject) => { + let conditions = this.get_filter_conditions(filters); + this.conn.all(`select ${fields.join(", ")} from ${frappe.slug(doctype)} - ${filters ? "where" : ""} ${this.get_filter_conditions(filters)} + ${conditions.conditions ? "where" : ""} ${conditions.conditions} ${order_by ? ("order by " + order_by) : ""} ${order_by ? (order || "asc") : ""} - ${limit ? ("limit " + limit) : ""} ${start ? ("offset " + start) : ""}`, + ${limit ? ("limit " + limit) : ""} ${start ? ("offset " + start) : ""}`, conditions.values, (err, rows) => { - resolve(rows); + if (err) { + reject(err); + } else { + resolve(rows); + } }); }); } @@ -106,22 +129,27 @@ class sqliteDatabase { // {"status": "Open", "name": ["like", "apple%"]} // => `status="Open" and name like "apple%" let conditions = []; + let values = []; for (let key in filters) { const value = filters[key]; if (value instanceof Array) { - conditions.push(`${key} ${value[0]} ${this.escape(value)}`); + conditions.push(`${key} ${value[0]} ?`); } else { - conditions.push(`${key} = ${this.escape(value)}`); + conditions.push(`${key} = ?`); } + values.push(value); } - return conditions.join(" and "); + return { + conditions: conditions.length ? conditions.join(" and ") : "", + values: values + }; } run(query, params) { - //console.log(query); return new Promise((resolve, reject) => { this.conn.run(query, params, (err) => { if (err) { + console.log(err); reject(err); } else { resolve(); @@ -131,7 +159,6 @@ class sqliteDatabase { } sql(query, params) { - //console.log(query); return new Promise((resolve) => { this.conn.all(query, params, (err, rows) => { resolve(rows); @@ -163,10 +190,6 @@ class sqliteDatabase { return row.length ? row[0][fieldname] : null; } - escape(value) { - return frappe.sqlescape(value); - } - get_fields(meta) { // add standard fields let fields = frappe.model.standard_fields.slice(); diff --git a/frappe/client/index.js b/frappe/client/index.js index 1b9ee46c..03b3c119 100644 --- a/frappe/client/index.js +++ b/frappe/client/index.js @@ -18,6 +18,7 @@ module.exports = { frappe.view.init({container: container}); frappe.router = new Router(); + await frappe.login(); } }; diff --git a/frappe/client/ui/index.js b/frappe/client/ui/index.js index 5857c99d..927ca30c 100644 --- a/frappe/client/ui/index.js +++ b/frappe/client/ui/index.js @@ -14,6 +14,10 @@ module.exports = { return element; }, + remove(element) { + element.parentNode.removeChild(element); + }, + add_class(element, className) { if (element.classList) { element.classList.add(className); diff --git a/frappe/client/view/controls/base.js b/frappe/client/view/controls/base.js index 18c5497a..562d4371 100644 --- a/frappe/client/view/controls/base.js +++ b/frappe/client/view/controls/base.js @@ -38,6 +38,7 @@ class BaseControl { this.make_form_group(); this.make_label(); this.make_input(); + this.set_input_name(); this.make_description(); this.bind_change_event(); } @@ -54,7 +55,10 @@ class BaseControl { make_input() { this.input = frappe.ui.add('input', 'form-control', this.form_group); - this.input.setAttribute('type', this.fieldname); + } + + set_input_name() { + this.input.setAttribute('name', this.fieldname); } make_description() { @@ -65,6 +69,9 @@ class BaseControl { } set_input_value(value) { + if (value === undefined || value === null) { + value = ''; + } this.input.value = value; } @@ -76,8 +83,12 @@ class BaseControl { return value; } + async validate(value) { + return value; + } + bind_change_event() { - this.input.addEventListener('change', () => this.handle_change(e)); + this.input.addEventListener('change', (e) => this.handle_change(e)); } async handle_change(e) { diff --git a/frappe/client/view/controls/index.js b/frappe/client/view/controls/index.js index 1d2bf5d9..5eb1ae8d 100644 --- a/frappe/client/view/controls/index.js +++ b/frappe/client/view/controls/index.js @@ -1,5 +1,7 @@ const control_classes = { - Data: require('./data') + Data: require('./data'), + Text: require('./text'), + Select: require('./select') } diff --git a/frappe/client/view/controls/select.js b/frappe/client/view/controls/select.js new file mode 100644 index 00000000..ff6dbc25 --- /dev/null +++ b/frappe/client/view/controls/select.js @@ -0,0 +1,24 @@ +const BaseControl = require('./base'); + +class SelectControl extends BaseControl { + make_input() { + this.input = frappe.ui.add('select', 'form-control', this.form_group); + + let options = this.options; + if (typeof options==='string') { + options = options.split('\n'); + } + + for (let value of options) { + let option = frappe.ui.add('option', null, this.input); + option.textContent = value; + option.setAttribute('value', value); + } + } + make() { + super.make(); + this.input.setAttribute('row', '3'); + } +}; + +module.exports = SelectControl; \ No newline at end of file diff --git a/frappe/client/view/controls/text.js b/frappe/client/view/controls/text.js new file mode 100644 index 00000000..342b69d2 --- /dev/null +++ b/frappe/client/view/controls/text.js @@ -0,0 +1,13 @@ +const BaseControl = require('./base'); + +class TextControl extends BaseControl { + make_input() { + this.input = frappe.ui.add('textarea', 'form-control', this.form_group); + } + make() { + super.make(); + this.input.setAttribute('rows', '8'); + } +}; + +module.exports = TextControl; \ No newline at end of file diff --git a/frappe/client/view/form.js b/frappe/client/view/form.js index d5b90590..ff829472 100644 --- a/frappe/client/view/form.js +++ b/frappe/client/view/form.js @@ -26,19 +26,33 @@ class Form { } make_submit() { - this.submit_btn = frappe.ui.add('button', 'btn btn-primary', this.body); + this.submit_btn = frappe.ui.add('button', 'btn btn-outline-primary', this.body); this.submit_btn.setAttribute('type', 'submit'); this.submit_btn.textContent = this.submit_label; this.submit_btn.addEventListener('click', (event) => { this.submit(); + event.preventDefault(); }) } + show_alert(message, type) { + 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) { @@ -47,12 +61,17 @@ class Form { } async submit() { - if (this.is_new) { - await this.doc.insert(); - } else { - await this.doc.update(); + try { + if (this.is_new) { + await this.doc.insert(); + } else { + await this.doc.update(); + } + await this.refresh(); + this.show_alert('Saved', 'success'); + } catch (e) { + this.show_alert('Failed', 'danger'); } - await this.refresh(); } refresh() { diff --git a/frappe/models/doctype/todo/todo.js b/frappe/models/doctype/todo/todo.js index 3a163c16..0054ae9a 100644 --- a/frappe/models/doctype/todo/todo.js +++ b/frappe/models/doctype/todo/todo.js @@ -8,7 +8,7 @@ class todo_meta extends frappe.meta.Meta { } get_row_html(data) { - return `${data.subject}`; + return `${data.subject}`; } } diff --git a/frappe/server/index.js b/frappe/server/index.js index 454eb726..69263425 100644 --- a/frappe/server/index.js +++ b/frappe/server/index.js @@ -4,7 +4,7 @@ backends.sqllite = require('frappe-core/frappe/backends/sqlite'); const express = require('express'); const app = express(); const frappe = require('frappe-core'); -const rest_server = require('frappe-core/frappe/server/rest_server') +const rest_api = require('./rest_api') const models = require('frappe-core/frappe/server/models'); const common = require('frappe-core/frappe/common'); const bodyParser = require('body-parser'); @@ -33,8 +33,12 @@ module.exports = { app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static('./')); + app.use(function (err, req, res, next) { + console.error(err.stack); + res.status(500).send('Something broke!'); + }) // routes - rest_server.setup(app); + rest_api.setup(app); // listen frappe.app = app; diff --git a/frappe/server/rest_api.js b/frappe/server/rest_api.js new file mode 100644 index 00000000..30e5c22e --- /dev/null +++ b/frappe/server/rest_api.js @@ -0,0 +1,61 @@ +const frappe = require('frappe-core'); + +module.exports = { + setup(app) { + // get list + app.get('/api/resource/:doctype', frappe.async_handler(async function(request, response) { + let fields, filters; + for (key of ['fields', 'filters']) { + if (request.params[key]) { + request.params[key] = JSON.parse(request.params[key]); + } + } + + let data = await frappe.db.get_all({ + doctype: request.params.doctype, + fields: request.params.fields || ['name', 'subject'], + filters: request.params.filters, + start: request.params.start || 0, + limit: request.params.limit || 20, + order_by: request.params.order_by, + order: request.params.order + }); + + return response.json(data); + })); + + // create + app.post('/api/resource/:doctype', frappe.async_handler(async function(request, response) { + data = request.body; + data.doctype = request.params.doctype; + let doc = await frappe.get_doc(data); + await doc.insert(); + await frappe.db.commit(); + return response.json(doc.get_valid_dict()); + })); + + // update + app.put('/api/resource/:doctype/:name', frappe.async_handler(async function(request, response) { + data = request.body; + let doc = await frappe.get_doc(request.params.doctype, request.params.name); + Object.assign(doc, data); + await doc.update(); + await frappe.db.commit(); + return response.json(doc.get_valid_dict()); + })); + + + // get document + app.get('/api/resource/:doctype/:name', frappe.async_handler(async function(request, response) { + let doc = await frappe.get_doc(request.params.doctype, request.params.name); + return response.json(doc.get_valid_dict()); + })); + + // delete + app.delete('/api/resource/:doctype/:name', frappe.async_handler(async function(request, response) { + let doc = await frappe.get_doc(request.params.doctype, request.params.name) + await doc.delete(); + return response.json({}); + })); + } +}; diff --git a/frappe/server/rest_server.js b/frappe/server/rest_server.js deleted file mode 100644 index 3b03d261..00000000 --- a/frappe/server/rest_server.js +++ /dev/null @@ -1,82 +0,0 @@ -const frappe = require('frappe-core'); - -module.exports = { - setup(app) { - // get list - app.get('/api/resource/:doctype', async function(request, response) { - try { - let fields, filters; - for (key of ['fields', 'filters']) { - if (request.params[key]) { - request.params[key] = JSON.parse(request.params[key]); - } - } - - let data = await frappe.db.get_all({ - doctype: request.params.doctype, - fields: request.params.fields || ['name', 'subject'], - filters: request.params.filters, - start: request.params.start || 0, - limit: request.params.limit || 20, - order_by: request.params.order_by, - order: request.params.order - }); - - return response.json(data); - } catch (e) { - throw e; - } - }); - - // create - app.post('/api/resource/:doctype', async function(request, response) { - try { - data = request.body; - data.doctype = request.params.doctype; - let doc = await frappe.get_doc(data); - await doc.insert(); - await frappe.db.commit(); - return response.json(doc.get_valid_dict()); - } catch (e) { - throw e; - } - }); - - // update - app.put('/api/resource/:doctype/:name', async function(request, response) { - try { - data = request.body; - let doc = await frappe.get_doc(request.params.doctype, request.params.name); - Object.assign(doc, data); - await doc.update(); - await frappe.db.commit(); - return response.json(doc.get_valid_dict()); - } catch (e) { - throw e; - } - }); - - - // get document - app.get('/api/resource/:doctype/:name', async function(request, response) { - try { - let doc = await frappe.get_doc(request.params.doctype, request.params.name); - return response.json(doc.get_valid_dict()); - } catch (e) { - throw e; - } - }); - - // delete - app.delete('/api/resource/:doctype/:name', async function(request, response) { - try { - let doc = await frappe.get_doc(request.params.doctype, request.params.name) - await doc.delete(); - return response.json({}); - } catch (e) { - throw e; - } - }); - - } -}; diff --git a/frappe/utils.js b/frappe/utils.js index 0eb25031..06004302 100644 --- a/frappe/utils.js +++ b/frappe/utils.js @@ -2,24 +2,12 @@ module.exports = { slug(text) { return text.toLowerCase().replace(/ /g, '_'); }, - sqlescape(value) { - if (value===null || value===undefined) { - // null - return 'null'; - } else if (value instanceof Date) { - // date - return `'${value.toISOString()}'`; - - } else if (typeof value==='string') { - // text - return "'" + value.replace(/'/g, '\'').replace(/"/g, '\"') + "'"; - - } else { - // number - return value + ''; - } + async_handler(fn) { + return (req, res, next) => Promise.resolve(fn(req, res, next)) + .catch(next); }, + async sleep(seconds) { return new Promise(resolve => { setTimeout(resolve, seconds * 1000);