From 60d6cb1040d093f2bc6d53bc13783732177ce051 Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Sun, 16 Oct 2022 23:15:13 +0530 Subject: [PATCH 1/4] fix(UI): child table custom horizontal scroll --- frappe/public/js/frappe/form/grid_row.js | 122 +++++++++++++++++++++-- frappe/public/scss/common/grid.scss | 20 +++- 2 files changed, 132 insertions(+), 10 deletions(-) diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js index 4927f1e300..4c021a8af4 100644 --- a/frappe/public/js/frappe/form/grid_row.js +++ b/frappe/public/js/frappe/form/grid_row.js @@ -835,6 +835,18 @@ export default class GridRow { : ""; add_class += ["Check"].indexOf(df.fieldtype) !== -1 ? " text-center" : ""; + let grid; + let gridContainer; + + let initalPositionX = 0; + let startX = 0; + let startY = 0; + + let inputInFocus = false; + + let vertical = false; + let horizontal = false; + var $col = $( '
' ) @@ -842,15 +854,107 @@ export default class GridRow { .attr("data-fieldtype", df.fieldtype) .data("df", df) .appendTo(this.row) - .on("click", function () { - if (frappe.ui.form.editable_row === me) { - return; + // initialize grid for horizontal scroll on mobile devices. + .on("touchstart", function (event) { + gridContainer = $(event.currentTarget).closest(".form-grid-container")[0]; + grid = $(event.currentTarget).closest(".form-grid")[0]; + + grid.style.position != "relative" && $(grid).css("position", "relative"); + !grid.style.left && $(grid).css("left", 0); + + startX = event.touches[0].clientX; + startY = event.touches[0].clientY; + + initalPositionX = -parseFloat(grid.style.left || 0) + startX; + }) + // calculate X and Y movement based on touch events. + .on("touchmove", function (event) { + if (inputInFocus) return; + + let movedX; + let movedY; + + if (!horizontal && !vertical) { + movedX = Math.abs(startX - event.touches[0].clientX); + movedY = Math.abs(startY - event.touches[0].clientY); + } + + if (!vertical && movedX > 16) { + horizontal = true; + } else if (!horizontal && movedY > 16) { + vertical = true; + } + if (horizontal) { + event.preventDefault(); + + let gridStart = initalPositionX - event.touches[0].clientX; + let gridEnd = grid.clientWidth - gridContainer.clientWidth; + + if (gridStart < 0) { + gridStart = 0; + } else if (gridStart > gridEnd) { + gridStart = gridEnd; + } + grid.style.left = `-${gridStart}px`; + } + }) + .on("touchend", function () { + vertical = false; + horizontal = false; + }) + .on("click", function () { + if (frappe.ui.form.editable_row !== me) { + var out = me.toggle_editable_row(); } - var out = me.toggle_editable_row(); var col = this; - setTimeout(function () { - $(col).find('input[type="Text"]:first').focus(); - }, 500); + let firstInputField = $(col).find('input[type="Text"]:first'); + // prevent random layout shifts caused by widgets and on click position elements inside view (UX). + function onInputFocus(el) { + inputInFocus = true; + + let containerWidth = gridContainer.getBoundingClientRect().width; + let containerLeft = gridContainer.getBoundingClientRect().left; + let gridLeft = parseFloat(grid.style.left); + let elementLeft = el.offset().left; + let fieldType = el.data("fieldtype"); + + let offsetRight = containerWidth - (elementLeft + el.width()); + let offsetLeft = 0; + let elementScreenX = elementLeft - containerLeft; + let elementPositionX = containerWidth - (elementLeft - containerLeft); + + if (["Date", "Time", "Datetime"].includes(fieldType)) { + offsetLeft = elementPositionX - 220; + } + if (["Link", "Dynamic Link"].includes(fieldType)) { + offsetLeft = elementPositionX - 250; + } + if (elementScreenX < 0) { + grid.style.left = `${gridLeft - elementScreenX}px`; + } else if (offsetLeft < 0) { + grid.style.left = `${gridLeft + offsetLeft}px`; + } else if (offsetRight < 0) { + grid.style.left = `${gridLeft + offsetRight}px`; + } + } + + firstInputField.length && onInputFocus(firstInputField); + + firstInputField.focus(); + firstInputField.one("blur", () => (inputInFocus = false)); + + // Delay datePicker widget to prevent temparary layout shift (UX). + if (firstInputField.data("fieldtype") == "Date") { + let dateTimePicker = document.querySelectorAll(".datepicker.active")[0]; + + dateTimePicker.classList.remove("active"); + + dateTimePicker.style.width = "220px"; + + setTimeout(() => { + dateTimePicker.classList.add("active"); + }, 600); + } return out; }); @@ -1151,6 +1255,7 @@ export default class GridRow { show_form() { if (frappe.utils.is_xs()) { $(this.grid.form_grid).css("min-width", "0"); + $(this.grid.form_grid).css("position", "unset"); } if (!this.grid_form) { this.grid_form = new GridRowForm({ @@ -1191,7 +1296,8 @@ export default class GridRow { } hide_form() { if (frappe.utils.is_xs()) { - $(this.grid.form_grid).css("min-width", "1000px"); + $(this.grid.form_grid).css("min-width", "738px"); + $(this.grid.form_grid).css("position", "relative"); } frappe.dom.unfreeze(); this.row.toggle(true); diff --git a/frappe/public/scss/common/grid.scss b/frappe/public/scss/common/grid.scss index 3bee40d2f2..f0cd91532e 100644 --- a/frappe/public/scss/common/grid.scss +++ b/frappe/public/scss/common/grid.scss @@ -486,10 +486,26 @@ @media (max-width: map-get($grid-breakpoints, "md")) { .form-grid-container { - overflow-x: scroll; + overflow-x: clip; .form-grid { - min-width: 1000px; + min-width: 738px; + } + } + + .form-column.col-sm-6 .form-grid { + .row-index { + display: block; + } + } +} + +@media (min-width: map-get($grid-breakpoints, "md")) { + .form-grid-container { + overflow-x: unset!important; + + .form-grid { + position: unset!important; } } From 57aad8716697589e04a19a627c33a6ccbb267196 Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Mon, 17 Oct 2022 21:56:55 +0530 Subject: [PATCH 2/4] chore(DX): camelCase to snake_case & separate functions. --- frappe/public/js/frappe/form/grid_row.js | 137 ++++++++++++----------- frappe/public/scss/common/grid.scss | 6 - 2 files changed, 70 insertions(+), 73 deletions(-) diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js index 4c021a8af4..0967ee7317 100644 --- a/frappe/public/js/frappe/form/grid_row.js +++ b/frappe/public/js/frappe/form/grid_row.js @@ -836,17 +836,59 @@ export default class GridRow { add_class += ["Check"].indexOf(df.fieldtype) !== -1 ? " text-center" : ""; let grid; - let gridContainer; + let grid_container; - let initalPositionX = 0; - let startX = 0; - let startY = 0; + let inital_position_x = 0; + let start_x = 0; + let start_y = 0; - let inputInFocus = false; + let input_in_focus = false; let vertical = false; let horizontal = false; + // prevent random layout shifts caused by widgets and on click position elements inside view (UX). + function on_input_focus(el) { + input_in_focus = true; + + let container_width = grid_container.getBoundingClientRect().width; + let container_left = grid_container.getBoundingClientRect().left; + let grid_left = parseFloat(grid.style.left); + let element_left = el.offset().left; + let fieldtype = el.data("fieldtype"); + + let offset_right = container_width - (element_left + el.width()); + let offset_left = 0; + let element_screen_x = element_left - container_left; + let element_position_x = container_width - (element_left - container_left); + + if (["Date", "Time", "Datetime"].includes(fieldtype)) { + offset_left = element_position_x - 220; + } + if (["Link", "Dynamic Link"].includes(fieldtype)) { + offset_left = element_position_x - 250; + } + if (element_screen_x < 0) { + grid.style.left = `${grid_left - element_screen_x}px`; + } else if (offset_left < 0) { + grid.style.left = `${grid_left + offset_left}px`; + } else if (offset_right < 0) { + grid.style.left = `${grid_left + offset_right}px`; + } + } + + // Delay date_picker widget to prevent temparary layout shift (UX). + function handle_date_picker() { + let date_time_picker = document.querySelectorAll(".datepicker.active")[0]; + + date_time_picker.classList.remove("active"); + date_time_picker.style.width = "220px"; + + setTimeout(() => { + date_time_picker.classList.add("active"); + }, 600); + } + var $col = $( '
' ) @@ -856,46 +898,46 @@ export default class GridRow { .appendTo(this.row) // initialize grid for horizontal scroll on mobile devices. .on("touchstart", function (event) { - gridContainer = $(event.currentTarget).closest(".form-grid-container")[0]; + grid_container = $(event.currentTarget).closest(".form-grid-container")[0]; grid = $(event.currentTarget).closest(".form-grid")[0]; grid.style.position != "relative" && $(grid).css("position", "relative"); !grid.style.left && $(grid).css("left", 0); - startX = event.touches[0].clientX; - startY = event.touches[0].clientY; + start_x = event.touches[0].clientX; + start_y = event.touches[0].clientY; - initalPositionX = -parseFloat(grid.style.left || 0) + startX; + inital_position_x = -parseFloat(grid.style.left || 0) + start_x; }) // calculate X and Y movement based on touch events. .on("touchmove", function (event) { - if (inputInFocus) return; + if (input_in_focus) return; - let movedX; - let movedY; + let moved_x; + let moved_y; if (!horizontal && !vertical) { - movedX = Math.abs(startX - event.touches[0].clientX); - movedY = Math.abs(startY - event.touches[0].clientY); + moved_x = Math.abs(start_x - event.touches[0].clientX); + moved_y = Math.abs(start_y - event.touches[0].clientY); } - if (!vertical && movedX > 16) { + if (!vertical && moved_x > 16) { horizontal = true; - } else if (!horizontal && movedY > 16) { + } else if (!horizontal && moved_y > 16) { vertical = true; } if (horizontal) { event.preventDefault(); - let gridStart = initalPositionX - event.touches[0].clientX; - let gridEnd = grid.clientWidth - gridContainer.clientWidth; + let grid_start = inital_position_x - event.touches[0].clientX; + let grid_end = grid.clientWidth - grid_container.clientWidth; - if (gridStart < 0) { - gridStart = 0; - } else if (gridStart > gridEnd) { - gridStart = gridEnd; + if (grid_start < 0) { + grid_start = 0; + } else if (grid_start > grid_end) { + grid_start = grid_end; } - grid.style.left = `-${gridStart}px`; + grid.style.left = `-${grid_start}px`; } }) .on("touchend", function () { @@ -907,54 +949,15 @@ export default class GridRow { var out = me.toggle_editable_row(); } var col = this; - let firstInputField = $(col).find('input[type="Text"]:first'); - // prevent random layout shifts caused by widgets and on click position elements inside view (UX). - function onInputFocus(el) { - inputInFocus = true; + let first_input_field = $(col).find('input[type="Text"]:first'); - let containerWidth = gridContainer.getBoundingClientRect().width; - let containerLeft = gridContainer.getBoundingClientRect().left; - let gridLeft = parseFloat(grid.style.left); - let elementLeft = el.offset().left; - let fieldType = el.data("fieldtype"); + first_input_field.length && on_input_focus(first_input_field); - let offsetRight = containerWidth - (elementLeft + el.width()); - let offsetLeft = 0; - let elementScreenX = elementLeft - containerLeft; - let elementPositionX = containerWidth - (elementLeft - containerLeft); + first_input_field.trigger("focus"); + first_input_field.one("blur", () => (input_in_focus = false)); - if (["Date", "Time", "Datetime"].includes(fieldType)) { - offsetLeft = elementPositionX - 220; - } - if (["Link", "Dynamic Link"].includes(fieldType)) { - offsetLeft = elementPositionX - 250; - } - if (elementScreenX < 0) { - grid.style.left = `${gridLeft - elementScreenX}px`; - } else if (offsetLeft < 0) { - grid.style.left = `${gridLeft + offsetLeft}px`; - } else if (offsetRight < 0) { - grid.style.left = `${gridLeft + offsetRight}px`; - } - } + first_input_field.data("fieldtype") == "Date" && handle_date_picker(); - firstInputField.length && onInputFocus(firstInputField); - - firstInputField.focus(); - firstInputField.one("blur", () => (inputInFocus = false)); - - // Delay datePicker widget to prevent temparary layout shift (UX). - if (firstInputField.data("fieldtype") == "Date") { - let dateTimePicker = document.querySelectorAll(".datepicker.active")[0]; - - dateTimePicker.classList.remove("active"); - - dateTimePicker.style.width = "220px"; - - setTimeout(() => { - dateTimePicker.classList.add("active"); - }, 600); - } return out; }); diff --git a/frappe/public/scss/common/grid.scss b/frappe/public/scss/common/grid.scss index f0cd91532e..82b1c73905 100644 --- a/frappe/public/scss/common/grid.scss +++ b/frappe/public/scss/common/grid.scss @@ -508,12 +508,6 @@ position: unset!important; } } - - .form-column.col-sm-6 .form-grid { - .row-index { - display: block; - } - } } @media (max-width: map-get($grid-breakpoints, "sm")) { From 98e1813de11bb1147f3cf99f3db33c23f37c2943 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Tue, 18 Oct 2022 11:32:57 +0530 Subject: [PATCH 3/4] fix(UI): lesser margin if cell is focused --- frappe/public/scss/common/grid.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/scss/common/grid.scss b/frappe/public/scss/common/grid.scss index 82b1c73905..ac15894670 100644 --- a/frappe/public/scss/common/grid.scss +++ b/frappe/public/scss/common/grid.scss @@ -268,8 +268,8 @@ .editable-row .frappe-control { padding-top: 0px !important; padding-bottom: 0px !important; - margin-left: -5px !important; - margin-right: -5px !important; + margin-left: -1px !important; + margin-right: -1px !important; } } From 6b1c825741ba3e078d9b8145e781510457587bd1 Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Tue, 18 Oct 2022 13:02:42 +0530 Subject: [PATCH 4/4] fix(UI): consider 2px border at grid end --- frappe/public/js/frappe/form/grid_row.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js index 0967ee7317..4a30ad68e0 100644 --- a/frappe/public/js/frappe/form/grid_row.js +++ b/frappe/public/js/frappe/form/grid_row.js @@ -930,7 +930,7 @@ export default class GridRow { event.preventDefault(); let grid_start = inital_position_x - event.touches[0].clientX; - let grid_end = grid.clientWidth - grid_container.clientWidth; + let grid_end = grid.clientWidth - grid_container.clientWidth + 2; if (grid_start < 0) { grid_start = 0;