2
0
mirror of https://github.com/frappe/books.git synced 2024-11-08 14:50:56 +00:00

added forms + controls

This commit is contained in:
Rushabh Mehta 2018-01-09 19:10:56 +05:30
parent 3b8ea0755c
commit d9e306b641
17 changed files with 441 additions and 73 deletions

23
frappe/client/index.js Normal file
View 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
View 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'), ' ');
}
}
}

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

View File

@ -0,0 +1,10 @@
const BaseControl = require('./base');
class DataControl extends BaseControl {
make() {
super.make();
this.input.setAttribute('type', 'text');
}
};
module.exports = DataControl;

View 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;
}
}

View 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};

View 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);
}
},
}

View File

@ -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];
}

View 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 };

View 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};

View File

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

View File

@ -11,6 +11,10 @@ class Document {
// add handlers
}
clear_handlers() {
this.handlers = {};
}
add_handler(key, method) {
if (!this.handlers[key]) {
this.handlers[key] = [];

View File

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

View File

@ -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>`;
}
}

View File

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

View File

@ -1,6 +0,0 @@
const frappe = require('frappe-core');
module.exports = {
setup() {
}
}

View File