687 lines
17 KiB
JavaScript
687 lines
17 KiB
JavaScript
import GridRowForm from './grid_row_form';
|
|
|
|
export default class GridRow {
|
|
constructor(opts) {
|
|
this.on_grid_fields_dict = {};
|
|
this.on_grid_fields = [];
|
|
$.extend(this, opts);
|
|
if (this.doc && this.parent_df.options) {
|
|
frappe.meta.make_docfield_copy_for(this.parent_df.options, this.doc.name, this.docfields);
|
|
this.docfields = frappe.meta.get_docfields(this.parent_df.options, this.doc.name);
|
|
}
|
|
this.columns = {};
|
|
this.columns_list = [];
|
|
this.row_check_html = '<input type="checkbox" class="grid-row-check pull-left">';
|
|
this.make();
|
|
}
|
|
make() {
|
|
var me = this;
|
|
|
|
this.wrapper = $('<div class="grid-row"></div>').appendTo(this.parent).data("grid_row", this);
|
|
this.row = $('<div class="data-row row"></div>').appendTo(this.wrapper)
|
|
.on("click", function(e) {
|
|
if($(e.target).hasClass('grid-row-check') || $(e.target).hasClass('row-index') || $(e.target).parent().hasClass('row-index')) {
|
|
return;
|
|
}
|
|
if(me.grid.allow_on_grid_editing() && me.grid.is_editable()) {
|
|
// pass
|
|
} else {
|
|
me.toggle_view();
|
|
return false;
|
|
}
|
|
});
|
|
|
|
if(this.grid.template && !this.grid.meta.editable_grid) {
|
|
this.render_template();
|
|
} else {
|
|
this.render_row();
|
|
}
|
|
if(this.doc) {
|
|
this.set_data();
|
|
}
|
|
}
|
|
set_data() {
|
|
this.wrapper.data({
|
|
"doc": this.doc
|
|
})
|
|
}
|
|
set_row_index() {
|
|
if(this.doc) {
|
|
this.wrapper
|
|
.attr('data-name', this.doc.name)
|
|
.attr("data-idx", this.doc.idx)
|
|
.find(".row-index span, .grid-form-row-index").html(this.doc.idx)
|
|
|
|
}
|
|
}
|
|
select(checked) {
|
|
this.doc.__checked = checked ? 1 : 0;
|
|
}
|
|
refresh_check() {
|
|
this.wrapper.find('.grid-row-check').prop('checked', this.doc ? !!this.doc.__checked : false);
|
|
this.grid.refresh_remove_rows_button();
|
|
}
|
|
remove() {
|
|
var me = this;
|
|
if(this.grid.is_editable()) {
|
|
if(this.frm) {
|
|
if(this.get_open_form()) {
|
|
this.hide_form();
|
|
}
|
|
|
|
frappe.run_serially([
|
|
() => {
|
|
return this.frm.script_manager.trigger("before_" + this.grid.df.fieldname + "_remove",
|
|
this.doc.doctype, this.doc.name);
|
|
},
|
|
() => {
|
|
frappe.model.clear_doc(this.doc.doctype, this.doc.name);
|
|
|
|
this.frm.script_manager.trigger(this.grid.df.fieldname + "_remove",
|
|
this.doc.doctype, this.doc.name);
|
|
this.frm.dirty();
|
|
this.grid.refresh();
|
|
},
|
|
]).catch((e) => {
|
|
// aborted
|
|
console.trace(e); // eslint-disable-line
|
|
});
|
|
} else {
|
|
let data = null;
|
|
if (this.grid.df.get_data) {
|
|
data = this.grid.df.get_data();
|
|
} else {
|
|
data = this.grid.df.data;
|
|
}
|
|
|
|
const index = data.findIndex(d => d.name === me.doc.name);
|
|
|
|
if (index > -1) {
|
|
// mutate array directly,
|
|
// else the object reference will be lost
|
|
data.splice(index, 1);
|
|
}
|
|
// remap idxs
|
|
data.forEach(function(d, i) {
|
|
d.idx = i+1;
|
|
});
|
|
|
|
this.grid.refresh();
|
|
}
|
|
}
|
|
}
|
|
insert(show, below, duplicate) {
|
|
var idx = this.doc.idx;
|
|
var copy_doc = duplicate ? this.doc : null;
|
|
if(below) idx ++;
|
|
this.toggle_view(false);
|
|
this.grid.add_new_row(idx, null, show, copy_doc);
|
|
}
|
|
move() {
|
|
// promopt the user where they want to move this row
|
|
var me = this;
|
|
frappe.prompt({
|
|
fieldname: 'move_to',
|
|
label: __('Move to Row Number'),
|
|
fieldtype: 'Int',
|
|
reqd: 1,
|
|
default: this.doc.idx,
|
|
}, function(values) {
|
|
if (me.doc._sortable === false) {
|
|
frappe.msgprint(__('Cannot move row'));
|
|
return;
|
|
}
|
|
|
|
// renumber and refresh
|
|
let data = me.grid.get_data();
|
|
data.move(me.doc.idx - 1, values.move_to - 1);
|
|
|
|
// renum idx
|
|
for(let i=0; i<data.length;i++) {
|
|
data[i].idx = i+1;
|
|
}
|
|
|
|
me.toggle_view(false);
|
|
me.grid.refresh();
|
|
$(me.frm.wrapper).trigger("grid-move-row", [me.frm, me]);
|
|
}, __('Move To'), 'Update');
|
|
}
|
|
refresh() {
|
|
if(this.frm && this.doc) {
|
|
this.doc = locals[this.doc.doctype][this.doc.name];
|
|
}
|
|
|
|
if(this.grid.template && !this.grid.meta.editable_grid) {
|
|
this.render_template();
|
|
} else {
|
|
this.render_row(true);
|
|
}
|
|
|
|
// refresh form fields
|
|
if(this.grid_form) {
|
|
this.grid_form.layout && this.grid_form.layout.refresh(this.doc);
|
|
}
|
|
}
|
|
render_template() {
|
|
this.set_row_index();
|
|
|
|
if(this.row_display) {
|
|
this.row_display.remove();
|
|
}
|
|
|
|
// row index
|
|
if(this.doc) {
|
|
if(!this.row_index) {
|
|
this.row_index = $('<div style="float: left; margin-left: 15px; margin-top: 8px; \
|
|
margin-right: -20px;">'+this.row_check_html+' <span></span></div>').appendTo(this.row);
|
|
}
|
|
this.row_index.find('span').html(this.doc.idx);
|
|
}
|
|
|
|
this.row_display = $('<div class="row-data sortable-handle template-row">'+
|
|
+'</div>').appendTo(this.row)
|
|
.html(frappe.render(this.grid.template, {
|
|
doc: this.doc ? frappe.get_format_helper(this.doc) : null,
|
|
frm: this.frm,
|
|
row: this
|
|
}));
|
|
}
|
|
render_row(refresh) {
|
|
var me = this;
|
|
this.set_row_index();
|
|
|
|
// index (1, 2, 3 etc)
|
|
if(!this.row_index) {
|
|
// REDESIGN-TODO: Make translation contextual, this No is Number
|
|
var txt = (this.doc ? this.doc.idx : __("No."));
|
|
this.row_index = $(
|
|
`<div class="row-index sortable-handle col col-xs-1">
|
|
${this.row_check_html}
|
|
<span class="hidden-xs">${txt}</span></div>`)
|
|
.appendTo(this.row)
|
|
.on('click', function(e) {
|
|
if(!$(e.target).hasClass('grid-row-check')) {
|
|
me.toggle_view();
|
|
}
|
|
});
|
|
} else {
|
|
this.row_index.find('span').html(txt);
|
|
}
|
|
|
|
this.setup_columns();
|
|
this.add_open_form_button();
|
|
this.refresh_check();
|
|
|
|
if(this.frm && this.doc) {
|
|
$(this.frm.wrapper).trigger("grid-row-render", [this]);
|
|
}
|
|
}
|
|
|
|
make_editable() {
|
|
this.row.toggleClass('editable-row', this.grid.is_editable());
|
|
}
|
|
|
|
is_too_small() {
|
|
return this.row.width() ? this.row.width() < 300 : false;
|
|
}
|
|
|
|
add_open_form_button() {
|
|
var me = this;
|
|
if(this.doc && !this.grid.df.in_place_edit) {
|
|
// remove row
|
|
if(!this.open_form_button) {
|
|
this.open_form_button = $(`
|
|
<div class="btn-open-row">
|
|
<a>${frappe.utils.icon('edit', 'xs')}</a>
|
|
<div class="hidden-xs edit-grid-row">${ __("Edit") }</div>
|
|
</div>
|
|
`)
|
|
.appendTo($('<div class="col col-xs-1"></div>').appendTo(this.row))
|
|
.on('click', function() {
|
|
me.toggle_view(); return false;
|
|
});
|
|
|
|
if(this.is_too_small()) {
|
|
// narrow
|
|
this.open_form_button.css({'margin-right': '-2px'});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
setup_columns() {
|
|
this.focus_set = false;
|
|
this.grid.setup_visible_columns();
|
|
|
|
this.grid.visible_columns.forEach((col, ci) => {
|
|
// to get update df for the row
|
|
let df = this.docfields.find(field => field.fieldname === col[0].fieldname);
|
|
|
|
let colsize = col[1];
|
|
let txt = this.doc ?
|
|
frappe.format(this.doc[df.fieldname], df, null, this.doc) :
|
|
__(df.label);
|
|
|
|
if (this.doc && df.fieldtype === "Select") {
|
|
txt = __(txt);
|
|
}
|
|
let column;
|
|
if (!this.columns[df.fieldname]) {
|
|
column = this.make_column(df, colsize, txt, ci);
|
|
} else {
|
|
column = this.columns[df.fieldname];
|
|
this.refresh_field(df.fieldname, txt);
|
|
}
|
|
|
|
// background color for cell
|
|
if (this.doc) {
|
|
if (df.reqd && !txt) {
|
|
column.addClass('error');
|
|
}
|
|
if (column.is_invalid) {
|
|
column.addClass('invalid');
|
|
} else if (df.reqd || df.bold) {
|
|
column.addClass('bold');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
make_column(df, colsize, txt, ci) {
|
|
let me = this;
|
|
var add_class = ((["Text", "Small Text"].indexOf(df.fieldtype)!==-1) ?
|
|
" grid-overflow-no-ellipsis" : "");
|
|
add_class += (["Int", "Currency", "Float", "Percent"].indexOf(df.fieldtype)!==-1) ?
|
|
" text-right": "";
|
|
add_class += (["Check"].indexOf(df.fieldtype)!==-1) ?
|
|
" text-center": "";
|
|
|
|
var $col = $('<div class="col grid-static-col col-xs-'+colsize+' '+add_class+'"></div>')
|
|
.attr("data-fieldname", df.fieldname)
|
|
.attr("data-fieldtype", df.fieldtype)
|
|
.data("df", df)
|
|
.appendTo(this.row)
|
|
.on('click', function() {
|
|
if(frappe.ui.form.editable_row===me) {
|
|
return;
|
|
}
|
|
var out = me.toggle_editable_row();
|
|
var col = this;
|
|
setTimeout(function() {
|
|
$(col).find('input[type="Text"]:first').focus();
|
|
}, 500);
|
|
return out;
|
|
});
|
|
|
|
$col.field_area = $('<div class="field-area"></div>').appendTo($col).toggle(false);
|
|
$col.static_area = $('<div class="static-area ellipsis"></div>').appendTo($col).html(txt);
|
|
$col.df = df;
|
|
$col.column_index = ci;
|
|
|
|
this.columns[df.fieldname] = $col;
|
|
this.columns_list.push($col);
|
|
|
|
return $col;
|
|
}
|
|
|
|
activate() {
|
|
this.toggle_editable_row(true);
|
|
return this;
|
|
}
|
|
|
|
toggle_editable_row(show) {
|
|
var me = this;
|
|
// show static for field based on
|
|
// whether grid is editable
|
|
if(this.grid.allow_on_grid_editing() && this.grid.is_editable() && this.doc && show !== false) {
|
|
|
|
// disable other editable row
|
|
if(frappe.ui.form.editable_row
|
|
&& frappe.ui.form.editable_row !== this) {
|
|
frappe.ui.form.editable_row.toggle_editable_row(false);
|
|
}
|
|
|
|
this.row.toggleClass('editable-row', true);
|
|
|
|
// setup controls
|
|
this.columns_list.forEach(function(column) {
|
|
me.make_control(column);
|
|
column.static_area.toggle(false);
|
|
column.field_area.toggle(true);
|
|
});
|
|
|
|
frappe.ui.form.editable_row = this;
|
|
return false;
|
|
} else {
|
|
this.row.toggleClass('editable-row', false);
|
|
|
|
this.columns_list.forEach((column, index) => {
|
|
|
|
if(!this.frm) {
|
|
let df = this.grid.visible_columns[index][0]
|
|
|
|
let txt = this.doc ?
|
|
frappe.format(this.doc[df.fieldname], df, null, this.doc) :
|
|
__(df.label);
|
|
|
|
this.refresh_field(df.fieldname, txt)
|
|
}
|
|
|
|
if (!column.df.hidden) {
|
|
column.static_area.toggle(true);
|
|
}
|
|
|
|
column.field_area && column.field_area.toggle(false);
|
|
});
|
|
frappe.ui.form.editable_row = null;
|
|
}
|
|
}
|
|
|
|
make_control(column) {
|
|
if(column.field) return;
|
|
|
|
var me = this,
|
|
parent = column.field_area,
|
|
df = column.df;
|
|
|
|
// no text editor in grid
|
|
if (df.fieldtype=='Text Editor') {
|
|
df = Object.assign({}, df);
|
|
df.fieldtype = 'Text';
|
|
}
|
|
|
|
var field = frappe.ui.form.make_control({
|
|
df: df,
|
|
parent: parent,
|
|
only_input: true,
|
|
with_link_btn: true,
|
|
doc: this.doc,
|
|
doctype: this.doc.doctype,
|
|
docname: this.doc.name,
|
|
frm: this.grid.frm,
|
|
grid: this.grid,
|
|
grid_row: this,
|
|
value: this.doc[df.fieldname]
|
|
});
|
|
|
|
// sync get_query
|
|
field.get_query = this.grid.get_field(df.fieldname).get_query;
|
|
|
|
if (!field.df.onchange_modified) {
|
|
var field_on_change_function = field.df.onchange;
|
|
field.df.onchange = (e) => {
|
|
field_on_change_function && field_on_change_function(e);
|
|
this.refresh_field(field.df.fieldname);
|
|
};
|
|
|
|
field.df.onchange_modified = true;
|
|
}
|
|
|
|
field.refresh();
|
|
if(field.$input) {
|
|
field.$input
|
|
.addClass('input-sm')
|
|
.attr('data-col-idx', column.column_index)
|
|
.attr('placeholder', __(df.label));
|
|
// flag list input
|
|
if (this.columns_list && this.columns_list.slice(-1)[0]===column) {
|
|
field.$input.attr('data-last-input', 1);
|
|
}
|
|
}
|
|
|
|
this.set_arrow_keys(field);
|
|
column.field = field;
|
|
this.on_grid_fields_dict[df.fieldname] = field;
|
|
this.on_grid_fields.push(field);
|
|
|
|
}
|
|
|
|
set_arrow_keys(field) {
|
|
var me = this;
|
|
if (field.$input) {
|
|
field.$input.on('keydown', function(e) {
|
|
var { TAB, UP: UP_ARROW, DOWN: DOWN_ARROW } = frappe.ui.keyCode;
|
|
if (!in_list([TAB, UP_ARROW, DOWN_ARROW], e.which)) {
|
|
return;
|
|
}
|
|
|
|
var values = me.grid.get_data();
|
|
var fieldname = $(this).attr('data-fieldname');
|
|
var fieldtype = $(this).attr('data-fieldtype');
|
|
|
|
var move_up_down = function(base) {
|
|
if (in_list(['Text', 'Small Text', 'Code', 'Text Editor', 'HTML Editor'], fieldtype) && !e.altKey) {
|
|
return false;
|
|
}
|
|
if (field.autocomplete_open) {
|
|
return false;
|
|
}
|
|
|
|
base.toggle_editable_row();
|
|
var input = base.columns[fieldname].field.$input;
|
|
if (input) {
|
|
input.focus();
|
|
}
|
|
return true;
|
|
};
|
|
|
|
// TAB
|
|
if (e.which === TAB && !e.shiftKey) {
|
|
var last_column = me.wrapper.find(':input:enabled:last').get(0);
|
|
var is_last_column = $(this).attr('data-last-input') || last_column === this;
|
|
|
|
if (is_last_column) {
|
|
// last row
|
|
if (me.doc.idx === values.length) {
|
|
setTimeout(function () {
|
|
me.grid.add_new_row(null, null, true);
|
|
me.grid.grid_rows[me.grid.grid_rows.length - 1].toggle_editable_row();
|
|
me.grid.set_focus_on_row();
|
|
}, 100);
|
|
} else {
|
|
// last column before last row
|
|
me.grid.grid_rows[me.doc.idx].toggle_editable_row();
|
|
me.grid.set_focus_on_row(me.doc.idx);
|
|
return false;
|
|
}
|
|
}
|
|
} else if (e.which === UP_ARROW) {
|
|
if (me.doc.idx > 1) {
|
|
var prev = me.grid.grid_rows[me.doc.idx-2];
|
|
if (move_up_down(prev)) {
|
|
return false;
|
|
}
|
|
}
|
|
} else if (e.which === DOWN_ARROW) {
|
|
if (me.doc.idx < values.length) {
|
|
var next = me.grid.grid_rows[me.doc.idx];
|
|
if (move_up_down(next)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
});
|
|
}
|
|
}
|
|
|
|
get_open_form() {
|
|
return frappe.ui.form.get_open_grid_form();
|
|
}
|
|
|
|
toggle_view(show, callback) {
|
|
if(!this.doc) {
|
|
return this;
|
|
}
|
|
|
|
if(this.frm) {
|
|
// reload doc
|
|
this.doc = locals[this.doc.doctype][this.doc.name];
|
|
}
|
|
|
|
// hide other
|
|
var open_row = this.get_open_form();
|
|
|
|
if (show===undefined) show = !!!open_row;
|
|
|
|
// call blur
|
|
document.activeElement && document.activeElement.blur();
|
|
|
|
if(show && open_row) {
|
|
if(open_row==this) {
|
|
// already open, do nothing
|
|
callback && callback();
|
|
return;
|
|
} else {
|
|
// close other views
|
|
open_row.toggle_view(false);
|
|
}
|
|
}
|
|
|
|
if(show) {
|
|
this.show_form();
|
|
} else {
|
|
this.hide_form();
|
|
}
|
|
callback && callback();
|
|
|
|
return this;
|
|
}
|
|
show_form() {
|
|
if (!this.grid_form) {
|
|
this.grid_form = new GridRowForm({
|
|
row: this
|
|
});
|
|
}
|
|
this.grid_form.render();
|
|
this.row.toggle(false);
|
|
// this.form_panel.toggle(true);
|
|
|
|
let cannot_add_rows = this.grid.cannot_add_rows || (this.grid.df && this.grid.df.cannot_add_rows);
|
|
this.wrapper
|
|
.find('.grid-insert-row-below, .grid-insert-row, .grid-duplicate-row, .grid-append-row')
|
|
.toggle(!cannot_add_rows);
|
|
|
|
frappe.dom.freeze("", "dark");
|
|
if (cur_frm) cur_frm.cur_grid = this;
|
|
this.wrapper.addClass("grid-row-open");
|
|
if (!frappe.dom.is_element_in_viewport(this.wrapper)
|
|
&& !frappe.dom.is_element_in_modal(this.wrapper)) {
|
|
// -15 offset to make form look visually centered
|
|
frappe.utils.scroll_to(this.wrapper, true, -15);
|
|
}
|
|
|
|
if (this.frm) {
|
|
this.frm.script_manager.trigger(this.doc.parentfield + "_on_form_rendered");
|
|
this.frm.script_manager.trigger("form_render", this.doc.doctype, this.doc.name);
|
|
}
|
|
}
|
|
hide_form() {
|
|
frappe.dom.unfreeze();
|
|
this.row.toggle(true);
|
|
if (!frappe.dom.is_element_in_modal(this.row)) {
|
|
frappe.utils.scroll_to(this.row, true, 15);
|
|
}
|
|
this.refresh();
|
|
if(cur_frm) cur_frm.cur_grid = null;
|
|
this.wrapper.removeClass("grid-row-open");
|
|
}
|
|
open_prev() {
|
|
const row_index = this.wrapper.index();
|
|
if (this.grid.grid_rows[row_index - 1]) {
|
|
this.grid.grid_rows[row_index - 1].toggle_view(true);
|
|
}
|
|
}
|
|
open_next() {
|
|
const row_index = this.wrapper.index();
|
|
if (this.grid.grid_rows[row_index + 1]) {
|
|
this.grid.grid_rows[row_index + 1].toggle_view(true);
|
|
} else {
|
|
this.grid.add_new_row(null, null, true);
|
|
}
|
|
}
|
|
refresh_field(fieldname, txt) {
|
|
let df = this.docfields.find(col => {
|
|
return col.fieldname === fieldname;
|
|
});
|
|
|
|
// format values if no frm
|
|
if (df && this.doc) {
|
|
txt = frappe.format(this.doc[fieldname], df, null, this.doc);
|
|
}
|
|
|
|
if (!txt && this.frm) {
|
|
txt = frappe.format(this.doc[fieldname], df, null, this.frm.doc);
|
|
}
|
|
|
|
// reset static value
|
|
let column = this.columns[fieldname];
|
|
if (column) {
|
|
column.static_area.html(txt || "");
|
|
if (df && df.reqd) {
|
|
column.toggleClass('error', !!(txt === null || txt === ''));
|
|
}
|
|
}
|
|
|
|
let field = this.on_grid_fields_dict[fieldname];
|
|
// reset field value
|
|
if (field) {
|
|
field.docname = this.doc.name;
|
|
field.refresh();
|
|
}
|
|
|
|
// in form
|
|
if (this.grid_form) {
|
|
this.grid_form.refresh_field(fieldname);
|
|
}
|
|
}
|
|
get_field(fieldname) {
|
|
let field = this.on_grid_fields_dict[fieldname];
|
|
if (field) {
|
|
return field;
|
|
} else if(this.grid_form) {
|
|
return this.grid_form.fields_dict[fieldname];
|
|
} else {
|
|
throw `fieldname ${fieldname} not found`;
|
|
}
|
|
}
|
|
|
|
get_visible_columns(blacklist=[]) {
|
|
var me = this;
|
|
var visible_columns = $.map(this.docfields, function(df) {
|
|
var visible = !df.hidden && df.in_list_view && me.grid.frm.get_perm(df.permlevel, "read")
|
|
&& !in_list(frappe.model.layout_fields, df.fieldtype) && !in_list(blacklist, df.fieldname);
|
|
|
|
return visible ? df : null;
|
|
});
|
|
return visible_columns;
|
|
}
|
|
set_field_property(fieldname, property, value) {
|
|
// set a field property for open form / grid form
|
|
var me = this;
|
|
|
|
var set_property = function(field) {
|
|
if(!field) return;
|
|
field.df[property] = value;
|
|
field.refresh();
|
|
}
|
|
|
|
// set property in grid form
|
|
if(this.grid_form) {
|
|
set_property(this.grid_form.fields_dict[fieldname]);
|
|
this.grid_form.layout && this.grid_form.layout.refresh_sections();
|
|
}
|
|
|
|
// set property in on grid fields
|
|
set_property(this.on_grid_fields_dict[fieldname]);
|
|
}
|
|
toggle_reqd(fieldname, reqd) {
|
|
this.set_field_property(fieldname, 'reqd', reqd ? 1 : 0);
|
|
}
|
|
toggle_display(fieldname, show) {
|
|
this.set_field_property(fieldname, 'hidden', show ? 0 : 1);
|
|
}
|
|
toggle_editable(fieldname, editable) {
|
|
this.set_field_property(fieldname, 'read_only', editable ? 0 : 1);
|
|
}
|
|
};
|