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

added date control

This commit is contained in:
Rushabh Mehta 2018-02-14 18:20:56 +05:30
parent bc781728aa
commit 1f5e3d8fcb
24 changed files with 186 additions and 129 deletions

View File

@ -249,7 +249,13 @@ module.exports = class Database {
let values = fields.map(field => { let values = fields.map(field => {
let value = doc[field.fieldname]; let value = doc[field.fieldname];
if (value instanceof Date) { if (value instanceof Date) {
return value.toISOString(); if (field.fieldtype==='Date') {
// date
return value.toISOString().substr(0, 10);
} else {
// datetime
return value.toISOString();
}
} else { } else {
return value; return value;
} }

View File

@ -12,7 +12,7 @@ module.exports = class FormPage extends Page {
doctype: doctype, doctype: doctype,
parent: this.body, parent: this.body,
container: this, container: this,
actions: ['submit', 'delete'] actions: ['submit', 'delete', 'duplicate', 'settings']
}); });
this.on('show', async (params) => { this.on('show', async (params) => {

View File

@ -17,9 +17,9 @@ module.exports = class Desk {
this.container = frappe.ui.add('div', 'container-fluid', body); this.container = frappe.ui.add('div', 'container-fluid', body);
this.containerRow = frappe.ui.add('div', 'row', this.container) this.containerRow = frappe.ui.add('div', 'row', this.container)
this.sidebar = frappe.ui.add('div', 'col-md-2 p-3 sidebar d-none d-md-block', this.containerRow); this.sidebar = frappe.ui.add('div', 'col-md-2 sidebar d-none d-md-block', this.containerRow);
this.sidebarList = frappe.ui.add('div', 'list-group list-group-flush', this.sidebar); this.sidebarList = frappe.ui.add('div', 'list-group list-group-flush', this.sidebar);
this.body = frappe.ui.add('div', 'col-md-10 p-4 main', this.containerRow); this.body = frappe.ui.add('div', 'col-md-10 main', this.containerRow);
this.pages = { this.pages = {
lists: {}, lists: {},

View File

@ -22,7 +22,7 @@ module.exports = class Navbar {
this.nav = frappe.ui.add('ul', 'navbar-nav mr-auto', this.navbar_collapse); this.nav = frappe.ui.add('ul', 'navbar-nav mr-auto', this.navbar_collapse);
} }
add_item(label, route) { addItem(label, route) {
let item = frappe.ui.add('li', 'nav-item', this.nav); let item = frappe.ui.add('li', 'nav-item', this.nav);
item.link = frappe.ui.add('a', 'nav-link', item); item.link = frappe.ui.add('a', 'nav-link', item);
item.link.textContent = label; item.link.textContent = label;

View File

@ -1,28 +1,36 @@
@import "node_modules/bootstrap/scss/bootstrap"; @import "node_modules/bootstrap/scss/bootstrap";
@import "node_modules/awesomplete/awesomplete"; @import "node_modules/awesomplete/awesomplete";
@import "node_modules/flatpickr/dist/flatpickr";
@import "node_modules/flatpickr/dist/themes/airbnb";
$spacer-1: 0.25rem; $spacer-1: 0.25rem;
$spacer-2: 0.5rem; $spacer-2: 0.5rem;
$spacer-3: 1rem; $spacer-3: 1rem;
$spacer-4: 2rem; $spacer-4: 2rem;
$page-width: 500px;
html { html {
font-size: 14px; font-size: 12px;
} }
.main { .main {
margin-left: -1px;
border-left: 1px solid $gray-300; border-left: 1px solid $gray-300;
min-height: calc(100vh - 50px); min-height: calc(100vh - 50px);
} }
.sidebar { .sidebar {
margin-right: -1px;
.list-group-item { .list-group-item {
padding: $spacer-2 $spacer-3; padding: $spacer-2 $spacer-3;
border: none; border: none;
} }
.list-group-flush { .list-group-flush {
margin: -$spacer-3; margin-left: -$spacer-3;
margin-right: -$spacer-3;
} }
} }
@ -30,7 +38,13 @@ html {
display: none !important; display: none !important;
} }
.page {
max-width: $page-width;
padding-bottom: $spacer-4;
}
.page-head { .page-head {
padding-top: $spacer-3;
padding-bottom: $spacer-3; padding-bottom: $spacer-3;
.btn { .btn {
@ -44,7 +58,7 @@ html {
} }
.form-body { .form-body {
max-width: 600px; max-width: $page-width;
.form-toolbar { .form-toolbar {
height: $spacer-4; height: $spacer-4;
@ -56,19 +70,27 @@ html {
} }
} }
.list-row {
.checkbox {
margin-right: $spacer-2;
}
a, a:hover, a:visited, a:active { .list-body {
color: $gray-800; margin-left: -15px;
margin-right: -15px;
.list-row {
padding: $spacer-2;
border-bottom: 1px solid $gray-200;
.checkbox {
margin-right: $spacer-2;
}
a, a:hover, a:visited, a:active {
color: $gray-800;
}
} }
} }
.dropdown-menu-right {
right: 0; .dropdown-item {
left: auto; padding: $spacer-2 $spacer-3;
} }
.awesomplete { .awesomplete {
@ -114,6 +136,11 @@ mark {
margin-top: $spacer-2; margin-top: $spacer-2;
} }
.data-table thead td {
border-bottom: 0px !important;
background-color: $gray-200 !important;
}
.data-table-col .edit-cell { .data-table-col .edit-cell {
padding: 0px !important; padding: 0px !important;

View File

@ -1,91 +1,55 @@
const frappe = require('frappejs'); const frappe = require('frappejs');
const bootstrap = require('bootstrap');
const $ = require('jquery');
class Dropdown { class Dropdown {
constructor({parent, label, btn_class = 'btn-secondary', items = []}) { constructor({parent, label, items = [], right}) {
Object.assign(this, arguments[0]); Object.assign(this, arguments[0]);
Dropdown.instances += 1;
this.id = 'dropdownMenuButton-' + Dropdown.instances;
this.dropdown_items = [];
this.setup_background_click();
this.make(); this.make();
// init items // init items
if (this.items) { if (this.items) {
for (item of this.items) { for (let item of this.items) {
this.add_item(item.label, item.action); this.addItem(item.label, item.action);
} }
} }
} }
setup_background_click() {
if (!document.dropdown_setup) {
frappe.dropdowns = [];
// setup hiding all dropdowns on click
document.addEventListener('click', (event) => {
for (let d of frappe.dropdowns) {
if (d.button !== event.target) {
d.collapse();
}
}
});
document.dropdown_setup = true;
}
frappe.dropdowns.push(this);
}
make() { make() {
this.dropdown = frappe.ui.add('div', 'dropdown', this.parent); this.$dropdown = $(`<div class="dropdown ${this.right ? 'float-right' : ''}">
this.make_button(); <button class="btn btn-outline-secondary dropdown-toggle"
this.dropdown_menu = frappe.ui.add('div', 'dropdown-menu', this.dropdown); type="button" id="${this.id}" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">${this.label}
</button>
<div class="dropdown-menu ${this.right ? 'dropdown-menu-right' : ''}" aria-labelledby="${this.id}"></div>
</div>`).appendTo(this.parent)
this.dropdown = this.$dropdown.get(0);
this.dropdownMenu = this.dropdown.querySelector('.dropdown-menu');
} }
make_button() { addItem(label, action) {
this.button = frappe.ui.add('button', 'btn ' + this.btn_class, let item = frappe.ui.add('button', 'dropdown-item', this.dropdownMenu);
this.dropdown);
frappe.ui.add_class(this.button, 'dropdown-toggle');
this.button.textContent = this.label;
this.button.addEventListener('click', () => {
this.toggle();
});
}
expand() {
this.dropdown.classList.add('show');
this.dropdown_menu.classList.add('show');
}
collapse() {
this.dropdown.classList.remove('show');
this.dropdown_menu.classList.remove('show');
}
toggle() {
this.dropdown.classList.toggle('show');
this.dropdown_menu.classList.toggle('show');
}
add_item(label, action) {
let item = frappe.ui.add('button', 'dropdown-item', this.dropdown_menu);
item.textContent = label; item.textContent = label;
item.setAttribute('type', 'button'); item.setAttribute('type', 'button');
if (typeof action === 'string') { if (typeof action === 'string') {
item.src = action;
item.addEventListener('click', async () => { item.addEventListener('click', async () => {
await frappe.router.setRoute(action); await frappe.router.setRoute(action);
this.toggle();
}); });
} else { } else {
item.addEventListener('click', async () => { item.addEventListener('click', async () => {
await action(); await action();
this.toggle();
}); });
} }
this.dropdown_items.push(item);
} }
float_right() { floatRight() {
frappe.ui.add_class(this.dropdown, 'float-right'); frappe.ui.addClass(this.dropdown, 'float-right');
frappe.ui.add_class(this.dropdown_menu, 'dropdown-menu-right');
} }
} }
Dropdown.instances = 0;
module.exports = Dropdown; module.exports = Dropdown;

View File

@ -6,7 +6,7 @@ module.exports = {
let element = document.createElement(tag); let element = document.createElement(tag);
if (className) { if (className) {
for (let c of className.split(' ')) { for (let c of className.split(' ')) {
this.add_class(element, c); this.addClass(element, c);
} }
} }
if (parent) { if (parent) {
@ -19,7 +19,7 @@ module.exports = {
element.parentNode.removeChild(element); element.parentNode.removeChild(element);
}, },
add_class(element, className) { addClass(element, className) {
if (element.classList) { if (element.classList) {
element.classList.add(className); element.classList.add(className);
} else { } else {

View File

@ -64,7 +64,7 @@ class BaseControl {
} }
makeInput() { makeInput() {
this.input = frappe.ui.add('input', 'form-control', this.get_input_parent()); this.input = frappe.ui.add('input', 'form-control', this.getInputParent());
this.input.autocomplete = "off"; this.input.autocomplete = "off";
this.input.id = this.id; this.input.id = this.id;
} }
@ -75,7 +75,7 @@ class BaseControl {
} }
} }
get_input_parent() { getInputParent() {
return this.formGroup || this.parent; return this.formGroup || this.parent;
} }
@ -86,6 +86,7 @@ class BaseControl {
setRequiredAttribute() { setRequiredAttribute() {
if (this.required) { if (this.required) {
this.input.required = true; this.input.required = true;
this.input.classList.add('font-weight-bold');
} }
} }

View File

@ -0,0 +1,14 @@
const flatpickr = require('flatpickr');
const BaseControl = require('./base');
class DateControl extends BaseControl {
make() {
super.make();
this.input.setAttribute('type', 'text');
flatpickr.default(this.input, {
dateFormat: "Y-m-d"
});
}
};
module.exports = DateControl;

View File

@ -1,11 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="js/bundle.js"></script>
</body>
</html>

View File

@ -1,13 +1,14 @@
const control_classes = { const control_classes = {
Data: require('./data'), Data: require('./data'),
Text: require('./text'), Date: require('./date'),
Select: require('./select'), Currency: require('./currency'),
Link: require('./link'),
Float: require('./float'), Float: require('./float'),
Int: require('./int'), Int: require('./int'),
Currency: require('./currency'), Link: require('./link'),
Password: require('./password'), Password: require('./password'),
Table: require('./table') Select: require('./select'),
Table: require('./table'),
Text: require('./text')
} }
module.exports = { module.exports = {

View File

@ -7,7 +7,7 @@ const Modal = require('frappejs/client/ui/modal');
class TableControl extends BaseControl { class TableControl extends BaseControl {
make() { make() {
if (!this.datatable) { if (!this.datatable) {
this.wrapper = frappe.ui.add('div', 'table-wrapper', this.get_input_parent()); this.wrapper = frappe.ui.add('div', 'table-wrapper', this.getInputParent());
this.wrapper.innerHTML = this.wrapper.innerHTML =
`<div class="datatable-wrapper"></div> `<div class="datatable-wrapper"></div>
<div class="table-toolbar"> <div class="table-toolbar">

View File

@ -3,7 +3,7 @@ const frappe = require('frappejs');
class TextControl extends BaseControl { class TextControl extends BaseControl {
makeInput() { makeInput() {
this.input = frappe.ui.add('textarea', 'form-control', this.get_input_parent()); this.input = frappe.ui.add('textarea', 'form-control', this.getInputParent());
} }
make() { make() {
super.make(); super.make();

View File

@ -44,18 +44,37 @@ module.exports = class BaseForm extends Observable {
makeToolbar() { makeToolbar() {
if (this.actions.includes('submit')) { if (this.actions.includes('submit')) {
this.btnSubmit = this.container.addButton(frappe._("Save"), 'primary', async (event) => { this.container.addButton(frappe._("Save"), 'primary', async (event) => {
await this.submit(); await this.submit();
}) })
} }
if (this.actions.includes('delete')) { if (!this.meta.isSingle && this.actions.includes('delete')) {
this.btnDelete = this.container.addButton(frappe._("Delete"), 'secondary', async (e) => { let menu = this.container.getDropdown(frappe._('Menu'));
menu.addItem(frappe._("Delete"), async (e) => {
await this.doc.delete(); await this.doc.delete();
this.showAlert('Deleted', 'success'); this.showAlert('Deleted', 'success');
this.trigger('delete'); this.trigger('delete');
}); });
} }
if (!this.meta.isSingle && this.actions.includes('duplicate')) {
let menu = this.container.getDropdown(frappe._('Menu'));
menu.addItem(frappe._('Duplicate'), async () => {
let newDoc = await frappe.getDuplicate(this.doc);
console.log(newDoc);
await frappe.router.setRoute('edit', newDoc.doctype, newDoc.name);
newDoc.set('name', '');
});
}
if (this.meta.settings && this.actions.includes('settings')) {
let menu = this.container.getDropdown(frappe._('Menu'));
menu.addItem(frappe._('Settings...'), () => {
frappe.router.setRoute('edit', frappe.slug(this.meta.settings), this.meta.settings);
});
}
} }
bindKeyboard() { bindKeyboard() {

View File

@ -74,7 +74,7 @@ module.exports = class BaseList {
makeToolbar() { makeToolbar() {
this.makeSearch(); this.makeSearch();
this.btnNew = this.page.addButton(frappe._('New'), 'btn-outline-primary', async () => { this.btnNew = this.page.addButton(frappe._('New'), 'btn-primary', async () => {
await frappe.router.setRoute('new', frappe.slug(this.doctype)); await frappe.router.setRoute('new', frappe.slug(this.doctype));
}) })
this.btnDelete = this.page.addButton(frappe._('Delete'), 'btn-outline-secondary hide', async () => { this.btnDelete = this.page.addButton(frappe._('Delete'), 'btn-outline-secondary hide', async () => {
@ -140,7 +140,7 @@ module.exports = class BaseList {
getRow(i) { getRow(i) {
if (!this.rows[i]) { if (!this.rows[i]) {
this.rows[i] = frappe.ui.add('div', 'list-row py-2', this.body); this.rows[i] = frappe.ui.add('div', 'list-row', this.body);
} }
return this.rows[i]; return this.rows[i];
} }

View File

@ -1,11 +1,13 @@
const frappe = require('frappejs'); const frappe = require('frappejs');
const Observable = require('frappejs/utils/observable'); const Observable = require('frappejs/utils/observable');
const Dropdown = require('frappejs/client/ui/dropdown');
module.exports = class Page extends Observable { module.exports = class Page extends Observable {
constructor(title) { constructor(title) {
super(); super();
this.title = title; this.title = title;
this.make(); this.make();
this.dropdowns = {};
} }
make() { make() {
@ -29,6 +31,13 @@ module.exports = class Page extends Observable {
return this.button; return this.button;
} }
getDropdown(label) {
if (!this.dropdowns[label]) {
this.dropdowns[label] = new Dropdown({parent: this.head, label: label, right: true});
}
return this.dropdowns[label];
}
async show(params) { async show(params) {
if (frappe.router.current_page) { if (frappe.router.current_page) {
frappe.router.current_page.hide(); frappe.router.current_page.hide();

View File

@ -1,19 +1,16 @@
const utils = require('../utils'); const utils = require('../utils');
const number_format = require('../utils/number_format'); const number_format = require('../utils/number_format');
const format = require('../utils/format');
const model = require('../model'); const model = require('../model');
const BaseDocument = require('../model/document');
const BaseMeta = require('../model/meta');
const _session = require('../session'); const _session = require('../session');
const errors = require('./errors'); const errors = require('./errors');
module.exports = { module.exports = {
init_libs(frappe) { init_libs(frappe) {
Object.assign(frappe, utils); Object.assign(frappe, utils);
Object.assign(frappe, number_format); Object.assign(frappe, number_format);
Object.assign(frappe, format);
frappe.model = model; frappe.model = model;
frappe.BaseDocument = BaseDocument;
frappe.BaseMeta = BaseMeta;
frappe._session = _session; frappe._session = _session;
frappe.errors = errors; frappe.errors = errors;
} }

View File

@ -14,7 +14,7 @@ module.exports = {
}, },
initGlobals() { initGlobals() {
this.meta_cache = {}; this.metaCache = {};
this.modules = {}; this.modules = {};
this.docs = {}; this.docs = {};
this.flags = { this.flags = {
@ -41,10 +41,10 @@ module.exports = {
}, },
getMeta(doctype) { getMeta(doctype) {
if (!this.meta_cache[doctype]) { if (!this.metaCache[doctype]) {
this.meta_cache[doctype] = new (this.getMetaClass(doctype))(); this.metaCache[doctype] = new (this.getMetaClass(doctype))();
} }
return this.meta_cache[doctype]; return this.metaCache[doctype];
}, },
getMetaClass(doctype) { getMetaClass(doctype) {
@ -71,6 +71,19 @@ module.exports = {
return await this.getDoc(doctype, doctype); return await this.getDoc(doctype, doctype);
}, },
async getDuplicate(doc) {
const newDoc = await this.getNewDoc(doc.doctype);
for (let field of this.getMeta(doc.doctype).getValidFields()) {
if (field.fieldname === 'name') continue;
if (field.fieldtype === 'Table') {
newDoc[field.fieldname] = (doc[field.fieldname] || []).map(d => Object.assign({}, d));
} else {
newDoc[field.fieldname] = doc[field.fieldname];
}
}
return newDoc;
},
newDoc(data) { newDoc(data) {
let controllerClass = this.getControllerClass(data.doctype); let controllerClass = this.getControllerClass(data.doctype);
let doc = new controllerClass(data); let doc = new controllerClass(data);

View File

@ -70,8 +70,12 @@ module.exports = class BaseDocument extends Observable {
setDefaults() { setDefaults() {
for (let field of this.meta.fields) { for (let field of this.meta.fields) {
if (!this[field.fieldname] && field.default) { if (field.fieldtype === 'Date') {
this[field.fieldname] = field.default; this[field.fieldname] = (new Date()).toISOString().substr(0, 10);
} else if (!this[field.fieldname]) {
if(field.default) {
this[field.fieldname] = field.default;
}
} }
} }
} }

View File

@ -12,16 +12,17 @@
"awesomplete": "^1.1.2", "awesomplete": "^1.1.2",
"body-parser": "^1.18.2", "body-parser": "^1.18.2",
"bootstrap": "^4.0.0", "bootstrap": "^4.0.0",
"clusterize.js": "^0.18.0",
"commander": "^2.13.0", "commander": "^2.13.0",
"express": "^4.16.2", "express": "^4.16.2",
"flatpickr": "^4.3.2",
"jquery": "^3.3.1", "jquery": "^3.3.1",
"mysql": "^2.15.0", "mysql": "^2.15.0",
"node-fetch": "^1.7.3", "node-fetch": "^1.7.3",
"popper.js": "^1.12.9", "popper.js": "^1.12.9",
"sortablejs": "^1.7.0",
"sqlite3": "^3.1.13", "sqlite3": "^3.1.13",
"walk": "^2.3.9", "walk": "^2.3.9"
"clusterize.js": "^0.18.0",
"sortablejs": "^1.7.0"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -12,7 +12,7 @@ const bodyParser = require('body-parser');
const path = require('path'); const path = require('path');
module.exports = { module.exports = {
async start({backend, connection_params, static, models_path}) { async start({backend, connection_params, models_path}) {
await this.init(); await this.init();
this.init_models(models_path); this.init_models(models_path);

20
utils/format.js Normal file
View File

@ -0,0 +1,20 @@
const number_format = require('./number_format');
module.exports = {
format(value, field) {
if (field.fieldtype==='Currency') {
value = number_format.format_number(value);
} else if (field.fieldtype==='Date') {
if (value instanceof Date) {
value = value.toISOString().substr(0, 10);
}
} else {
if (value===null || value===undefined) {
value = '';
} else {
value = value + '';
}
}
return value;
}
}

View File

@ -1,16 +1,4 @@
module.exports = { module.exports = {
format(value, field) {
if (field.fieldtype==='Currency') {
return frappe.format_number(value);
} else {
if (value===null || value===undefined) {
return '';
} else {
return value + '';
}
}
},
slug(text) { slug(text) {
return text.toLowerCase().replace(/ /g, '_'); return text.toLowerCase().replace(/ /g, '_');
}, },

View File

@ -1346,6 +1346,10 @@ flat-cache@^1.2.1:
graceful-fs "^4.1.2" graceful-fs "^4.1.2"
write "^0.2.1" write "^0.2.1"
flatpickr@^4.3.2:
version "4.3.2"
resolved "https://registry.yarnpkg.com/flatpickr/-/flatpickr-4.3.2.tgz#6a477043c075ef36c3ff54fadb49b936a64d635f"
flatten@^1.0.2: flatten@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"