mirror of
https://github.com/frappe/books.git
synced 2024-11-09 23:30:56 +00:00
routing for desk, error handling, test for router
This commit is contained in:
parent
b305e1e4f4
commit
df9fc91123
@ -1,7 +1,7 @@
|
|||||||
const frappe = require('frappe-core');
|
const frappe = require('frappe-core');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
class RESTClient {
|
module.exports = class RESTClient {
|
||||||
constructor({server, protocol='http'}) {
|
constructor({server, protocol='http'}) {
|
||||||
this.server = server;
|
this.server = server;
|
||||||
this.protocol = protocol;
|
this.protocol = protocol;
|
||||||
@ -123,8 +123,4 @@ class RESTClient {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
Database: RESTClient
|
|
||||||
}
|
}
|
@ -1,10 +1,14 @@
|
|||||||
const frappe = require('frappe-core');
|
const frappe = require('frappe-core');
|
||||||
const Search = require('./search');
|
const Search = require('./search');
|
||||||
const Router = require('./router');
|
const Router = require('frappe-core/common/router');
|
||||||
|
const Page = require('frappe-core/client/view/page');
|
||||||
|
const List = require('frappe-core/client/view/list');
|
||||||
|
const Form = require('frappe-core/client/view/form');
|
||||||
|
|
||||||
module.exports = class Desk {
|
module.exports = class Desk {
|
||||||
constructor() {
|
constructor() {
|
||||||
frappe.router = new Router();
|
frappe.router = new Router();
|
||||||
|
frappe.router.listen();
|
||||||
|
|
||||||
this.wrapper = document.querySelector('.desk');
|
this.wrapper = document.querySelector('.desk');
|
||||||
|
|
||||||
@ -15,20 +19,67 @@ module.exports = class Desk {
|
|||||||
this.main = frappe.ui.add('div', 'main', this.body);
|
this.main = frappe.ui.add('div', 'main', this.body);
|
||||||
|
|
||||||
this.sidebar_items = [];
|
this.sidebar_items = [];
|
||||||
this.list_pages = {};
|
this.pages = {
|
||||||
this.edit_pages = {};
|
lists: {},
|
||||||
|
forms: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.init_routes();
|
||||||
|
|
||||||
// this.search = new Search(this.nav);
|
// this.search = new Search(this.nav);
|
||||||
}
|
}
|
||||||
|
|
||||||
init_routes() {
|
init_routes() {
|
||||||
frappe.router.on('list/:doctype', (params) => {
|
frappe.router.add('list/:doctype', async (params) => {
|
||||||
|
let page = this.get_list_page(params.doctype);
|
||||||
})
|
await page.show(params);
|
||||||
frappe.router.on('edit/:doctype/:name', (params) => {
|
});
|
||||||
|
|
||||||
|
frappe.router.add('edit/:doctype/:name', async (params) => {
|
||||||
|
let page = this.get_form_page(params.doctype);
|
||||||
|
await page.show(params);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
frappe.router.add('new/:doctype', async (params) => {
|
||||||
|
let doc = await frappe.get_new_doc(params.doctype);
|
||||||
|
frappe.router.set_route('edit', doc.doctype, doc.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
get_list_page(doctype) {
|
||||||
|
if (!this.pages.lists[doctype]) {
|
||||||
|
let page = new Page('List ' + doctype);
|
||||||
|
page.list = new List({
|
||||||
|
doctype: doctype,
|
||||||
|
parent: page.body
|
||||||
|
});
|
||||||
|
page.on('show', async () => {
|
||||||
|
await page.list.run();
|
||||||
|
});
|
||||||
|
this.pages.lists[doctype] = page;
|
||||||
|
}
|
||||||
|
return this.pages.lists[doctype];
|
||||||
|
}
|
||||||
|
|
||||||
|
get_form_page(doctype) {
|
||||||
|
if (!this.pages.forms[doctype]) {
|
||||||
|
let page = new Page('Edit ' + doctype);
|
||||||
|
page.form = new Form({
|
||||||
|
doctype: doctype,
|
||||||
|
parent: page.body
|
||||||
|
});
|
||||||
|
page.on('show', async (params) => {
|
||||||
|
try {
|
||||||
|
page.doc = await frappe.get_doc(params.doctype, params.name);
|
||||||
|
page.form.use(page.doc);
|
||||||
|
} catch (e) {
|
||||||
|
page.render_error(e.status_code, e.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.pages.forms[doctype] = page;
|
||||||
|
}
|
||||||
|
return this.pages.forms[doctype];
|
||||||
}
|
}
|
||||||
|
|
||||||
add_sidebar_item(label, action) {
|
add_sidebar_item(label, action) {
|
||||||
|
@ -1,82 +0,0 @@
|
|||||||
const frappe = require('frappe-core');
|
|
||||||
|
|
||||||
module.exports = class Router {
|
|
||||||
constructor() {
|
|
||||||
this.current_page = null;
|
|
||||||
this.static_routes = {};
|
|
||||||
this.dynamic_routes = {};
|
|
||||||
this.listen();
|
|
||||||
}
|
|
||||||
|
|
||||||
add(route, handler) {
|
|
||||||
let page = {handler: handler};
|
|
||||||
|
|
||||||
// '/todo/:name/:place'.match(/:([^/]+)/g);
|
|
||||||
page.param_keys = route.match(/:([^/]+)/g);
|
|
||||||
|
|
||||||
if (page.param_keys) {
|
|
||||||
// make expression
|
|
||||||
// '/todo/:name/:place'.replace(/\/:([a-z1-9]+)/g, "\/([a-z0-9]+)");
|
|
||||||
page.expression = route.replace(/\/:([a-z1-9]+)/g, "\/([a-z0-9]+)");
|
|
||||||
this.dynamic_routes[route] = page;
|
|
||||||
} else {
|
|
||||||
this.static_routes[route] = page;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
listen() {
|
|
||||||
window.onhashchange = this.changed.bind(this);
|
|
||||||
this.changed();
|
|
||||||
}
|
|
||||||
|
|
||||||
async changed(event) {
|
|
||||||
if (window.location.hash.length > 0) {
|
|
||||||
const page_name = window.location.hash.substr(1);
|
|
||||||
this.show(page_name);
|
|
||||||
} else if (this.static_routes['default']) {
|
|
||||||
this.show('default');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
show(route) {
|
|
||||||
if (!route) {
|
|
||||||
route = 'default';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (route[0]==='#') {
|
|
||||||
route = route.substr(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let page = this.match(route);
|
|
||||||
|
|
||||||
if (page) {
|
|
||||||
if (typeof page.handler==='function') {
|
|
||||||
page.handler(page.params);
|
|
||||||
} else {
|
|
||||||
page.handler.show(page.params);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match(route) {
|
|
||||||
for(let key in this.static_routes) {
|
|
||||||
if (key === route) {
|
|
||||||
return {handler: this.static_routes[key].handler};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for(let key in this.dynamic_routes) {
|
|
||||||
let page = this.dynamic_routes[key];
|
|
||||||
let matches = route.match(new RegExp(page.expression));
|
|
||||||
|
|
||||||
if (matches && matches.length == page.param_keys.length + 1) {
|
|
||||||
let params = {}
|
|
||||||
for (let i=0; i < page.param_keys.length; i++) {
|
|
||||||
params[page.param_keys[i].substr(1)] = matches[i + 1];
|
|
||||||
}
|
|
||||||
return {handler:page.handler, params: params};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
const common = require('frappe-core/common');
|
const common = require('frappe-core/common');
|
||||||
const Database = require('frappe-core/backends/rest_client').Database;
|
const RESTClient = require('frappe-core/backends/rest_client');
|
||||||
const frappe = require('frappe-core');
|
const frappe = require('frappe-core');
|
||||||
frappe.ui = require('./ui');
|
frappe.ui = require('./ui');
|
||||||
const Desk = require('./desk');
|
const Desk = require('./desk');
|
||||||
@ -11,7 +11,9 @@ module.exports = {
|
|||||||
common.init_libs(frappe);
|
common.init_libs(frappe);
|
||||||
|
|
||||||
frappe.fetch = window.fetch.bind();
|
frappe.fetch = window.fetch.bind();
|
||||||
frappe.db = await new Database({server: server});
|
frappe.db = await new RESTClient({server: server});
|
||||||
|
|
||||||
|
frappe.flags.cache_docs = true;
|
||||||
|
|
||||||
frappe.desk = new Desk();
|
frappe.desk = new Desk();
|
||||||
await frappe.login();
|
await frappe.login();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
const frappe = require('frappe-core');
|
const frappe = require('frappe-core');
|
||||||
const controls = require('./controls');
|
const controls = require('./controls');
|
||||||
|
|
||||||
class Form {
|
module.exports = class Form {
|
||||||
constructor({doctype, parent, submit_label='Submit'}) {
|
constructor({doctype, parent, submit_label='Submit'}) {
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
this.doctype = doctype;
|
this.doctype = doctype;
|
||||||
@ -85,7 +85,7 @@ class Form {
|
|||||||
|
|
||||||
async submit() {
|
async submit() {
|
||||||
try {
|
try {
|
||||||
if (this.is_new) {
|
if (this.is_new || this.doc.__not_inserted) {
|
||||||
await this.doc.insert();
|
await this.doc.insert();
|
||||||
} else {
|
} else {
|
||||||
await this.doc.update();
|
await this.doc.update();
|
||||||
@ -103,6 +103,4 @@ class Form {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {Form: Form};
|
|
@ -1,6 +1,6 @@
|
|||||||
const frappe = require('frappe-core');
|
const frappe = require('frappe-core');
|
||||||
|
|
||||||
class ListView {
|
module.exports = class List {
|
||||||
constructor({doctype, parent, fields}) {
|
constructor({doctype, parent, fields}) {
|
||||||
this.doctype = doctype;
|
this.doctype = doctype;
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
@ -117,8 +117,4 @@ class ListView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
ListView: ListView
|
|
||||||
};
|
};
|
@ -1,6 +1,6 @@
|
|||||||
const frappe = require('frappe-core');
|
const frappe = require('frappe-core');
|
||||||
|
|
||||||
class Page {
|
module.exports = class Page {
|
||||||
constructor(title) {
|
constructor(title) {
|
||||||
this.handlers = {};
|
this.handlers = {};
|
||||||
this.title = title;
|
this.title = title;
|
||||||
@ -8,12 +8,12 @@ class Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
make() {
|
make() {
|
||||||
this.body = frappe.ui.add('div', 'page hide', frappe.desk.main);
|
this.wrapper = frappe.ui.add('div', 'page hide', frappe.desk.main);
|
||||||
|
this.body = frappe.ui.add('div', 'page-body', this.wrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
hide() {
|
hide() {
|
||||||
frappe.ui.add_class(this.body, 'hide');
|
this.wrapper.classList.add('hide');
|
||||||
|
|
||||||
this.trigger('hide');
|
this.trigger('hide');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,25 +21,38 @@ class Page {
|
|||||||
if (frappe.router.current_page) {
|
if (frappe.router.current_page) {
|
||||||
frappe.router.current_page.hide();
|
frappe.router.current_page.hide();
|
||||||
}
|
}
|
||||||
frappe.ui.remove_class(this.body, 'hide');
|
this.wrapper.classList.remove('hide');
|
||||||
|
this.body.classList.remove('hide');
|
||||||
|
|
||||||
|
if (this.page_error) {
|
||||||
|
this.page_error.classList.add('hide');
|
||||||
|
}
|
||||||
|
|
||||||
frappe.router.current_page = this;
|
frappe.router.current_page = this;
|
||||||
document.title = this.title;
|
document.title = this.title;
|
||||||
|
|
||||||
this.trigger('show', params);
|
this.trigger('show', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
render_error(status_code, message) {
|
||||||
|
if (!this.page_error) {
|
||||||
|
this.page_error = frappe.ui.add('div', 'page-error', this.wrapper);
|
||||||
|
}
|
||||||
|
this.body.classList.add('hide');
|
||||||
|
this.page_error.classList.remove('hide');
|
||||||
|
this.page_error.innerHTML = `<h3 class="text-extra-muted">${status_code}</h3><p class="text-muted">${message}</p>`;
|
||||||
|
}
|
||||||
|
|
||||||
on(event, fn) {
|
on(event, fn) {
|
||||||
if (!this.handlers[event]) this.handlers.event = [];
|
if (!this.handlers[event]) this.handlers[event] = [];
|
||||||
this.handlers[event].push(fn);
|
this.handlers[event].push(fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
trigger(event, params) {
|
async trigger(event, params) {
|
||||||
if (this.handlers[event]) {
|
if (this.handlers[event]) {
|
||||||
for (let handler of this.handlers[event]) {
|
for (let handler of this.handlers[event]) {
|
||||||
handler(params);
|
await handler(params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { Page: Page };
|
|
21
common/errors.js
Normal file
21
common/errors.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
class BaseError extends Error {
|
||||||
|
constructor(status_code, ...params) {
|
||||||
|
super(...params);
|
||||||
|
this.status_code = status_code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ValidationError extends BaseError {
|
||||||
|
constructor(...params) { super(417, ...params); }
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
ValidationError: ValidationError,
|
||||||
|
ValueError: class ValueError extends ValidationError { },
|
||||||
|
NotFound: class NotFound extends BaseError {
|
||||||
|
constructor(...params) { super(404, ...params); }
|
||||||
|
},
|
||||||
|
Forbidden: class Forbidden extends BaseError {
|
||||||
|
constructor(...params) { super(403, ...params); }
|
||||||
|
},
|
||||||
|
}
|
@ -4,6 +4,7 @@ const model = require('../model');
|
|||||||
const _document = require('../model/document');
|
const _document = require('../model/document');
|
||||||
const meta = require('../model/meta');
|
const meta = require('../model/meta');
|
||||||
const _session = require('../session');
|
const _session = require('../session');
|
||||||
|
const errors = require('./errors');
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -14,5 +15,6 @@ module.exports = {
|
|||||||
frappe.document = _document;
|
frappe.document = _document;
|
||||||
frappe.meta = meta;
|
frappe.meta = meta;
|
||||||
frappe._session = _session;
|
frappe._session = _session;
|
||||||
|
frappe.errors = errors;
|
||||||
}
|
}
|
||||||
}
|
}
|
99
common/router.js
Normal file
99
common/router.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
const frappe = require('frappe-core');
|
||||||
|
|
||||||
|
module.exports = class Router {
|
||||||
|
constructor() {
|
||||||
|
this.current_page = null;
|
||||||
|
this.static_routes = [];
|
||||||
|
this.dynamic_routes = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
add(route, handler) {
|
||||||
|
let page = {handler: handler, route: route};
|
||||||
|
|
||||||
|
// '/todo/:name/:place'.match(/:([^/]+)/g);
|
||||||
|
page.param_keys = route.match(/:([^/]+)/g);
|
||||||
|
|
||||||
|
if (page.param_keys) {
|
||||||
|
// make expression
|
||||||
|
// '/todo/:name/:place'.replace(/\/:([a-z1-9]+)/g, "\/([a-z0-9]+)");
|
||||||
|
page.expression = route.replace(/\/:([a-z1-9]+)/g, "\/([a-z0-9]+)");
|
||||||
|
this.dynamic_routes.push(page);
|
||||||
|
this.sort_dynamic_routes();
|
||||||
|
} else {
|
||||||
|
this.static_routes.push(page);
|
||||||
|
this.sort_static_routes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort_dynamic_routes() {
|
||||||
|
// routes with fewer parameters first
|
||||||
|
this.dynamic_routes = this.dynamic_routes.sort((a, b) => {
|
||||||
|
if (a.param_keys.length > b.param_keys.length) {
|
||||||
|
return 1;
|
||||||
|
} else if (a.param_keys.length < b.param_keys.length) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return a.route.length > b.route.length ? 1 : -1;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
sort_static_routes() {
|
||||||
|
// longer routes on first
|
||||||
|
this.static_routes = this.static_routes.sort((a, b) => {
|
||||||
|
return a.route.length > b.route.length ? 1 : -1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
listen() {
|
||||||
|
window.addEventListener('hashchange', (event) => {
|
||||||
|
this.show(window.location.hash);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
set_route(...parts) {
|
||||||
|
const route = parts.join('/');
|
||||||
|
window.location.hash = route;
|
||||||
|
}
|
||||||
|
|
||||||
|
async show(route) {
|
||||||
|
if (route && route[0]==='#') {
|
||||||
|
route = route.substr(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!route) {
|
||||||
|
route = this.default;
|
||||||
|
}
|
||||||
|
let page = this.match(route);
|
||||||
|
|
||||||
|
if (page) {
|
||||||
|
if (typeof page.handler==='function') {
|
||||||
|
await page.handler(page.params);
|
||||||
|
} else {
|
||||||
|
await page.handler.show(page.params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match(route) {
|
||||||
|
// match static
|
||||||
|
for(let page of this.static_routes) {
|
||||||
|
if (page.route === route) {
|
||||||
|
return {handler: page.handler};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// match dynamic
|
||||||
|
for(let page of this.dynamic_routes) {
|
||||||
|
let matches = route.match(new RegExp(page.expression));
|
||||||
|
|
||||||
|
if (matches && matches.length == page.param_keys.length + 1) {
|
||||||
|
let params = {}
|
||||||
|
for (let i=0; i < page.param_keys.length; i++) {
|
||||||
|
params[page.param_keys[i].substr(1)] = matches[i + 1];
|
||||||
|
}
|
||||||
|
return {handler:page.handler, params: params};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
docs/client/desk.md
Normal file
27
docs/client/desk.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Desk
|
||||||
|
|
||||||
|
Desk includes the default routing and menu system for the single page application
|
||||||
|
|
||||||
|
## Menus
|
||||||
|
|
||||||
|
You can add a new menu to the desk via
|
||||||
|
|
||||||
|
```js
|
||||||
|
frappe.desk.add_sidebar_item('New ToDo', '#new/todo');
|
||||||
|
```
|
||||||
|
|
||||||
|
## Views
|
||||||
|
|
||||||
|
Default route handling for various views
|
||||||
|
|
||||||
|
### List Documents
|
||||||
|
|
||||||
|
All list views are rendered at `/list/:doctype`
|
||||||
|
|
||||||
|
### Edit Documents
|
||||||
|
|
||||||
|
Documents can be edited via `/edit/:doctype/:name`
|
||||||
|
|
||||||
|
### New Documents
|
||||||
|
|
||||||
|
New Documents can be created via `/new/:doctype`
|
16
docs/errors.md
Normal file
16
docs/errors.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Errors
|
||||||
|
|
||||||
|
Frappe.js comes with standard error classes that have an HTTP status code attached.
|
||||||
|
|
||||||
|
For example you can raise a "not found" (HTTP Status Code 404) via:
|
||||||
|
|
||||||
|
```js
|
||||||
|
throw new frappe.errors.NotFound('Document Not Found');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Standard Errors
|
||||||
|
|
||||||
|
- 403: Forbidden
|
||||||
|
- 404: NotFound
|
||||||
|
- 417: ValidationError
|
||||||
|
- 417: ValueError
|
46
index.js
46
index.js
@ -2,7 +2,6 @@ module.exports = {
|
|||||||
async init() {
|
async init() {
|
||||||
if (this._initialized) return;
|
if (this._initialized) return;
|
||||||
this.init_config();
|
this.init_config();
|
||||||
this.init_errors();
|
|
||||||
this.init_globals();
|
this.init_globals();
|
||||||
this._initialized = true;
|
this._initialized = true;
|
||||||
},
|
},
|
||||||
@ -14,12 +13,30 @@ module.exports = {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
init_errors() {
|
|
||||||
this.ValueError = class extends Error { };
|
|
||||||
},
|
|
||||||
|
|
||||||
init_globals() {
|
init_globals() {
|
||||||
this.meta_cache = {};
|
this.meta_cache = {};
|
||||||
|
this.docs = {};
|
||||||
|
this.flags = {
|
||||||
|
cache_docs: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
add_to_cache(doc) {
|
||||||
|
if (!this.flags.cache_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;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
get_doc_from_cache(doctype, name) {
|
||||||
|
if (this.docs[doctype] && this.docs[doctype][name]) {
|
||||||
|
return this.docs[doctype][name];
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
get_meta(doctype) {
|
get_meta(doctype) {
|
||||||
@ -37,9 +54,14 @@ module.exports = {
|
|||||||
|
|
||||||
async get_doc(data, name) {
|
async get_doc(data, name) {
|
||||||
if (typeof data==='string' && typeof name==='string') {
|
if (typeof data==='string' && typeof name==='string') {
|
||||||
let controller_class = this.models.get_controller(data);
|
let doc = this.get_doc_from_cache(data, name);
|
||||||
var doc = new controller_class({doctype:data, name: name});
|
if (!doc) {
|
||||||
await doc.load();
|
let controller_class = this.models.get_controller(data);
|
||||||
|
doc = new controller_class({doctype:data, name: name});
|
||||||
|
await doc.load();
|
||||||
|
this.add_to_cache(doc);
|
||||||
|
}
|
||||||
|
return doc;
|
||||||
} else {
|
} else {
|
||||||
let controller_class = this.models.get_controller(data.doctype);
|
let controller_class = this.models.get_controller(data.doctype);
|
||||||
var doc = new controller_class(data);
|
var doc = new controller_class(data);
|
||||||
@ -47,6 +69,14 @@ module.exports = {
|
|||||||
return doc;
|
return doc;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async get_new_doc(doctype) {
|
||||||
|
let doc = await frappe.get_doc({doctype: doctype});
|
||||||
|
doc.set_name();
|
||||||
|
doc.__not_inserted = true;
|
||||||
|
this.add_to_cache(doc);
|
||||||
|
return doc;
|
||||||
|
},
|
||||||
|
|
||||||
async insert(data) {
|
async insert(data) {
|
||||||
const doc = await this.get_doc(data);
|
const doc = await this.get_doc(data);
|
||||||
return await doc.insert();
|
return await doc.insert();
|
||||||
|
@ -98,7 +98,12 @@ class Document {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
Object.assign(this, await frappe.db.get(this.doctype, this.name));
|
let data = await frappe.db.get(this.doctype, this.name);
|
||||||
|
if (data.name) {
|
||||||
|
Object.assign(this, data);
|
||||||
|
} else {
|
||||||
|
throw new frappe.errors.NotFound(`Not Found: ${this.doctype} ${this.name}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async insert() {
|
async insert() {
|
||||||
|
@ -77,7 +77,7 @@ class Meta extends Document {
|
|||||||
options = df.options.split('\n');
|
options = df.options.split('\n');
|
||||||
}
|
}
|
||||||
if (!options.includes(value)) {
|
if (!options.includes(value)) {
|
||||||
throw new frappe.ValueError(`${value} must be one of ${options.join(", ")}`);
|
throw new frappe.errors.ValueError(`${value} must be one of ${options.join(", ")}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,11 +4,12 @@ class todo_meta extends frappe.meta.Meta {
|
|||||||
setup_meta() {
|
setup_meta() {
|
||||||
Object.assign(this, require('./todo.json'));
|
Object.assign(this, require('./todo.json'));
|
||||||
this.name = 'ToDo';
|
this.name = 'ToDo';
|
||||||
this.list_options.fields = ['name', 'subject', 'status', 'description'];
|
this.list_options.fields = ['name', 'subject', 'status'];
|
||||||
}
|
}
|
||||||
|
|
||||||
get_row_html(data) {
|
get_row_html(data) {
|
||||||
return `<a href="#edit/todo/${data.name}">${data.subject}</a>`;
|
const sign = data.status === 'Open' ? '' : '✔';
|
||||||
|
return `<p><a href="#edit/todo/${data.name}">${sign} ${data.subject}</a></p>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -36,10 +36,6 @@ module.exports = {
|
|||||||
app.use(bodyParser.urlencoded({ extended: true }));
|
app.use(bodyParser.urlencoded({ extended: true }));
|
||||||
app.use(express.static('./'));
|
app.use(express.static('./'));
|
||||||
|
|
||||||
app.use(function (err, req, res, next) {
|
|
||||||
console.error(err.stack);
|
|
||||||
res.status(500).send('Something broke!');
|
|
||||||
})
|
|
||||||
// routes
|
// routes
|
||||||
rest_api.setup(app);
|
rest_api.setup(app);
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ describe('Document', () => {
|
|||||||
() => {
|
() => {
|
||||||
doc.set('status', 'Illegal');
|
doc.set('status', 'Illegal');
|
||||||
},
|
},
|
||||||
frappe.ValueError
|
frappe.errors.ValueError
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ const fetch = require('node-fetch');
|
|||||||
const helpers = require('./helpers');
|
const helpers = require('./helpers');
|
||||||
const { spawn } = require('child_process');
|
const { spawn } = require('child_process');
|
||||||
const process = require('process');
|
const process = require('process');
|
||||||
const Database = require('frappe-core/backends/rest_client').Database
|
const RESTClient = require('frappe-core/backends/rest_client')
|
||||||
|
|
||||||
// create a copy of frappe
|
// create a copy of frappe
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ describe('REST', () => {
|
|||||||
await frappe.init();
|
await frappe.init();
|
||||||
await frappe.login();
|
await frappe.login();
|
||||||
|
|
||||||
frappe.db = await new Database({server: 'localhost:8000'});
|
frappe.db = await new RESTClient({server: 'localhost:8000'});
|
||||||
frappe.fetch = fetch;
|
frappe.fetch = fetch;
|
||||||
|
|
||||||
// wait for server to start
|
// wait for server to start
|
||||||
|
28
tests/test_router.js
Normal file
28
tests/test_router.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
const Router = require('frappe-core/common/router');
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
describe('Router', () => {
|
||||||
|
it('router should sort static routes', () => {
|
||||||
|
let router = new Router();
|
||||||
|
router.add('/a', 'x');
|
||||||
|
router.add('/a/b', 'y');
|
||||||
|
router.add('/a/b/clong/', 'z');
|
||||||
|
|
||||||
|
assert.equal(router.match('/a/b').handler, 'y');
|
||||||
|
assert.equal(router.match('/a').handler, 'x');
|
||||||
|
assert.equal(router.match('/a/b/clong/').handler, 'z');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('router should sort dynamic routes', () => {
|
||||||
|
let router = new Router();
|
||||||
|
router.add('/edit/todo/mytest', 'static');
|
||||||
|
router.add('/edit/:doctype/:name', 'all');
|
||||||
|
router.add('/edit/todo/:name', 'todo');
|
||||||
|
|
||||||
|
assert.equal(router.match('/edit/todo/test').handler, 'todo');
|
||||||
|
assert.equal(router.match('/edit/user/test').handler, 'all');
|
||||||
|
assert.equal(router.match('/edit/todo/mytest').handler, 'static');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
@ -5,7 +5,10 @@ module.exports = {
|
|||||||
|
|
||||||
async_handler(fn) {
|
async_handler(fn) {
|
||||||
return (req, res, next) => Promise.resolve(fn(req, res, next))
|
return (req, res, next) => Promise.resolve(fn(req, res, next))
|
||||||
.catch(next);
|
.catch((err) => {
|
||||||
|
// handle error
|
||||||
|
res.status(err.status_code).send({ error: err.message });
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
async sleep(seconds) {
|
async sleep(seconds) {
|
||||||
|
Loading…
Reference in New Issue
Block a user