mirror of
https://github.com/frappe/books.git
synced 2024-11-08 23:00:56 +00:00
added forms + controls
This commit is contained in:
parent
3b8ea0755c
commit
d9e306b641
23
frappe/client/index.js
Normal file
23
frappe/client/index.js
Normal file
@ -0,0 +1,23 @@
|
||||
const common = require('frappe-core/frappe/common');
|
||||
const Database = require('frappe-core/frappe/backends/rest_client').Database;
|
||||
const frappe = require('frappe-core');
|
||||
frappe.ui = require('./ui');
|
||||
frappe.view = require('./view');
|
||||
const Router = require('./view/router').Router;
|
||||
|
||||
module.exports = {
|
||||
async start({server, container}) {
|
||||
window.frappe = frappe;
|
||||
frappe.init();
|
||||
common.init_libs(frappe);
|
||||
|
||||
frappe.db = await new Database({
|
||||
server: server,
|
||||
fetch: window.fetch.bind()
|
||||
});
|
||||
|
||||
frappe.view.init({container: container});
|
||||
frappe.router = new Router();
|
||||
}
|
||||
};
|
||||
|
33
frappe/client/ui/index.js
Normal file
33
frappe/client/ui/index.js
Normal file
@ -0,0 +1,33 @@
|
||||
const frappe = require('frappe-core');
|
||||
|
||||
module.exports = {
|
||||
add(tag, className, parent) {
|
||||
let element = document.createElement(tag);
|
||||
if (className) {
|
||||
for (let c of className.split(' ')) {
|
||||
this.add_class(element, c);
|
||||
}
|
||||
}
|
||||
if (parent) {
|
||||
parent.appendChild(element);
|
||||
}
|
||||
return element;
|
||||
},
|
||||
|
||||
add_class(element, className) {
|
||||
if (element.classList) {
|
||||
element.classList.add(className);
|
||||
} else {
|
||||
element.className += " " + className;
|
||||
}
|
||||
},
|
||||
|
||||
remove_class(element, className) {
|
||||
if (element.classList) {
|
||||
element.classList.remove(className);
|
||||
} else {
|
||||
element.className = element.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
90
frappe/client/view/controls/base.js
Normal file
90
frappe/client/view/controls/base.js
Normal file
@ -0,0 +1,90 @@
|
||||
const frappe = require('frappe-core');
|
||||
|
||||
class BaseControl {
|
||||
constructor(docfield, parent) {
|
||||
Object.assign(this, docfield);
|
||||
if (!this.fieldname) {
|
||||
this.fieldname = frappe.slug(this.label);
|
||||
}
|
||||
this.parent = parent;
|
||||
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.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);
|
||||
this.input.setAttribute('type', 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) {
|
||||
this.input.value = value;
|
||||
}
|
||||
|
||||
async get_input_value() {
|
||||
return await this.parse(this.input.value);
|
||||
}
|
||||
|
||||
async parse(value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
bind_change_event() {
|
||||
this.input.addEventListener('change', () => 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;
|
10
frappe/client/view/controls/data.js
Normal file
10
frappe/client/view/controls/data.js
Normal file
@ -0,0 +1,10 @@
|
||||
const BaseControl = require('./base');
|
||||
|
||||
class DataControl extends BaseControl {
|
||||
make() {
|
||||
super.make();
|
||||
this.input.setAttribute('type', 'text');
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = DataControl;
|
16
frappe/client/view/controls/index.js
Normal file
16
frappe/client/view/controls/index.js
Normal file
@ -0,0 +1,16 @@
|
||||
const control_classes = {
|
||||
Data: require('./data')
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
get_control_class(fieldtype) {
|
||||
return control_classes[fieldtype];
|
||||
},
|
||||
make_control(field, parent) {
|
||||
const control_class = this.get_control_class(field.fieldtype);
|
||||
let control = new control_class(field, parent);
|
||||
control.make();
|
||||
return control;
|
||||
}
|
||||
}
|
66
frappe/client/view/form.js
Normal file
66
frappe/client/view/form.js
Normal file
@ -0,0 +1,66 @@
|
||||
const frappe = require('frappe-core');
|
||||
const controls = require('./controls');
|
||||
|
||||
class Form {
|
||||
constructor({doctype, parent, submit_label='Submit'}) {
|
||||
this.parent = parent;
|
||||
this.doctype = doctype;
|
||||
this.submit_label = submit_label;
|
||||
|
||||
this.controls = {};
|
||||
this.controls_list = [];
|
||||
|
||||
this.meta = frappe.get_meta(this.doctype);
|
||||
}
|
||||
|
||||
make() {
|
||||
this.body = frappe.ui.add('form', null, this.parent);
|
||||
for(let df of this.meta.fields) {
|
||||
if (controls.get_control_class(df.fieldtype)) {
|
||||
let control = controls.make_control(df, this.body);
|
||||
this.controls_list.push(control);
|
||||
this.controls[df.fieldname] = control;
|
||||
}
|
||||
}
|
||||
this.make_submit();
|
||||
}
|
||||
|
||||
make_submit() {
|
||||
this.submit_btn = frappe.ui.add('button', 'btn btn-primary', this.body);
|
||||
this.submit_btn.setAttribute('type', 'submit');
|
||||
this.submit_btn.textContent = this.submit_label;
|
||||
this.submit_btn.addEventListener('click', (event) => {
|
||||
this.submit();
|
||||
})
|
||||
}
|
||||
|
||||
async use(doc, is_new = false) {
|
||||
if (this.doc) {
|
||||
// clear handlers of outgoing doc
|
||||
this.doc.clear_handlers();
|
||||
}
|
||||
this.doc = doc;
|
||||
this.is_new = is_new;
|
||||
for (let control of this.controls_list) {
|
||||
control.bind(this.doc);
|
||||
}
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (this.is_new) {
|
||||
await this.doc.insert();
|
||||
} else {
|
||||
await this.doc.update();
|
||||
}
|
||||
await this.refresh();
|
||||
}
|
||||
|
||||
refresh() {
|
||||
for(let control of this.controls_list) {
|
||||
control.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = {Form: Form};
|
20
frappe/client/view/index.js
Normal file
20
frappe/client/view/index.js
Normal file
@ -0,0 +1,20 @@
|
||||
const frappe = require('frappe-core');
|
||||
|
||||
module.exports = {
|
||||
init({container, main, sidebar}) {
|
||||
frappe.container = container;
|
||||
|
||||
if (sidebar) {
|
||||
frappe.sidebar = sidebar;
|
||||
} else {
|
||||
frappe.sidebar = frappe.ui.add('div', 'sidebar', frappe.container);
|
||||
}
|
||||
|
||||
if (main) {
|
||||
frappe.main = main;
|
||||
} else {
|
||||
frappe.main = frappe.ui.add('div', 'main', frappe.container);
|
||||
}
|
||||
},
|
||||
|
||||
}
|
@ -13,9 +13,9 @@ class ListView {
|
||||
this.rows = [];
|
||||
}
|
||||
|
||||
async render() {
|
||||
async run() {
|
||||
this.make_body();
|
||||
let data = await this.meta.get_list(this.start, this.page_length);
|
||||
let data = await this.meta.get_list({start:this.start, limit:this.page_length});
|
||||
|
||||
for (let i=0; i< data.length; i++) {
|
||||
this.render_row(this.start + i, data[i]);
|
||||
@ -24,19 +24,18 @@ class ListView {
|
||||
|
||||
make_body() {
|
||||
if (!this.body) {
|
||||
this.body = $('<div class="list-body"></div>')
|
||||
.appendTo(frappe.main);
|
||||
this.body = frappe.ui.add('div', 'list-body', this.parent);
|
||||
}
|
||||
}
|
||||
|
||||
render_row(i, data) {
|
||||
let row = this.get_row(i);
|
||||
row.html(this.meta.get_row_html(data));
|
||||
row.innerHTML = this.meta.get_row_html(data);
|
||||
}
|
||||
|
||||
get_row(i) {
|
||||
if (!this.rows[i]) {
|
||||
this.rows[i] = $('<div class="list-row"></div>').appendTo(this.body);
|
||||
this.rows[i] = frappe.ui.add('div', 'list-row', this.body);
|
||||
}
|
||||
return this.rows[i];
|
||||
}
|
24
frappe/client/view/page.js
Normal file
24
frappe/client/view/page.js
Normal file
@ -0,0 +1,24 @@
|
||||
const frappe = require('frappe-core');
|
||||
|
||||
class Page {
|
||||
constructor(title) {
|
||||
this.title = title;
|
||||
this.make();
|
||||
}
|
||||
make() {
|
||||
this.body = frappe.ui.add('div', 'page hide', frappe.main);
|
||||
}
|
||||
hide() {
|
||||
frappe.ui.add_class(this.body, 'hide');
|
||||
}
|
||||
show() {
|
||||
if (frappe.router.current_page) {
|
||||
frappe.router.current_page.hide();
|
||||
}
|
||||
frappe.ui.remove_class(this.body, 'hide');
|
||||
frappe.router.current_page = this;
|
||||
document.title = this.title;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { Page: Page };
|
82
frappe/client/view/router.js
Normal file
82
frappe/client/view/router.js
Normal file
@ -0,0 +1,82 @@
|
||||
const frappe = require('frappe-core');
|
||||
|
||||
class Router {
|
||||
constructor() {
|
||||
this.current_page = null;
|
||||
this.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.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.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.routes) {
|
||||
let page = this.routes[key];
|
||||
|
||||
if (page.param_keys) {
|
||||
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};
|
||||
}
|
||||
|
||||
} else {
|
||||
if (key === route) {
|
||||
return {handler:page.handler};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {Router: Router};
|
@ -22,22 +22,6 @@ module.exports = {
|
||||
this.meta_cache = {};
|
||||
},
|
||||
|
||||
init_view({container, main, sidebar}) {
|
||||
this.container = container;
|
||||
|
||||
if (sidebar) {
|
||||
this.sidebar = sidebar;
|
||||
} else {
|
||||
this.sidebar = $('<div class="sidebar"></div>').appendTo(this.container);
|
||||
}
|
||||
|
||||
if (main) {
|
||||
this.main = main;
|
||||
} else {
|
||||
this.main = $('<div class="main"></div>').appendTo(this.container);
|
||||
}
|
||||
},
|
||||
|
||||
get_meta(doctype) {
|
||||
if (!this.meta_cache[doctype]) {
|
||||
this.meta_cache[doctype] = new (this.models.get_meta_class(doctype))(this.models.get('DocType', doctype));
|
||||
|
@ -11,6 +11,10 @@ class Document {
|
||||
// add handlers
|
||||
}
|
||||
|
||||
clear_handlers() {
|
||||
this.handlers = {};
|
||||
}
|
||||
|
||||
add_handler(key, method) {
|
||||
if (!this.handlers[key]) {
|
||||
this.handlers[key] = [];
|
||||
|
@ -5,6 +5,12 @@ class Meta extends Document {
|
||||
constructor(data) {
|
||||
super(data);
|
||||
this.event_handlers = {};
|
||||
this.list_options = {
|
||||
fields: ['name', 'modified']
|
||||
};
|
||||
if (this.setup_meta) {
|
||||
this.setup_meta();
|
||||
}
|
||||
}
|
||||
|
||||
get_field(fieldname) {
|
||||
@ -24,6 +30,15 @@ class Meta extends Document {
|
||||
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 = [];
|
||||
@ -62,15 +77,26 @@ class Meta extends Document {
|
||||
}
|
||||
}
|
||||
|
||||
trigger(key) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// views
|
||||
async get_list(start, limit=20) {
|
||||
// collections
|
||||
async get_list({start, limit=20, filters}) {
|
||||
return await frappe.db.get_all({
|
||||
doctype: this.name,
|
||||
fields: ['name'],
|
||||
fields: this.list_options.fields,
|
||||
filters: filters,
|
||||
start: start,
|
||||
limit: limit
|
||||
});
|
||||
|
@ -1,17 +1,14 @@
|
||||
const frappe = require('frappe-core');
|
||||
|
||||
class todo_meta extends frappe.meta.Meta {
|
||||
async get_list(start, limit=20) {
|
||||
return await frappe.db.get_all({
|
||||
doctype: 'ToDo',
|
||||
fields: ['name', 'subject', 'status', 'description'],
|
||||
start: start,
|
||||
limit: limit
|
||||
});
|
||||
setup_meta() {
|
||||
Object.assign(this, require('./todo.json'));
|
||||
this.name = 'ToDo';
|
||||
this.list_options.fields = ['name', 'subject', 'status', 'description'];
|
||||
}
|
||||
|
||||
get_row_html(data) {
|
||||
return `<a href="/view/todo/${data.name}">${data.subject}</a>`;
|
||||
return `<a href="#todo/${data.name}">${data.subject}</a>`;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,37 +9,37 @@ const models = require('frappe-core/frappe/server/models');
|
||||
const common = require('frappe-core/frappe/common');
|
||||
const bodyParser = require('body-parser');
|
||||
|
||||
async function init({backend, connection_params}) {
|
||||
await frappe.init();
|
||||
common.init_libs(frappe);
|
||||
await frappe.login();
|
||||
|
||||
// walk and find models
|
||||
models.init();
|
||||
|
||||
// database
|
||||
|
||||
frappe.db = await new backends[backend].Database(connection_params);
|
||||
await frappe.db.connect();
|
||||
await frappe.db.migrate();
|
||||
}
|
||||
|
||||
async function start({backend, connection_params}) {
|
||||
await init({backend, connection_params});
|
||||
|
||||
// app
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
|
||||
// routes
|
||||
rest_server.setup(app);
|
||||
|
||||
// listen
|
||||
frappe.app = app;
|
||||
frappe.server = app.listen(frappe.config.port);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: init,
|
||||
start: start
|
||||
async init() {
|
||||
await frappe.init();
|
||||
common.init_libs(frappe);
|
||||
await frappe.login();
|
||||
|
||||
// walk and find models
|
||||
models.init();
|
||||
|
||||
},
|
||||
|
||||
async start({backend, connection_params, static}) {
|
||||
await this.init();
|
||||
|
||||
// database
|
||||
frappe.db = await new backends[backend].Database(connection_params);
|
||||
await frappe.db.connect();
|
||||
await frappe.db.migrate();
|
||||
|
||||
// app
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use(express.static('./'));
|
||||
|
||||
// routes
|
||||
rest_server.setup(app);
|
||||
|
||||
// listen
|
||||
frappe.app = app;
|
||||
frappe.server = app.listen(frappe.config.port);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +0,0 @@
|
||||
const frappe = require('frappe-core');
|
||||
|
||||
module.exports = {
|
||||
setup() {
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user