App loaded
This commit is contained in:
parent
5474d7408d
commit
ed3432b568
|
|
@ -1,32 +1,22 @@
|
||||||
app_name = "frappe_enhanced_gridview"
|
app_name = "frappe_enhanced_gridview"
|
||||||
app_title = "Frappe Enhanced Gridview"
|
app_title = "Frappe Enhanced Gridview"
|
||||||
app_publisher = "Jey ERP"
|
app_publisher = "Nesscale Solutions Private Limited"
|
||||||
app_description = "App that allows more than 10 columns"
|
app_description = (
|
||||||
app_email = "info@jeyerp.az"
|
"Frappe Grid view with scrollablee child table and allow more than 10 columns"
|
||||||
app_license = "unlicense"
|
)
|
||||||
|
app_email = "info@nesscale.com"
|
||||||
# Apps
|
app_license = "mit"
|
||||||
# ------------------
|
|
||||||
|
|
||||||
# required_apps = []
|
# required_apps = []
|
||||||
|
|
||||||
# Each item in the list will be shown as an app in the apps page
|
|
||||||
# add_to_apps_screen = [
|
|
||||||
# {
|
|
||||||
# "name": "frappe_enhanced_gridview",
|
|
||||||
# "logo": "/assets/frappe_enhanced_gridview/logo.png",
|
|
||||||
# "title": "Frappe Enhanced Gridview",
|
|
||||||
# "route": "/frappe_enhanced_gridview",
|
|
||||||
# "has_permission": "frappe_enhanced_gridview.api.permission.has_app_permission"
|
|
||||||
# }
|
|
||||||
# ]
|
|
||||||
|
|
||||||
# Includes in <head>
|
# Includes in <head>
|
||||||
# ------------------
|
# ------------------
|
||||||
|
|
||||||
# include js, css files in header of desk.html
|
# include js, css files in header of desk.html
|
||||||
# app_include_css = "/assets/frappe_enhanced_gridview/css/frappe_enhanced_gridview.css"
|
# app_include_css = "/assets/frappe_enhanced_gridview/css/frappe_enhanced_gridview.css"
|
||||||
# app_include_js = "/assets/frappe_enhanced_gridview/js/frappe_enhanced_gridview.js"
|
# app_include_js = "/assets/frappe_enhanced_gridview/js/frappe_enhanced_gridview.js"
|
||||||
|
app_include_css = ["enhanced_gridview.bundle.css"]
|
||||||
|
app_include_js = ["enhanced_gridview.bundle.js"]
|
||||||
|
|
||||||
|
|
||||||
# include js, css files in header of web template
|
# include js, css files in header of web template
|
||||||
# web_include_css = "/assets/frappe_enhanced_gridview/css/frappe_enhanced_gridview.css"
|
# web_include_css = "/assets/frappe_enhanced_gridview/css/frappe_enhanced_gridview.css"
|
||||||
|
|
@ -70,9 +60,6 @@ app_license = "unlicense"
|
||||||
# automatically create page for each record of this doctype
|
# automatically create page for each record of this doctype
|
||||||
# website_generators = ["Web Page"]
|
# website_generators = ["Web Page"]
|
||||||
|
|
||||||
# automatically load and sync documents of this doctype from downstream apps
|
|
||||||
# importable_doctypes = [doctype_1]
|
|
||||||
|
|
||||||
# Jinja
|
# Jinja
|
||||||
# ----------
|
# ----------
|
||||||
|
|
||||||
|
|
@ -244,4 +231,3 @@ app_license = "unlicense"
|
||||||
# default_log_clearing_doctypes = {
|
# default_log_clearing_doctypes = {
|
||||||
# "Logging DocType Name": 30 # days to retain logs
|
# "Logging DocType Name": 30 # days to retain logs
|
||||||
# }
|
# }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,349 @@
|
||||||
|
import GridRow from './grid_row';
|
||||||
|
import Grid from './grid';
|
||||||
|
|
||||||
|
class Custom_GridRow extends GridRow {
|
||||||
|
|
||||||
|
validate_columns_width() {
|
||||||
|
let total_column_width = 0.0;
|
||||||
|
|
||||||
|
this.selected_columns_for_grid.forEach((row) => {
|
||||||
|
if (row.columns && row.columns > 0) {
|
||||||
|
total_column_width += cint(row.columns);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// if (total_column_width && total_column_width > 10) {
|
||||||
|
// frappe.throw(__("The total column width cannot be more than 10."));
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
show_form() {
|
||||||
|
super.show_form()
|
||||||
|
|
||||||
|
$(this.grid.form_grid).removeClass("relative-important");
|
||||||
|
}
|
||||||
|
hide_form() {
|
||||||
|
super.hide_form()
|
||||||
|
|
||||||
|
$(this.grid.form_grid).addClass("relative-important");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Custom_Grid extends Grid {
|
||||||
|
|
||||||
|
make() {
|
||||||
|
let template = `
|
||||||
|
<div class="grid-field">
|
||||||
|
<label class="control-label">${__(this.df.label || "")}</label>
|
||||||
|
<span class="help"></span>
|
||||||
|
<p class="text-muted small grid-description"></p>
|
||||||
|
<div class="grid-custom-buttons"></div>
|
||||||
|
<div class="form-grid-container enhanced-grid-container">
|
||||||
|
<div class="form-grid">
|
||||||
|
<div class="grid-heading-row"></div>
|
||||||
|
<div class="grid-body">
|
||||||
|
<div class="rows"></div>
|
||||||
|
<div class="grid-empty text-center">
|
||||||
|
<img
|
||||||
|
src="/assets/frappe/images/ui-states/grid-empty-state.svg"
|
||||||
|
alt="Grid Empty State"
|
||||||
|
class="grid-empty-illustration"
|
||||||
|
>
|
||||||
|
${__("No Data")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input type="range" min="1" max="100" value="1" class="enhanced-slider">
|
||||||
|
</div>
|
||||||
|
<div class="small form-clickable-section grid-footer">
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<div class="grid-buttons">
|
||||||
|
<button type="button" class="btn btn-xs btn-danger grid-remove-rows hidden"
|
||||||
|
data-action="delete_rows">
|
||||||
|
${__("Delete")}
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-xs btn-danger grid-remove-all-rows hidden"
|
||||||
|
data-action="delete_all_rows">
|
||||||
|
${__("Delete All")}
|
||||||
|
</button>
|
||||||
|
<!-- hack to allow firefox include this in tabs -->
|
||||||
|
<button type="button" class="btn btn-xs btn-secondary grid-add-row">
|
||||||
|
${__("Add Row")}
|
||||||
|
</button>
|
||||||
|
<button type="button" class="grid-add-multiple-rows btn btn-xs btn-secondary hidden">
|
||||||
|
${__("Add Multiple")}</a>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="grid-pagination">
|
||||||
|
</div>
|
||||||
|
<div class="grid-bulk-actions text-right">
|
||||||
|
<button type="button" class="grid-download btn btn-xs btn-secondary hidden">
|
||||||
|
${__("Download")}
|
||||||
|
</button>
|
||||||
|
<button type="button" class="grid-upload btn btn-xs btn-secondary hidden">
|
||||||
|
${__("Upload")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
this.wrapper = $(template).appendTo(this.parent);
|
||||||
|
$(this.parent).addClass("form-group");
|
||||||
|
this.set_grid_description();
|
||||||
|
this.set_doc_url();
|
||||||
|
|
||||||
|
frappe.utils.bind_actions_with_object(this.wrapper, this);
|
||||||
|
|
||||||
|
this.form_grid = this.wrapper.find(".form-grid");
|
||||||
|
|
||||||
|
|
||||||
|
// enhance slider changes
|
||||||
|
this.form_grid.addClass("relative-important");
|
||||||
|
this.form_grid_container = this.wrapper.find(".form-grid-container");
|
||||||
|
this.enhanced_slider = this.wrapper.find(".enhanced-slider");
|
||||||
|
let me = this
|
||||||
|
this.enhanced_slider.on("input", function (event) {
|
||||||
|
const value = event.target.value;
|
||||||
|
me.form_grid.css("left", `-${value}px`)
|
||||||
|
me.setup_scrollable_width()
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
this.setup_add_row();
|
||||||
|
|
||||||
|
this.setup_grid_pagination();
|
||||||
|
|
||||||
|
this.custom_buttons = {};
|
||||||
|
this.grid_buttons = this.wrapper.find(".grid-buttons");
|
||||||
|
this.grid_custom_buttons = this.wrapper.find(".grid-custom-buttons");
|
||||||
|
this.remove_rows_button = this.grid_buttons.find(".grid-remove-rows");
|
||||||
|
this.remove_all_rows_button = this.grid_buttons.find(".grid-remove-all-rows");
|
||||||
|
|
||||||
|
this.setup_allow_bulk_edit();
|
||||||
|
this.setup_check();
|
||||||
|
if (this.df.on_setup) {
|
||||||
|
this.df.on_setup(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
make_head() {
|
||||||
|
if (this.prevent_build) return;
|
||||||
|
|
||||||
|
// labels
|
||||||
|
if (this.header_row) {
|
||||||
|
$(this.parent).find(".grid-heading-row .grid-row").remove();
|
||||||
|
}
|
||||||
|
// implement custom class
|
||||||
|
this.header_row = new Custom_GridRow({
|
||||||
|
parent: $(this.parent).find(".grid-heading-row"),
|
||||||
|
parent_df: this.df,
|
||||||
|
docfields: this.docfields,
|
||||||
|
frm: this.frm,
|
||||||
|
grid: this,
|
||||||
|
configure_columns: true,
|
||||||
|
});
|
||||||
|
// implement custom class
|
||||||
|
this.header_search = new Custom_GridRow({
|
||||||
|
parent: $(this.parent).find(".grid-heading-row"),
|
||||||
|
parent_df: this.df,
|
||||||
|
docfields: this.docfields,
|
||||||
|
frm: this.frm,
|
||||||
|
grid: this,
|
||||||
|
show_search: true,
|
||||||
|
});
|
||||||
|
this.header_search.row.addClass("filter-row");
|
||||||
|
if (this.header_search.show_search || this.header_search.show_search_row()) {
|
||||||
|
$(this.parent).find(".grid-heading-row").addClass("with-filter");
|
||||||
|
} else {
|
||||||
|
$(this.parent).find(".grid-heading-row").removeClass("with-filter");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.filter_applied && this.update_search_columns();
|
||||||
|
}
|
||||||
|
|
||||||
|
render_result_rows($rows, append_row) {
|
||||||
|
let result_length = this.grid_pagination.get_result_length();
|
||||||
|
let page_index = this.grid_pagination.page_index;
|
||||||
|
let page_length = this.grid_pagination.page_length;
|
||||||
|
if (!this.grid_rows) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (var ri = (page_index - 1) * page_length; ri < result_length; ri++) {
|
||||||
|
var d = this.data[ri];
|
||||||
|
if (!d) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (d.idx === undefined) {
|
||||||
|
d.idx = ri + 1;
|
||||||
|
}
|
||||||
|
if (d.name === undefined) {
|
||||||
|
d.name = "row " + d.idx;
|
||||||
|
}
|
||||||
|
let grid_row;
|
||||||
|
if (this.grid_rows[ri] && !append_row) {
|
||||||
|
grid_row = this.grid_rows[ri];
|
||||||
|
grid_row.doc = d;
|
||||||
|
grid_row.refresh();
|
||||||
|
} else {
|
||||||
|
// implement custom class
|
||||||
|
grid_row = new Custom_GridRow({
|
||||||
|
parent: $rows,
|
||||||
|
parent_df: this.df,
|
||||||
|
docfields: this.docfields,
|
||||||
|
doc: d,
|
||||||
|
frm: this.frm,
|
||||||
|
grid: this,
|
||||||
|
});
|
||||||
|
this.grid_rows[ri] = grid_row;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.grid_rows_by_docname[d.name] = grid_row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_visible_columns() {
|
||||||
|
if (this.visible_columns && this.visible_columns.length > 0) return;
|
||||||
|
|
||||||
|
this.user_defined_columns = [];
|
||||||
|
this.setup_user_defined_columns();
|
||||||
|
var total_colsize = 1,
|
||||||
|
fields =
|
||||||
|
this.user_defined_columns && this.user_defined_columns.length > 0
|
||||||
|
? this.user_defined_columns
|
||||||
|
: this.editable_fields || this.docfields;
|
||||||
|
|
||||||
|
this.visible_columns = [];
|
||||||
|
|
||||||
|
for (var ci in fields) {
|
||||||
|
var _df = fields[ci];
|
||||||
|
|
||||||
|
// get docfield if from fieldname
|
||||||
|
df =
|
||||||
|
this.user_defined_columns && this.user_defined_columns.length > 0
|
||||||
|
? _df
|
||||||
|
: this.fields_map[_df.fieldname];
|
||||||
|
|
||||||
|
if (
|
||||||
|
df &&
|
||||||
|
!df.hidden &&
|
||||||
|
(this.editable_fields || df.in_list_view) &&
|
||||||
|
((this.frm && this.frm.get_perm(df.permlevel, "read")) || !this.frm) &&
|
||||||
|
!frappe.model.layout_fields.includes(df.fieldtype)
|
||||||
|
) {
|
||||||
|
if (df.columns) {
|
||||||
|
df.colsize = df.columns;
|
||||||
|
} else {
|
||||||
|
this.update_default_colsize(df);
|
||||||
|
}
|
||||||
|
|
||||||
|
// attach formatter on refresh
|
||||||
|
if (
|
||||||
|
df.fieldtype == "Link" &&
|
||||||
|
!df.formatter &&
|
||||||
|
df.parent &&
|
||||||
|
frappe.meta.docfield_map[df.parent]
|
||||||
|
) {
|
||||||
|
const docfield = frappe.meta.docfield_map[df.parent][df.fieldname];
|
||||||
|
if (docfield && docfield.formatter) {
|
||||||
|
df.formatter = docfield.formatter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
total_colsize += df.colsize;
|
||||||
|
if (total_colsize > 100) return false; // Increased limit to 20
|
||||||
|
this.visible_columns.push([df, df.colsize]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// redistribute if total-col size is less than 12
|
||||||
|
var passes = 0;
|
||||||
|
while (total_colsize < 11 && passes < 12) { // Adjusted loop conditions
|
||||||
|
for (var i in this.visible_columns) {
|
||||||
|
var df = this.visible_columns[i][0];
|
||||||
|
var colsize = this.visible_columns[i][1];
|
||||||
|
if (colsize > 1 && colsize < 11 && frappe.model.is_non_std_field(df.fieldname)) {
|
||||||
|
if (
|
||||||
|
passes < 3 &&
|
||||||
|
["Int", "Currency", "Float", "Check", "Percent"].indexOf(df.fieldtype) !==
|
||||||
|
-1
|
||||||
|
) {
|
||||||
|
// don't increase col size of these fields in first 3 passes
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.visible_columns[i][1] += 1;
|
||||||
|
total_colsize++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (total_colsize > 10) break;
|
||||||
|
}
|
||||||
|
passes++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set width of scrollable area
|
||||||
|
this.setup_scrollable_width()
|
||||||
|
this.verify_overflow_columns_width()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
setup_scrollable_width() {
|
||||||
|
let width = 200
|
||||||
|
this.visible_columns.forEach(column => {
|
||||||
|
width += column[1] * 50 + 100
|
||||||
|
});
|
||||||
|
if (width > this.form_grid_container[0].clientWidth) {
|
||||||
|
this.enhanced_slider.prop("max", width - this.form_grid_container[0].clientWidth)
|
||||||
|
this.enhanced_slider.prop("style", "display:block")
|
||||||
|
} else {
|
||||||
|
this.form_grid.css("left", `0px`)
|
||||||
|
this.enhanced_slider.prop("max", this.form_grid_container[0].clientWidth)
|
||||||
|
this.enhanced_slider.prop("style", "display:none")
|
||||||
|
this.enhanced_slider.prop("value", 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_overflow_columns_width() {
|
||||||
|
let width = 200
|
||||||
|
this.visible_columns.forEach(column => {
|
||||||
|
width += column[1] * 50 + 100
|
||||||
|
});
|
||||||
|
|
||||||
|
if (width > this.form_grid_container[0].clientWidth) {
|
||||||
|
this.form_grid_container.addClass('enhanced-grid-container')
|
||||||
|
this.enhanced_slider.prop("style", "display:block")
|
||||||
|
} else {
|
||||||
|
this.enhanced_slider.prop("style", "display:none")
|
||||||
|
this.enhanced_slider.prop("value", 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
frappe.ui.form.ControlTable = class CustomControlTable extends frappe.ui.form.ControlTable {
|
||||||
|
make() {
|
||||||
|
super.make();
|
||||||
|
|
||||||
|
// add title if prev field is not column / section heading or html
|
||||||
|
this.grid = new Custom_Grid({
|
||||||
|
frm: this.frm,
|
||||||
|
df: this.df,
|
||||||
|
parent: this.wrapper,
|
||||||
|
control: this,
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,184 @@
|
||||||
|
export default class GridPagination {
|
||||||
|
constructor(opts) {
|
||||||
|
$.extend(this, opts);
|
||||||
|
this.setup_pagination();
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_pagination() {
|
||||||
|
this.page_length = 50;
|
||||||
|
this.page_index = 1;
|
||||||
|
this.total_pages = Math.ceil(this.grid.data.length / this.page_length);
|
||||||
|
|
||||||
|
this.render_pagination();
|
||||||
|
}
|
||||||
|
|
||||||
|
render_pagination() {
|
||||||
|
if (this.grid.data.length <= this.page_length) {
|
||||||
|
this.wrapper.find(".grid-pagination").html("");
|
||||||
|
} else {
|
||||||
|
let $pagination_template = this.get_pagination_html();
|
||||||
|
this.wrapper.find(".grid-pagination").html($pagination_template);
|
||||||
|
this.prev_page_button = this.wrapper.find(".prev-page");
|
||||||
|
this.next_page_button = this.wrapper.find(".next-page");
|
||||||
|
this.$page_number = this.wrapper.find(".current-page-number");
|
||||||
|
this.$total_pages = this.wrapper.find(".total-page-number");
|
||||||
|
this.first_page_button = this.wrapper.find(".first-page");
|
||||||
|
this.last_page_button = this.wrapper.find(".last-page");
|
||||||
|
|
||||||
|
this.bind_pagination_events();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bind_pagination_events() {
|
||||||
|
this.prev_page_button.on("click", () => {
|
||||||
|
this.render_prev_page();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.next_page_button.on("click", () => {
|
||||||
|
this.render_next_page();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.first_page_button.on("click", () => {
|
||||||
|
this.go_to_page(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.last_page_button.on("click", () => {
|
||||||
|
this.go_to_page(this.total_pages);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$page_number.on("keyup", (e) => {
|
||||||
|
e.currentTarget.style.width = (e.currentTarget.value.length + 1) * 8 + "px";
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$page_number.on("keydown", (e) => {
|
||||||
|
e = e ? e : window.event;
|
||||||
|
var charCode = e.which ? e.which : e.keyCode;
|
||||||
|
let arrow = { up: 38, down: 40 };
|
||||||
|
|
||||||
|
switch (charCode) {
|
||||||
|
case arrow.up:
|
||||||
|
this.inc_dec_number(true);
|
||||||
|
break;
|
||||||
|
case arrow.down:
|
||||||
|
this.inc_dec_number(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// only allow numbers from 0-9 and up, down, left, right arrow keys
|
||||||
|
if (
|
||||||
|
charCode > 31 &&
|
||||||
|
(charCode < 48 || charCode > 57) &&
|
||||||
|
![37, 38, 39, 40].includes(charCode)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$page_number.on("focusout", (e) => {
|
||||||
|
if (this.page_index == e.currentTarget.value) return;
|
||||||
|
this.page_index = e.currentTarget.value;
|
||||||
|
|
||||||
|
if (this.page_index < 1) {
|
||||||
|
this.page_index = 1;
|
||||||
|
} else if (this.page_index > this.total_pages) {
|
||||||
|
this.page_index = this.total_pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.go_to_page();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
inc_dec_number(increment) {
|
||||||
|
let new_value = parseInt(this.$page_number.val());
|
||||||
|
increment ? new_value++ : new_value--;
|
||||||
|
|
||||||
|
if (new_value < 1 || new_value > this.total_pages) return;
|
||||||
|
|
||||||
|
this.$page_number.val(new_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
update_page_numbers() {
|
||||||
|
let total_pages = Math.ceil(this.grid.data.length / this.page_length);
|
||||||
|
if (this.total_pages !== total_pages) {
|
||||||
|
this.total_pages = total_pages;
|
||||||
|
this.render_pagination();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
check_page_number() {
|
||||||
|
if (this.page_index > this.total_pages && this.page_index > 1) {
|
||||||
|
this.go_to_page(this.page_index - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get_pagination_html() {
|
||||||
|
let page_text_html = `<div class="page-text">
|
||||||
|
<input class="current-page-number page-number" type="text" value="${__(this.page_index)}"/>
|
||||||
|
<span>${__("of")}</span>
|
||||||
|
<span class="total-page-number page-number"> ${__(this.total_pages)} </span>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
return $(`<button class="btn btn-secondary btn-xs first-page"">
|
||||||
|
<span>${__("First")}</span>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-secondary btn-xs prev-page">${frappe.utils.icon("left", "xs")}</button>
|
||||||
|
${page_text_html}
|
||||||
|
<button class="btn btn-secondary btn-xs next-page">${frappe.utils.icon("right", "xs")}</button>
|
||||||
|
<button class="btn btn-secondary btn-xs last-page">
|
||||||
|
<span>${__("Last")}</span>
|
||||||
|
</button>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
render_next_page() {
|
||||||
|
if (this.page_index * this.page_length < this.grid.data.length) {
|
||||||
|
this.page_index++;
|
||||||
|
this.go_to_page();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render_prev_page() {
|
||||||
|
if (this.page_index > 1) {
|
||||||
|
this.page_index--;
|
||||||
|
this.go_to_page();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go_to_page(index, from_refresh) {
|
||||||
|
if (!index) {
|
||||||
|
index = this.page_index;
|
||||||
|
} else {
|
||||||
|
this.page_index = index;
|
||||||
|
}
|
||||||
|
let $rows = $(this.grid.parent).find(".rows").empty();
|
||||||
|
this.grid.render_result_rows($rows, true);
|
||||||
|
if (this.$page_number) {
|
||||||
|
this.$page_number.val(index);
|
||||||
|
this.$page_number.css("width", (index.toString().length + 1) * 8 + "px");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.update_page_numbers();
|
||||||
|
if (!from_refresh) {
|
||||||
|
this.grid.scroll_to_top();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go_to_last_page_to_add_row() {
|
||||||
|
let total_pages = this.total_pages;
|
||||||
|
let page_length = this.page_length;
|
||||||
|
if (this.grid.data.length == page_length * total_pages) {
|
||||||
|
this.go_to_page(total_pages + 1);
|
||||||
|
frappe.utils.scroll_to(this.wrapper);
|
||||||
|
} else if (this.page_index == this.total_pages) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
this.go_to_page(total_pages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get_result_length() {
|
||||||
|
return this.grid.data.length < this.page_index * this.page_length
|
||||||
|
? this.grid.data.length
|
||||||
|
: this.page_index * this.page_length;
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,148 @@
|
||||||
|
export default class GridRowForm {
|
||||||
|
constructor(opts) {
|
||||||
|
$.extend(this, opts);
|
||||||
|
this.wrapper = $('<div class="form-in-grid"></div>').appendTo(this.row.wrapper);
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
var me = this;
|
||||||
|
this.make_form();
|
||||||
|
this.form_area.empty();
|
||||||
|
frappe.utils.scroll_to(0, false, 0, this.wrapper.find(".grid-form-body"));
|
||||||
|
|
||||||
|
this.layout = new frappe.ui.form.Layout({
|
||||||
|
fields: this.row.docfields,
|
||||||
|
body: this.form_area,
|
||||||
|
no_submit_on_enter: true,
|
||||||
|
frm: this.row.frm,
|
||||||
|
grid: this.row.grid,
|
||||||
|
grid_row: this.row,
|
||||||
|
grid_row_form: this,
|
||||||
|
});
|
||||||
|
this.layout.make();
|
||||||
|
|
||||||
|
this.fields = this.layout.fields;
|
||||||
|
this.fields_dict = this.layout.fields_dict;
|
||||||
|
|
||||||
|
this.layout.refresh(this.row.doc);
|
||||||
|
|
||||||
|
// copy get_query to fields
|
||||||
|
for (var fieldname in this.row.grid.fieldinfo || {}) {
|
||||||
|
var fi = this.row.grid.fieldinfo[fieldname];
|
||||||
|
$.extend(me.fields_dict[fieldname], fi);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.toggle_add_delete_button_display(this.wrapper);
|
||||||
|
|
||||||
|
this.row.grid.open_grid_row = this;
|
||||||
|
|
||||||
|
this.set_focus();
|
||||||
|
}
|
||||||
|
make_form() {
|
||||||
|
if (!this.form_area) {
|
||||||
|
let template = `<div class="grid-form-heading">
|
||||||
|
<div class="toolbar grid-header-toolbar">
|
||||||
|
<span class="panel-title">
|
||||||
|
${__("Editing Row")} #<span class="grid-form-row-index"></span></span>
|
||||||
|
<span class="row-actions">
|
||||||
|
<button class="btn btn-secondary btn-sm pull-right grid-collapse-row">
|
||||||
|
${frappe.utils.icon("down")}
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-secondary btn-sm pull-right grid-move-row hidden-xs">
|
||||||
|
${__("Move")}</button>
|
||||||
|
<button class="btn btn-secondary btn-sm pull-right grid-duplicate-row hidden-xs">
|
||||||
|
${frappe.utils.icon("duplicate")}
|
||||||
|
${__("Duplicate")}
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-secondary btn-sm pull-right grid-insert-row hidden-xs">
|
||||||
|
${__("Insert Above")}</button>
|
||||||
|
<button class="btn btn-secondary btn-sm pull-right grid-insert-row-below hidden-xs">
|
||||||
|
${__("Insert Below")}</button>
|
||||||
|
<button class="btn btn-danger btn-sm pull-right grid-delete-row">
|
||||||
|
${frappe.utils.icon("delete")}
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid-form-body">
|
||||||
|
<div class="form-area"></div>
|
||||||
|
<div class="grid-footer-toolbar hidden-xs flex justify-between">
|
||||||
|
<div class="grid-shortcuts">
|
||||||
|
<span> ${frappe.utils.icon("keyboard", "md")} </span>
|
||||||
|
<span class="text-medium"> ${__("Shortcuts")}: </span>
|
||||||
|
<kbd>${__("Ctrl + Up")}</kbd> . <kbd>${__("Ctrl + Down")}</kbd> . <kbd>${__("ESC")}</kbd>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-secondary btn-sm pull-right grid-append-row">
|
||||||
|
${__("Insert Below")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
$(template).appendTo(this.wrapper);
|
||||||
|
this.form_area = this.wrapper.find(".form-area");
|
||||||
|
this.row.set_row_index();
|
||||||
|
this.set_form_events();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set_form_events() {
|
||||||
|
var me = this;
|
||||||
|
this.wrapper.find(".grid-delete-row").on("click", function () {
|
||||||
|
me.row.remove();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
this.wrapper.find(".grid-insert-row").on("click", function () {
|
||||||
|
me.row.insert(true);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
this.wrapper.find(".grid-insert-row-below").on("click", function () {
|
||||||
|
me.row.insert(true, true);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
this.wrapper.find(".grid-duplicate-row").on("click", function () {
|
||||||
|
me.row.insert(true, true, true);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
this.wrapper.find(".grid-move-row").on("click", function () {
|
||||||
|
me.row.move();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
this.wrapper.find(".grid-append-row").on("click", function () {
|
||||||
|
me.row.toggle_view(false);
|
||||||
|
me.row.grid.add_new_row(me.row.doc.idx + 1, null, true);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
this.wrapper.find(".grid-form-heading, .grid-footer-toolbar").on("click", function () {
|
||||||
|
me.row.toggle_view();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
toggle_add_delete_button_display($parent) {
|
||||||
|
$parent.find(".row-actions, .grid-append-row").toggle(this.row.grid.is_editable());
|
||||||
|
}
|
||||||
|
refresh_field(fieldname) {
|
||||||
|
const field = this.fields_dict[fieldname];
|
||||||
|
if (!field) return;
|
||||||
|
|
||||||
|
field.docname = this.row.doc.name;
|
||||||
|
field.refresh();
|
||||||
|
this.layout && this.layout.refresh_dependency();
|
||||||
|
}
|
||||||
|
set_focus() {
|
||||||
|
// wait for animation and then focus on the first row
|
||||||
|
var me = this;
|
||||||
|
setTimeout(function () {
|
||||||
|
if ((me.row.frm && me.row.frm.doc.docstatus === 0) || !me.row.frm) {
|
||||||
|
var first = me.form_area.find("input:first");
|
||||||
|
if (
|
||||||
|
first.length &&
|
||||||
|
!["Date", "Datetime", "Time"].includes(first.attr("data-fieldtype"))
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
first.get(0).focus();
|
||||||
|
} catch (e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
.form-grid-container.enhanced-grid-container {
|
||||||
|
// overflow-y: auto;
|
||||||
|
border: 1px solid var(--table-border-color);
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
|
overflow-x: clip !important;
|
||||||
|
|
||||||
|
.form-grid {
|
||||||
|
border: none;
|
||||||
|
border-radius: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-grid.relative-important {
|
||||||
|
position: relative !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-heading-row {
|
||||||
|
height: auto;
|
||||||
|
|
||||||
|
.grid-row .static-area.ellipsis {
|
||||||
|
text-overflow: unset;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-heading-row .grid-static-col,
|
||||||
|
.grid-heading-row .row-check,
|
||||||
|
.grid-heading-row .row-index {
|
||||||
|
height: auto !important;
|
||||||
|
background-color: var(--subtle-fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-row {
|
||||||
|
.data-row.row {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
// display: grid;
|
||||||
|
// grid-auto-flow: column;
|
||||||
|
|
||||||
|
.col {
|
||||||
|
max-width: 100px !important;
|
||||||
|
min-width: 100px !important;
|
||||||
|
border-bottom: 1px solid var(--table-border-color);
|
||||||
|
padding-bottom: 1px;
|
||||||
|
|
||||||
|
.field-area {
|
||||||
|
border-bottom: 1px solid var(--table-border-color);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.col.custom-1 {
|
||||||
|
min-width: 150px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col.custom-2 {
|
||||||
|
min-width: 200px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col.custom-3 {
|
||||||
|
min-width: 250px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col.custom-4 {
|
||||||
|
min-width: 300px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col.custom-5 {
|
||||||
|
min-width: 350px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col.custom-6 {
|
||||||
|
min-width: 400px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col.custom-7 {
|
||||||
|
min-width: 450px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col.custom-8 {
|
||||||
|
min-width: 500px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col.custom-9 {
|
||||||
|
min-width: 550px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col.custom-10 {
|
||||||
|
min-width: 600px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.row-check,
|
||||||
|
.row-index {
|
||||||
|
max-width: 50px !important;
|
||||||
|
min-width: 50px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.slidecontainer {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enhanced-slider {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
width: 100%;
|
||||||
|
height: 8px;
|
||||||
|
background: #d3d3d3;
|
||||||
|
outline: none;
|
||||||
|
opacity: 0.7;
|
||||||
|
-webkit-transition: .2s;
|
||||||
|
transition: opacity .2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enhanced-slider:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enhanced-slider::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
width: 30%;
|
||||||
|
height: 5px;
|
||||||
|
background-color: grey;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enhanced-slider::-moz-range-thumb {
|
||||||
|
width: 30%;
|
||||||
|
height: 5px;
|
||||||
|
background-color: grey;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue