2
0
mirror of https://github.com/frappe/books.git synced 2024-11-09 23:30:56 +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 value = doc[field.fieldname];
if (value instanceof Date) {
return value.toISOString();
if (field.fieldtype==='Date') {
// date
return value.toISOString().substr(0, 10);
} else {
// datetime
return value.toISOString();
}
} else {
return value;
}

View File

@ -12,7 +12,7 @@ module.exports = class FormPage extends Page {
doctype: doctype,
parent: this.body,
container: this,
actions: ['submit', 'delete']
actions: ['submit', 'delete', 'duplicate', 'settings']
});
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.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.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 = {
lists: {},

View File

@ -22,7 +22,7 @@ module.exports = class Navbar {
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);
item.link = frappe.ui.add('a', 'nav-link', item);
item.link.textContent = label;

View File

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

View File

@ -1,91 +1,55 @@
const frappe = require('frappejs');
const bootstrap = require('bootstrap');
const $ = require('jquery');
class Dropdown {
constructor({parent, label, btn_class = 'btn-secondary', items = []}) {
constructor({parent, label, items = [], right}) {
Object.assign(this, arguments[0]);
Dropdown.instances += 1;
this.id = 'dropdownMenuButton-' + Dropdown.instances;
this.dropdown_items = [];
this.setup_background_click();
this.make();
// init items
if (this.items) {
for (item of this.items) {
this.add_item(item.label, item.action);
for (let item of this.items) {
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() {
this.dropdown = frappe.ui.add('div', 'dropdown', this.parent);
this.make_button();
this.dropdown_menu = frappe.ui.add('div', 'dropdown-menu', this.dropdown);
this.$dropdown = $(`<div class="dropdown ${this.right ? 'float-right' : ''}">
<button class="btn btn-outline-secondary dropdown-toggle"
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() {
this.button = frappe.ui.add('button', 'btn ' + this.btn_class,
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);
addItem(label, action) {
let item = frappe.ui.add('button', 'dropdown-item', this.dropdownMenu);
item.textContent = label;
item.setAttribute('type', 'button');
if (typeof action === 'string') {
item.src = action;
item.addEventListener('click', async () => {
await frappe.router.setRoute(action);
this.toggle();
});
} else {
item.addEventListener('click', async () => {
await action();
this.toggle();
});
}
this.dropdown_items.push(item);
}
float_right() {
frappe.ui.add_class(this.dropdown, 'float-right');
frappe.ui.add_class(this.dropdown_menu, 'dropdown-menu-right');
floatRight() {
frappe.ui.addClass(this.dropdown, 'float-right');
}
}
Dropdown.instances = 0;
module.exports = Dropdown;

View File

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

View File

@ -64,7 +64,7 @@ class BaseControl {
}
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.id = this.id;
}
@ -75,7 +75,7 @@ class BaseControl {
}
}
get_input_parent() {
getInputParent() {
return this.formGroup || this.parent;
}
@ -86,6 +86,7 @@ class BaseControl {
setRequiredAttribute() {
if (this.required) {
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 = {
Data: require('./data'),
Text: require('./text'),
Select: require('./select'),
Link: require('./link'),
Date: require('./date'),
Currency: require('./currency'),
Float: require('./float'),
Int: require('./int'),
Currency: require('./currency'),
Link: require('./link'),
Password: require('./password'),
Table: require('./table')
Select: require('./select'),
Table: require('./table'),
Text: require('./text')
}
module.exports = {

View File

@ -7,7 +7,7 @@ const Modal = require('frappejs/client/ui/modal');
class TableControl extends BaseControl {
make() {
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 =
`<div class="datatable-wrapper"></div>
<div class="table-toolbar">

View File

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

View File

@ -44,18 +44,37 @@ module.exports = class BaseForm extends Observable {
makeToolbar() {
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();
})
}
if (this.actions.includes('delete')) {
this.btnDelete = this.container.addButton(frappe._("Delete"), 'secondary', async (e) => {
if (!this.meta.isSingle && this.actions.includes('delete')) {
let menu = this.container.getDropdown(frappe._('Menu'));
menu.addItem(frappe._("Delete"), async (e) => {
await this.doc.delete();
this.showAlert('Deleted', 'success');
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() {

View File

@ -74,7 +74,7 @@ module.exports = class BaseList {
makeToolbar() {
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));
})
this.btnDelete = this.page.addButton(frappe._('Delete'), 'btn-outline-secondary hide', async () => {
@ -140,7 +140,7 @@ module.exports = class BaseList {
getRow(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];
}

View File

@ -1,11 +1,13 @@
const frappe = require('frappejs');
const Observable = require('frappejs/utils/observable');
const Dropdown = require('frappejs/client/ui/dropdown');
module.exports = class Page extends Observable {
constructor(title) {
super();
this.title = title;
this.make();
this.dropdowns = {};
}
make() {
@ -29,6 +31,13 @@ module.exports = class Page extends Observable {
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) {
if (frappe.router.current_page) {
frappe.router.current_page.hide();

View File

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

View File

@ -14,7 +14,7 @@ module.exports = {
},
initGlobals() {
this.meta_cache = {};
this.metaCache = {};
this.modules = {};
this.docs = {};
this.flags = {
@ -41,10 +41,10 @@ module.exports = {
},
getMeta(doctype) {
if (!this.meta_cache[doctype]) {
this.meta_cache[doctype] = new (this.getMetaClass(doctype))();
if (!this.metaCache[doctype]) {
this.metaCache[doctype] = new (this.getMetaClass(doctype))();
}
return this.meta_cache[doctype];
return this.metaCache[doctype];
},
getMetaClass(doctype) {
@ -71,6 +71,19 @@ module.exports = {
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) {
let controllerClass = this.getControllerClass(data.doctype);
let doc = new controllerClass(data);

View File

@ -70,8 +70,12 @@ module.exports = class BaseDocument extends Observable {
setDefaults() {
for (let field of this.meta.fields) {
if (!this[field.fieldname] && field.default) {
this[field.fieldname] = field.default;
if (field.fieldtype === 'Date') {
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",
"body-parser": "^1.18.2",
"bootstrap": "^4.0.0",
"clusterize.js": "^0.18.0",
"commander": "^2.13.0",
"express": "^4.16.2",
"flatpickr": "^4.3.2",
"jquery": "^3.3.1",
"mysql": "^2.15.0",
"node-fetch": "^1.7.3",
"popper.js": "^1.12.9",
"sortablejs": "^1.7.0",
"sqlite3": "^3.1.13",
"walk": "^2.3.9",
"clusterize.js": "^0.18.0",
"sortablejs": "^1.7.0"
"walk": "^2.3.9"
},
"repository": {
"type": "git",

View File

@ -12,7 +12,7 @@ const bodyParser = require('body-parser');
const path = require('path');
module.exports = {
async start({backend, connection_params, static, models_path}) {
async start({backend, connection_params, models_path}) {
await this.init();
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 = {
format(value, field) {
if (field.fieldtype==='Currency') {
return frappe.format_number(value);
} else {
if (value===null || value===undefined) {
return '';
} else {
return value + '';
}
}
},
slug(text) {
return text.toLowerCase().replace(/ /g, '_');
},

View File

@ -1346,6 +1346,10 @@ flat-cache@^1.2.1:
graceful-fs "^4.1.2"
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:
version "1.0.2"
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"