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