diff --git a/cypress/integration/web_form.js b/cypress/integration/web_form.js index 9173bfaeb3..d7f210e7d2 100644 --- a/cypress/integration/web_form.js +++ b/cypress/integration/web_form.js @@ -45,7 +45,7 @@ context("Web Form", () => { cy.login(); cy.visit("/app/web-form/note"); - cy.findByRole("tab", { name: "Form Settings" }).click(); + cy.findByRole("tab", { name: "Settings" }).click(); cy.get('input[data-fieldname="login_required"]').check({ force: true }); cy.save(); @@ -65,7 +65,8 @@ context("Web Form", () => { cy.login(); cy.visit("/app/web-form/note"); - cy.findByRole("tab", { name: "List Settings" }).click(); + cy.findByRole("tab", { name: "Settings" }).click(); + cy.get(".section-head").contains("List Settings").click(); cy.get('input[data-fieldname="show_list"]').check(); cy.save(); @@ -78,7 +79,7 @@ context("Web Form", () => { it("Show Custom List Title", () => { cy.visit("/app/web-form/note"); - cy.findByRole("tab", { name: "List Settings" }).click(); + cy.findByRole("tab", { name: "Settings" }).click(); cy.fill_field("list_title", "Note List"); cy.save(); @@ -97,7 +98,7 @@ context("Web Form", () => { cy.visit("/app/web-form/note"); - cy.findByRole("tab", { name: "List Settings" }).click(); + cy.findByRole("tab", { name: "Settings" }).click(); cy.get('[data-fieldname="list_columns"] .grid-footer button') .contains("Add Row") @@ -108,19 +109,19 @@ context("Web Form", () => { cy.get("@grid-rows").find('.grid-row:first [data-fieldname="fieldname"]').click(); cy.get("@grid-rows") .find('.grid-row:first select[data-fieldname="fieldname"]') - .select("Title (Data)"); + .select("Title"); cy.get("@add-row").click(); cy.get("@grid-rows").find('.grid-row[data-idx="2"] [data-fieldname="fieldname"]').click(); cy.get("@grid-rows") .find('.grid-row[data-idx="2"] select[data-fieldname="fieldname"]') - .select("Public (Check)"); + .select("Public"); cy.get("@add-row").click(); cy.get("@grid-rows").find('.grid-row:last [data-fieldname="fieldname"]').click(); cy.get("@grid-rows") .find('.grid-row:last select[data-fieldname="fieldname"]') - .select("Content (Text Editor)"); + .select("Content"); cy.save(); @@ -171,7 +172,7 @@ context("Web Form", () => { it("Edit Mode", () => { cy.visit("/app/web-form/note"); - cy.findByRole("tab", { name: "Form Settings" }).click(); + cy.findByRole("tab", { name: "Settings" }).click(); cy.get('input[data-fieldname="allow_edit"]').check(); cy.save(); @@ -179,7 +180,7 @@ context("Web Form", () => { cy.visit("/note/Note 1"); cy.url().should("include", "/note/Note%201"); - cy.get(".web-form-actions a").contains("Edit").click(); + cy.get(".web-form-actions a").contains("Edit Response").click(); cy.url().should("include", "/note/Note%201/edit"); // Editable Field @@ -194,7 +195,7 @@ context("Web Form", () => { it("Allow Multiple Response", () => { cy.visit("/app/web-form/note"); - cy.findByRole("tab", { name: "Form Settings" }).click(); + cy.findByRole("tab", { name: "Settings" }).click(); cy.get('input[data-fieldname="allow_multiple"]').check(); cy.save(); @@ -212,7 +213,7 @@ context("Web Form", () => { it("Allow Delete", () => { cy.visit("/app/web-form/note"); - cy.findByRole("tab", { name: "Form Settings" }).click(); + cy.findByRole("tab", { name: "Settings" }).click(); cy.get('input[data-fieldname="allow_delete"]').check(); cy.save(); @@ -235,7 +236,7 @@ context("Web Form", () => { it("Navigate and Submit a WebForm", () => { cy.visit("/update-profile"); - cy.get(".web-form-actions a").contains("Edit").click(); + cy.get(".web-form-actions a").contains("Edit Response").click(); cy.fill_field("middle_name", "_Test User"); @@ -247,7 +248,7 @@ context("Web Form", () => { cy.call("frappe.tests.ui_test_helpers.update_webform_to_multistep").then(() => { cy.visit("/update-profile-duplicate"); - cy.get(".web-form-actions a").contains("Edit").click(); + cy.get(".web-form-actions a").contains("Edit Response").click(); cy.fill_field("middle_name", "_Test User"); diff --git a/frappe/public/js/bootstrap-4-web.bundle.js b/frappe/public/js/bootstrap-4-web.bundle.js index 083b7aecaa..3845a7b185 100644 --- a/frappe/public/js/bootstrap-4-web.bundle.js +++ b/frappe/public/js/bootstrap-4-web.bundle.js @@ -32,10 +32,7 @@ frappe.get_modal = function (title, content) { ${content} @@ -49,11 +46,19 @@ frappe.ui.Dialog = class Dialog extends frappe.ui.Dialog { return this.$wrapper.find(".modal-footer .btn-primary"); } + get_secondary_btn() { + return this.$wrapper.find(".modal-footer .btn-secondary"); + } + set_primary_action(label, click) { this.$wrapper.find(".modal-footer").removeClass("hidden"); return super.set_primary_action(label, click).removeClass("hidden"); } + set_secondary_action(click) { + return super.set_secondary_action(click).removeClass("hidden"); + } + make() { super.make(); if (this.fields) { diff --git a/frappe/public/js/frappe/form/controls/base_control.js b/frappe/public/js/frappe/form/controls/base_control.js index a315abc7e0..aed3f9ff3d 100644 --- a/frappe/public/js/frappe/form/controls/base_control.js +++ b/frappe/public/js/frappe/form/controls/base_control.js @@ -82,7 +82,7 @@ frappe.ui.form.Control = class BaseControl { is_null(value) && !in_list(["HTML", "Image", "Button"], this.df.fieldtype) ) - status = "None"; + status = "Read"; return status; } diff --git a/frappe/public/js/frappe/form/controls/color.js b/frappe/public/js/frappe/form/controls/color.js index 7fcadc6638..e9f88faec5 100644 --- a/frappe/public/js/frappe/form/controls/color.js +++ b/frappe/public/js/frappe/form/controls/color.js @@ -93,11 +93,11 @@ frappe.ui.form.ControlColor = class ControlColor extends frappe.ui.form.ControlD set_formatted_input(value) { super.set_formatted_input(value); - this.$input.val(value); - this.selected_color.css({ + this.$input?.val(value); + this.selected_color?.css({ "background-color": value || "transparent", }); - this.selected_color.toggleClass("no-value", !value); + this.selected_color?.toggleClass("no-value", !value); } get_color() { diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index e0286a4af1..7c580ff2b3 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -294,7 +294,10 @@ frappe.form.formatters = { let formatted_value = frappe.form.formatters.Text(value); // to use ql-editor styles try { - if (!$(formatted_value).find(".ql-editor").length) { + if ( + !$(formatted_value).find(".ql-editor").length && + !$(formatted_value).hasClass("ql-editor") + ) { formatted_value = `
${formatted_value}
`; } } catch (e) { diff --git a/frappe/public/js/frappe/ui/dialog.js b/frappe/public/js/frappe/ui/dialog.js index 56cbfbf5a2..ed42b81b68 100644 --- a/frappe/public/js/frappe/ui/dialog.js +++ b/frappe/public/js/frappe/ui/dialog.js @@ -168,7 +168,7 @@ frappe.ui.Dialog = class Dialog extends frappe.ui.FieldGroup { set_secondary_action(click) { this.footer.removeClass("hide"); - this.get_secondary_btn().removeClass("hide").off("click").on("click", click); + return this.get_secondary_btn().removeClass("hide").off("click").on("click", click); } set_secondary_action_label(label) { diff --git a/frappe/public/js/frappe/ui/messages.js b/frappe/public/js/frappe/ui/messages.js index 8b0585b727..bbd74dba4a 100644 --- a/frappe/public/js/frappe/ui/messages.js +++ b/frappe/public/js/frappe/ui/messages.js @@ -72,7 +72,6 @@ frappe.warn = function (title, message_html, proceed_action, primary_label, is_m d.$body.append(`
${message_html}
`); d.standard_actions.find(".btn-primary").removeClass("btn-primary").addClass("btn-danger"); - d.standard_actions.find(".btn-primary").removeClass("btn-primary").addClass("btn-danger"); d.show(); return d; diff --git a/frappe/public/js/frappe/web_form/web_form.js b/frappe/public/js/frappe/web_form/web_form.js index ccd88f0ba3..b8f7c327f6 100644 --- a/frappe/public/js/frappe/web_form/web_form.js +++ b/frappe/public/js/frappe/web_form/web_form.js @@ -24,10 +24,10 @@ export default class WebForm extends frappe.ui.FieldGroup { super.make(); this.set_page_breaks(); this.set_field_values(); - this.setup_listeners(); - if (this.is_new || this.is_form_editable) { + if (this.is_new || this.in_edit_mode) { this.setup_primary_action(); + this.setup_discard_action(); } this.setup_previous_next_button(); @@ -35,6 +35,7 @@ export default class WebForm extends frappe.ui.FieldGroup { // webform client script frappe.init_client_script && frappe.init_client_script(); + this.setup_listeners(); frappe.web_form.events.trigger("after_load"); this.after_load && this.after_load(); } @@ -43,34 +44,39 @@ export default class WebForm extends frappe.ui.FieldGroup { let field = this.fields_dict[fieldname]; field.df.change = () => { handler(field, field.value); + this.make_form_dirty(); }; } setup_listeners() { - // Event listener for triggering Save/Next button for Multi Step Forms - // Do not use `on` event here since that can be used by user which will render this function useless - // setTimeout has 200ms delay so that all the base_control triggers for the fields have been run - let me = this; - - if (!me.is_multi_step_form) { - return; - } + // setup change event for all fields if not already set through client script + this.fields.forEach((field) => { + if (!field.change) { + field.change = () => { + this.make_form_dirty(); + }; + } + }); + } - for (let field of $(".input-with-feedback")) { - $(field).change((e) => { - setTimeout(() => { - e.stopPropagation(); - me.toggle_buttons(); - }, 200); - }); - } + make_form_dirty() { + frappe.form_dirty = true; + $(".indicator-pill.orange").removeClass("hide"); } set_page_breaks() { - if (this.page_breaks.length) return; + this.page_breaks = $(".page-break"); + + if (this.page_breaks.length) { + this.page_breaks.each((i, page_break) => { + if (!$(page_break).find("form").length) { + $(page_break).remove(); + } + }); + } - this.page_breaks = $(`.page-break`); - this.is_multi_step_form = true; + this.page_breaks = $(".page-break"); + this.is_multi_step_form = !!this.page_breaks.length; } setup_previous_next_button() { @@ -80,15 +86,19 @@ export default class WebForm extends frappe.ui.FieldGroup { return; } - $(".web-form-footer .web-form-actions .left-area").prepend(` - - `); + this.$next_button = $(``); + + this.$previous_button = $(``); - $(".web-form-footer .web-form-actions .right-area").prepend(` - - `); + this.$next_button.insertAfter(".web-form-footer .right-area .discard-btn"); + this.in_view_mode && $(".web-form-footer .right-area").append(this.$next_button); + $(".web-form-footer .left-area").prepend(this.$previous_button); - $(".btn-previous").on("click", function () { + this.$previous_button.on("click", () => { let is_validated = me.validate_section(); if (!is_validated) return false; @@ -115,7 +125,7 @@ export default class WebForm extends frappe.ui.FieldGroup { return false; }); - $(".btn-next").on("click", function () { + this.$next_button.on("click", () => { let is_validated = me.validate_section(); if (!is_validated) return false; @@ -155,7 +165,29 @@ export default class WebForm extends frappe.ui.FieldGroup { } setup_primary_action() { - $(".web-form-container").on("submit", () => this.save()); + $(".web-form").on("submit", () => this.save()); + } + + setup_discard_action() { + $(".web-form-footer .discard-btn").on("click", () => this.discard_form()); + } + + discard_form() { + let path = window.location.href; + // remove new or edit after last / from url + path = path.substring(0, path.lastIndexOf("/")); + + if (frappe.form_dirty) { + frappe.warn( + __("Discard?"), + __("Are you sure you want to discard the changes?"), + () => (window.location.href = path), + __("Discard") + ); + } else { + window.location.href = path; + } + return false; } validate_section() { @@ -223,8 +255,18 @@ export default class WebForm extends frappe.ui.FieldGroup { } render_progress_dots() { + if (!this.is_multi_step_form) return; $(".center-area.paging").empty(); + if (this.in_view_mode) { + let paging_text = __("Page {0} of {1}", [ + this.current_section + 1, + this.page_breaks.length + 1, + ]); + $(".center-area.paging").append(`
${paging_text}
`); + return; + } + this.$slide_progress = $(`
`).appendTo( $(".center-area.paging") ); @@ -246,12 +288,6 @@ export default class WebForm extends frappe.ui.FieldGroup { } this.$slide_progress.append($dot); } - - let paging_text = __("Page {0} of {1}", [ - this.current_section + 1, - this.page_breaks.length + 1, - ]); - $(".center-area.paging").append(`
${paging_text}
`); } toggle_buttons() { @@ -290,7 +326,7 @@ export default class WebForm extends frappe.ui.FieldGroup { show_next_and_hide_save_button() { $(".btn-next").show(); - $(".submit-btn").hide(); + !this.allow_incomplete && $(".submit-btn").hide(); } toggle_previous_button() { @@ -398,16 +434,16 @@ export default class WebForm extends frappe.ui.FieldGroup { render_success_page(data) { if (this.allow_edit && data.name) { - $(".success-page").append(` - + $(".success-footer").append(` + ${__("Edit your response", null, "Button in web form")} `); } if (this.login_required && !this.allow_multiple && !this.show_list && data.name) { - $(".success-page").append(` - + $(".success-footer").append(` + ${__("View your response", null, "Button in web form")} `); diff --git a/frappe/public/js/frappe/web_form/web_form_list.js b/frappe/public/js/frappe/web_form/web_form_list.js index 4481901e0b..6d89b8ac95 100644 --- a/frappe/public/js/frappe/web_form/web_form_list.js +++ b/frappe/public/js/frappe/web_form/web_form_list.js @@ -381,7 +381,11 @@ frappe.ui.WebFormListRow = class WebFormListRow { formatter(this.doc[field.fieldname], field, { only_value: 1 }, this.doc) )) || ""; - let cell = $(`${value}`); + let cell = $(`

${value}

`); + if (field.fieldtype === "Text Editor") { + value = $(value).addClass("ellipsis"); + cell = $("").append(value); + } cell.appendTo(this.row); }); diff --git a/frappe/public/js/frappe/web_form/webform_script.js b/frappe/public/js/frappe/web_form/webform_script.js index 75bbce9b27..f24e10cff3 100644 --- a/frappe/public/js/frappe/web_form/webform_script.js +++ b/frappe/public/js/frappe/web_form/webform_script.js @@ -36,9 +36,6 @@ frappe.ready(function () { function show_form() { let web_form = new WebForm({ parent: $(".web-form-wrapper"), - is_new: web_form_doc.is_new, - is_form_editable: web_form_doc.is_form_editable, - web_form_name: web_form_doc.name, }); let doc = reference_doc || {}; setup_fields(web_form_doc, doc); @@ -58,7 +55,7 @@ frappe.ready(function () { function setup_fields(web_form_doc, doc_data) { web_form_doc.web_form_fields.forEach((df) => { df.is_web_form = true; - df.read_only = !web_form_doc.is_new && !web_form_doc.is_form_editable; + df.read_only = !web_form_doc.is_new && !web_form_doc.in_edit_mode; if (df.fieldtype === "Table") { df.get_data = () => { let data = []; diff --git a/frappe/public/scss/website/web_form.scss b/frappe/public/scss/website/web_form.scss index 0a4350e0bf..e5a0093307 100644 --- a/frappe/public/scss/website/web_form.scss +++ b/frappe/public/scss/website/web_form.scss @@ -1,361 +1,494 @@ @import "../common/form"; [data-doctype="Web Form"] { - .page_content { - max-width: 800px; - margin: auto; - - h1 { - font-size: 2.25rem; - margin-top: 0; - margin-bottom: 0; - } - - .web-form-banner-image { - margin: -4rem -14rem 5rem; - padding-top: 3rem; - position: relative; - - img { - position: absolute; - object-fit: cover; + .page-content-wrapper { + .container { + .page-header { width: 100%; - height: 250px; - z-index: -1; - } - } - .web-form-header { - border: 1px solid var(--dark-border-color); - border-bottom: none; - border-top-left-radius: var(--border-radius-md); - border-top-right-radius: var(--border-radius-md); - background-color: var(--fg-color); - padding: 2rem 2rem 0; - - .breadcrumb-container { - padding: 0px; - margin: 0 0 2rem; - - ol.breadcrumb { - padding: 0px; + img { + margin: -1rem 0rem -10.5rem; + object-fit: cover; + width: 100%; + height: 250px; + z-index: -1; } } - .web-form-head { - border-bottom: 1px solid var(--dark-border-color); - padding-bottom: 1.25rem; + .page_content { + max-width: 800px; + margin: auto; - .title { - display: flex; - justify-content: space-between; + h1 { + font-size: 2.25rem; + margin-top: 0; + margin-bottom: 0; + padding-bottom: 2px; } - .web-form-introduction { - color: var(--text-muted); - margin-top: 1.25rem; + .web-form-header { + border: 1px solid var(--dark-border-color); + border-bottom: none; + border-top-left-radius: var(--border-radius-md); + border-top-right-radius: var(--border-radius-md); + background-color: var(--fg-color); + padding: 2rem 2rem 0; - p { - color: var(--text-muted); + .breadcrumb-container { + padding: 0px; + margin: 0 0 2rem; + + ol.breadcrumb { + padding: 0px; + } } - } - } - } - .web-form { - background-color: var(--fg-color); - padding: 1.25rem 2rem 2rem; - border: 1px solid var(--dark-border-color); - border-top: none; - border-bottom-left-radius: var(--border-radius); - border-bottom-right-radius: var(--border-radius); - - .web-form-wrapper { - .form-control { - color: var(--text-color); - background-color: var(--control-bg); - } + .web-form-head { + border-bottom: 1px solid var(--dark-border-color); + padding-bottom: 1.25rem; - .form-section { - .section-head { - font-weight: bold; - font-size: var(--text-xl); - padding: var(--padding-md) 0; - } - } + .title { + display: flex; + justify-content: space-between; + flex-wrap: wrap; + gap: 1rem; - .form-column { - padding: 0 var(--padding-sm); + .web-form-title p { + margin-bottom: 0; + } - &:first-child { - padding-left: 0; - } + .indicator-pill { + margin-top: 7px; + } - &:last-child { - padding-right: 0; - } + .web-form-actions { + display: flex; + align-items: center; + justify-content: flex-end; + flex: 1; + + .btn { + font-size: var(--text-base); + } + } + } - @include media-breakpoint-down(sm) { - padding: 0; + .web-form-introduction { + color: var(--text-muted); + margin-top: 1.25rem; + + p { + color: var(--text-muted); + } + } } } - .web-form-skeleton { - .box-group { - display: flex; - gap: 20px; - margin-bottom: 15px; + .web-form { + background-color: var(--fg-color); + padding: 1.25rem 2rem 2rem; + border: 1px solid var(--dark-border-color); + border-top: none; + border-bottom-left-radius: var(--border-radius); + border-bottom-right-radius: var(--border-radius); + + .web-form-wrapper { + .form-control { + color: var(--text-color); + background-color: var(--control-bg); + } - .box-container { - width: 100%; + .form-section { + .section-head { + font-weight: bold; + font-size: var(--text-xl); + padding: var(--padding-md) 0; + } + } - .box { - background-color: var(--control-bg); - border-radius: var(--border-radius); + .form-column { + padding: 0 var(--padding-sm); + + .frappe-control[data-fieldtype="Rating"] { + .like-disabled-input { + background-color: unset; + padding-left: 0px; + + .rating { + cursor: default; + } + } } - .box-label { - height: 20px; - width: 100px; - margin-bottom: 0.5rem; + &:first-child { + padding-left: 0; } - .box-area { - height: 34px; - width: 100%; + &:last-child { + padding-right: 0; + } + + @include media-breakpoint-down(xs) { + padding: 0; } } - } - } - } - .web-form-footer { - margin-top: 1rem; + .web-form-skeleton { + .box-group { + display: flex; + flex-wrap: wrap; - .web-form-actions { - display: flex; - justify-content: space-between; + .box-container { + width: 100%; + padding: 0 var(--padding-sm); + margin-bottom: 15px; - .btn { - font-size: var(--font-size-base); + &:first-child { + padding-left: 0; + } + + &:last-child { + padding-right: 0; + } + + @include media-breakpoint-down(xs) { + padding: 0; + } + + .box { + background-color: var(--control-bg); + border-radius: var(--border-radius); + } + + .box-label { + height: 20px; + width: 100px; + margin-bottom: 0.5rem; + } + + .box-area { + height: 34px; + width: 100%; + } + } + } + } } - .center-area { - padding: 0.5rem; - display: flex; - align-items: center; + .web-form-footer { + margin-top: 1rem; - .slides-progress { + .web-form-actions { display: flex; - margin-right: .5rem; - - .slide-step { - @include flex(flex, center, center, null); - - height: 18px; - width: 18px; - border-radius: var(--border-radius-full); - border: 1px solid var(--gray-300); - margin: 0 var(--margin-xs); - background-color: var(--card-bg); - - .slide-step-indicator { - height: 6px; - width: 6px; - background-color: var(--gray-300); - border-radius: var(--border-radius-full); - } + justify-content: space-between; + flex-wrap: wrap; - .slide-step-complete { - display: none; + .btn { + font-size: var(--text-base); + } - .icon-xs { - height: 10px; - width: 10px; - } + .btn-link { + padding-left: 0px; + color: var(--text-color); + + &:hover { + color: var(--text-on-light-blue); } + } - &.active { - border: 1px solid var(--primary); + .left-area { + display: flex; + flex: 1; - .slide-step-indicator { - display: block; - background-color: var(--primary); - } + @include media-breakpoint-down(sm) { + order: 1 } + } - &.step-success:not(.active) { - background-color: var(--primary); - border: 1px solid var(--primary); + .center-area { + display: flex; + align-items: center; + font-size: var(--text-base); + + .slides-progress { + display: flex; + + .slide-step { + @include flex(flex, center, center, null); + + height: 18px; + width: 18px; + border-radius: var(--border-radius-full); + border: 1px solid var(--gray-300); + margin: 0 var(--margin-xs); + background-color: var(--card-bg); + + .slide-step-indicator { + height: 6px; + width: 6px; + background-color: var(--gray-300); + border-radius: var(--border-radius-full); + } - .slide-step-indicator { - display: none; - } + .slide-step-complete { + display: none; + + .icon-xs { + height: 10px; + width: 10px; + } + } + + &.active { + border: 1px solid var(--primary); + + .slide-step-indicator { + display: block; + background-color: var(--primary); + } + } + + &.step-success:not(.active) { + background-color: var(--primary); + border: 1px solid var(--primary); - .slide-step-complete { - display: flex; + .slide-step-indicator { + display: none; + } - .icon use { - stroke-width: 2; - stroke: var(--white); + .slide-step-complete { + display: flex; + + .icon use { + stroke-width: 2; + stroke: var(--white); + } + } + } + + @include media-breakpoint-down(xs) { + width: 16px; + height: 16px; } } } + + @include media-breakpoint-down(sm) { + order: 0; + width: 100%; + justify-content: center; + margin-bottom: 1.5rem; + } } - } - } - } - } - } - .attachments { - margin-top: 2rem; - padding: 2rem; - border-radius: var(--border-radius); - border: 1px solid var(--dark-border-color); - - .attachment { - display: flex; - justify-content: space-between; - gap: 6px; - color: var(--text-muted); - font-size: var(--text-md); - - &:hover { - text-decoration: none; - .file-name span { - text-decoration: underline; + .right-area { + display: flex; + justify-content: flex-end; + flex: 1; + + @include media-breakpoint-down(sm) { + order: 2 + } + } + } } } - } - } - .success-page { - background-color: var(--fg-color); - padding: 2rem; - border: 1px solid var(--dark-border-color); - border-radius: var(--border-radius); - text-align: center; - - svg.icon { - width: 5rem; - height: 5rem; - margin: 1rem; - } + .attachments { + margin-top: 2rem; + padding: 2rem; + border-radius: var(--border-radius); + border: 1px solid var(--dark-border-color); - h2 { - margin-top: 0; - margin-bottom: 0; - } + .attachment { + display: flex; + justify-content: space-between; + gap: 6px; + color: var(--text-muted); + font-size: var(--text-md); - .success-message { - margin-bottom: 1.6rem; - } - } + &:hover { + text-decoration: none; + .file-name span { + text-decoration: underline; + } + } + } + } - .web-list-container { - min-height: 470px; - border: 1px solid var(--dark-border-color); - border-radius: var(--border-radius-md); - padding: 2rem; + .success-page { + background-color: var(--fg-color); + padding: 5rem 2rem; + margin-top: 3rem; + border: 1px solid var(--dark-border-color); + border-radius: var(--border-radius); + text-align: center; - .web-list-header { - display: flex; - justify-content: space-between; - border-bottom: 1px solid var(--dark-border-color); - padding-bottom: 1.25rem; + .success-header { + display: flex; + justify-content: center; + align-items: center; + gap: 0.5rem; + margin-top: 1rem; - .web-list-actions { - align-self: center; - } - } + .success-icon { + width: 3rem; + height: 3rem; + margin: 0; - .web-list-filters { - display: flex; - flex-wrap: wrap; - margin: 1.25rem 0; - gap: 10px; - - .form-group.frappe-control { - min-width: 145px; - padding: 0px; - margin: 0px; - align-self: center; - - .checkbox { - .input-xs { - height: var(--checkbox-size); + @include media-breakpoint-down(sm) { + width: 2rem; + height: 2rem; + } } - .help-box { - display: none; + .success-title { + margin-top: 0; + margin-bottom: 0; } } - .input-xs { - height: 28px; - line-height: 1.2; + .success-body .success-message { + margin: 1rem 0rem 1.5rem; + } + + .success-footer a { + margin: 0rem 0.3rem 1rem; } } - } - .web-list-table { - overflow: auto; + .web-list-container { + min-height: 470px; + border: 1px solid var(--dark-border-color); + border-radius: var(--border-radius-md); + padding: 2rem; - .table { - border-bottom: 1px solid var(--border-color); - border-top: 1px solid var(--border-color); + .web-list-header { + display: flex; + justify-content: space-between; + flex-wrap: wrap; + gap: 1rem; + border-bottom: 1px solid var(--dark-border-color); + padding-bottom: 1.25rem; - thead tr { - th { - border: 0; - font-size: 13px; - font-weight: normal; - color: var(--text-muted); + .web-list-actions { + display: flex; + align-items: center; + justify-content: flex-end; + flex: 1; + } + } - input[type="checkbox"] { - margin-bottom: -2px; + .web-list-filters { + display: flex; + flex-wrap: wrap; + margin: 1.25rem 0; + gap: 10px; + + .form-group.frappe-control { + min-width: 145px; + padding: 0px; + margin: 0px; + align-self: center; + + .checkbox { + .input-xs { + height: var(--checkbox-size); + } + + .help-box { + display: none; + } + } + + .input-xs { + height: 28px; + line-height: 1.2; } } } - tbody tr { - color: var(--text-color); - cursor: pointer; + .web-list-table { + overflow: auto; - td { - font-size: 13px; + .table { + border-bottom: 1px solid var(--border-color); border-top: 1px solid var(--border-color); + + thead tr { + th { + border: 0; + font-size: 13px; + font-weight: normal; + color: var(--text-muted); + + input[type="checkbox"] { + margin-bottom: -2px; + } + } + } + + tbody tr { + color: var(--text-color); + cursor: pointer; + + td { + font-size: 13px; + border-top: 1px solid var(--border-color); + max-width: 160px; + + .ql-editor, p { + width: max-content; + max-width: 150px; + margin-bottom: 0; + + &.read-mode { + display: inline-flex; + gap: 5px; + } + } + } + } + + input[type="checkbox"] { + margin-top: 2px; + } + + .list-col-checkbox { + width: 1rem; + } + + .list-col-serial { + width: 1.5rem; + } } - } - input[type="checkbox"] { - margin-top: 2px; + .no-result { + min-height: 330px; + border-top: 1px solid var(--border-color); + } } - .list-col-checkbox { - width: 1rem; + .web-list-footer { + text-align: right; } + } - .list-col-serial { - width: 1.5rem; + .breadcrumb-container.container { + @include media-breakpoint-up(sm) { + padding-left: 0; } } - .no-result { - min-height: 330px; - border-top: 1px solid var(--border-color); + @include media-breakpoint-down(lg) { + padding-left: 1.5rem; + padding-right: 1.5rem; } } - .web-list-footer { - text-align: right; - } - } - - .breadcrumb-container.container { - @include media-breakpoint-up(sm) { + @include media-breakpoint-down(lg) { padding-left: 0; + padding-right: 0; } } } diff --git a/frappe/website/doctype/web_form/templates/web_form.html b/frappe/website/doctype/web_form/templates/web_form.html index fce6401457..94dcb226c4 100644 --- a/frappe/website/doctype/web_form/templates/web_form.html +++ b/frappe/website/doctype/web_form/templates/web_form.html @@ -2,49 +2,44 @@ {% block breadcrumbs %}{% endblock %} +{% block header %} + {% if banner_image %} + + Banner Image + {% endif %} +{% endblock %} + {% macro header_buttons() %} - {% if allow_print and not is_new %} + {% if allow_edit and in_view_mode %} + + {{ _("Edit Response", null, "Button in web form") }} + {% endif %} + + {% if allow_print and in_view_mode %} {% set print_format_url = "/printview?doctype=" + doc_type + "&name=" + doc_name + "&format=" + print_format %} - + {% endif %} - - {% if allow_edit and doc_name and not is_form_editable %} - - {{ _("Edit", null, "Button in web form") }} - {% endif %} {% endmacro %} {% macro action_buttons() %} - {% if is_new or is_form_editable %} -
- - - {% if is_form_editable %} - {{ _("Reset Form", null, "Button in web form") }} - {% else %} - {{ _("Clear Form", null, "Button in web form") }} - {% endif %} - -
-
-
+
+
+
+ {% if not in_view_mode %} + + - -
- {% endif %} + + {% endif %} +
{% endmacro %} {% block page_content %} - - {% if banner_image %} -
- Banner Image -
- {% endif %} -
@@ -61,12 +56,20 @@ {% endif %}
-

{{ _(title) }}

+
+ {% if show_list and not is_new %} +

{{ _(web_form_title) }}

+

{{ _(title) }}

+ {% else %} +

{{ _(title) }}

+ {% endif %} +
+ Not Saved
{{ header_buttons() }}
- {% if is_new and introduction_text %} + {% if introduction_text and (is_new or in_edit_mode) %}
{{ introduction_text }}
{% endif %}
@@ -115,30 +118,37 @@
- - - -

{{ _(success_title) or _("Submitted") }}

-

{{ _(success_message) or _("Thank you for spending your valuable time to fill this form") }}

- - {% if success_url %} -
-

- Click on this - {{_("URL")}} - if you are not redirected within - 5 - seconds. -

-
- {% else %} - {% if show_list %} - {{ _("See previous responses", null, "Button in web form") }} - {% endif %} - {% if not login_required or allow_multiple %} - {{ _("Submit another response", null, "Button in web form") }} +
+ + + +

{{ _(success_title) or _("Submitted") }}

+
+ +
+

{{ _(success_message) or _("Thank you for spending your valuable time to fill this form") }}

+
+ +
{% endblock page_content %} diff --git a/frappe/website/doctype/web_form/templates/web_form_skeleton.html b/frappe/website/doctype/web_form/templates/web_form_skeleton.html index 82fb2bccac..299356cee6 100644 --- a/frappe/website/doctype/web_form/templates/web_form_skeleton.html +++ b/frappe/website/doctype/web_form/templates/web_form_skeleton.html @@ -1,10 +1,10 @@
-
+
-
+
@@ -16,21 +16,21 @@
-
+
-
+
-
+
-
+
diff --git a/frappe/website/doctype/web_form/templates/web_list.html b/frappe/website/doctype/web_form/templates/web_list.html index 2ec6edaf1c..177ebe3c87 100644 --- a/frappe/website/doctype/web_form/templates/web_list.html +++ b/frappe/website/doctype/web_form/templates/web_list.html @@ -7,7 +7,9 @@
-

{{ _(list_title or title) }}

+
+

{{ _(list_title or title) }}

+
{%- if allow_multiple -%} New diff --git a/frappe/website/doctype/web_form/test_web_form.py b/frappe/website/doctype/web_form/test_web_form.py index 13c73d1f14..939e9d54f5 100644 --- a/frappe/website/doctype/web_form/test_web_form.py +++ b/frappe/website/doctype/web_form/test_web_form.py @@ -71,7 +71,7 @@ class TestWebForm(unittest.TestCase): def test_webform_render(self): set_request(method="GET", path="manage-events/new") content = get_response_content("manage-events/new") - self.assertIn("

New Manage Events

", content) + self.assertIn('

New Manage Events

', content) self.assertIn('data-doctype="Web Form"', content) self.assertIn('data-path="manage-events/new"', content) self.assertIn('source-type="Generator"', content) diff --git a/frappe/website/doctype/web_form/web_form.js b/frappe/website/doctype/web_form/web_form.js index 05fc7ff87b..c494781f1f 100644 --- a/frappe/website/doctype/web_form/web_form.js +++ b/frappe/website/doctype/web_form/web_form.js @@ -1,4 +1,28 @@ frappe.ui.form.on("Web Form", { + setup: function () { + frappe.meta.docfield_map["Web Form Field"].fieldtype.formatter = (value) => { + const prefix = { + "Page Break": "--red-600", + "Section Break": "--blue-600", + "Column Break": "--yellow-600", + }; + if (prefix[value]) { + value = `${value}`; + } + return value; + }; + + frappe.meta.docfield_map["Web Form Field"].fieldname.formatter = (value) => { + if (!value) return; + return frappe.unscrub(value); + }; + + frappe.meta.docfield_map["Web Form List Column"].fieldname.formatter = (value) => { + if (!value) return; + return frappe.unscrub(value); + }; + }, + refresh: function (frm) { // show is-standard only if developer mode frm.get_field("is_standard").toggle(frappe.boot.developer_mode); @@ -32,6 +56,14 @@ frappe.ui.form.on("Web Form", { frm.scroll_to_field("web_form_fields"); frappe.throw(__("Atleast one field is required in Web Form Fields Table")); } + + let page_break_count = frm.doc.web_form_fields.filter( + (f) => f.fieldtype == "Page Break" + ).length; + + if (page_break_count >= 10) { + frappe.throw(__("There can be only 9 Page Break fields in a Web Form")); + } }, add_publish_button(frm) { @@ -97,7 +129,7 @@ frappe.ui.form.on("Web Form", { get_fields_for_doctype(doc.doc_type).then((fields) => { let as_select_option = (df) => ({ - label: df.label + " (" + df.fieldtype + ")", + label: df.label, value: df.fieldname, }); update_options(fields.map(as_select_option)); @@ -147,9 +179,19 @@ frappe.ui.form.on("Web Form List Column", { frappe.ui.form.on("Web Form Field", { fieldtype: function (frm, doctype, name) { - var doc = frappe.get_doc(doctype, name); + let doc = frappe.get_doc(doctype, name); + + if (doc.fieldtype == "Page Break") { + let page_break_count = frm.doc.web_form_fields.filter( + (f) => f.fieldtype == "Page Break" + ).length; + page_break_count >= 10 && + frappe.throw(__("There can be only 9 Page Break fields in a Web Form")); + } + if (["Section Break", "Column Break", "Page Break"].includes(doc.fieldtype)) { doc.fieldname = ""; + doc.label = ""; doc.options = ""; frm.refresh_field("web_form_fields"); } @@ -188,23 +230,18 @@ function get_fields_for_doctype(doctype) { function render_list_settings_message(frm) { // render list setting message if (frm.fields_dict["list_setting_message"] && !frm.doc.login_required) { - const switch_to_form_settings_tab = ` - - ${__("Form Settings Tab")} - + const go_to_login_required_field = ` + + ${__("login_required")} + `; + let message = __( + "Login is required to see web form list view. Enable {0} to see list settings", + [go_to_login_required_field] + ); $(frm.fields_dict["list_setting_message"].wrapper) - .html( - $( - `
- ${__( - "Login is required to see web form list view. Enable login_required from {0} to see list settings", - [switch_to_form_settings_tab] - )} -
` - ) - ) - .find("span") + .html($(`
${message}
`)) + .find("code") .click(() => frm.scroll_to_field("login_required")); } else { $(frm.fields_dict["list_setting_message"].wrapper).empty(); diff --git a/frappe/website/doctype/web_form/web_form.json b/frappe/website/doctype/web_form/web_form.json index 8faa263e5b..f5ab147c64 100644 --- a/frappe/website/doctype/web_form/web_form.json +++ b/frappe/website/doctype/web_form/web_form.json @@ -5,50 +5,50 @@ "document_type": "Document", "engine": "InnoDB", "field_order": [ - "title_and_route_tab", + "form_tab", "title", "route", "published", - "column_break_4", + "column_break_1", "doc_type", "module", "is_standard", - "introduction", + "section_break_1", "introduction_text", - "form_settings_tab", + "web_form_fields", + "settings_tab", "login_required", "allow_multiple", "allow_edit", "allow_delete", - "column_break_18", + "column_break_2", "apply_document_permissions", "allow_print", "print_format", "allow_comments", "show_attachments", "allow_incomplete", - "form_fields", - "web_form_fields", + "section_break_2", "max_attachment_size", - "list_settings_tab", + "section_break_3", "list_setting_message", "show_list", "list_title", "list_columns", - "sidebar_settings_tab", + "section_break_4", "show_sidebar", "website_sidebar", "customization_tab", "button_label", "banner_image", - "column_break_37", + "column_break_3", "breadcrumbs", - "section_break_43", + "section_break_5", "success_title", "success_url", - "column_break_41", + "column_break_4", "success_message", - "scripting_style_tab", + "section_break_6", "client_script", "custom_css" ], @@ -81,10 +81,6 @@ "label": "Module", "options": "Module Def" }, - { - "fieldname": "column_break_4", - "fieldtype": "Column Break" - }, { "default": "0", "fieldname": "is_standard", @@ -158,12 +154,6 @@ "fieldtype": "Check", "label": "Allow Incomplete Forms" }, - { - "collapsible": 1, - "fieldname": "introduction", - "fieldtype": "Section Break", - "label": "Introduction" - }, { "fieldname": "introduction_text", "fieldtype": "Text Editor", @@ -250,21 +240,6 @@ "label": "List Columns", "options": "Web Form List Column" }, - { - "fieldname": "title_and_route_tab", - "fieldtype": "Tab Break", - "label": "Title & Route" - }, - { - "collapsible": 1, - "fieldname": "form_fields", - "fieldtype": "Section Break", - "label": "Form Fields" - }, - { - "fieldname": "column_break_18", - "fieldtype": "Column Break" - }, { "fieldname": "website_sidebar", "fieldtype": "Link", @@ -277,62 +252,89 @@ "label": "List Setting Message" }, { - "fieldname": "form_settings_tab", + "fieldname": "customization_tab", "fieldtype": "Tab Break", - "label": "Form Settings" + "label": "Customization" }, { - "collapsible": 1, - "collapsible_depends_on": "show_list", - "fieldname": "list_settings_tab", - "fieldtype": "Tab Break", - "label": "List Settings" + "fieldname": "success_title", + "fieldtype": "Data", + "label": "Success Title" }, { - "collapsible": 1, - "fieldname": "sidebar_settings_tab", - "fieldtype": "Tab Break", - "label": "Sidebar Settings" + "fieldname": "banner_image", + "fieldtype": "Attach Image", + "label": "Banner Image" }, { - "fieldname": "scripting_style_tab", + "fieldname": "form_tab", "fieldtype": "Tab Break", - "label": "Scripting / Style" + "label": "Form" }, { - "fieldname": "customization_tab", + "fieldname": "column_break_1", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_1", + "fieldtype": "Section Break" + }, + { + "fieldname": "settings_tab", "fieldtype": "Tab Break", - "label": "Customization" + "label": "Settings" }, { - "fieldname": "success_title", - "fieldtype": "Data", - "label": "Success Title" + "fieldname": "column_break_2", + "fieldtype": "Column Break" }, { - "fieldname": "banner_image", - "fieldtype": "Attach Image", - "label": "Banner Image" + "collapsible": 1, + "fieldname": "section_break_2", + "fieldtype": "Section Break" }, { - "fieldname": "column_break_41", + "collapsible": 1, + "collapsible_depends_on": "show_list", + "fieldname": "section_break_3", + "fieldtype": "Section Break", + "label": "List Settings" + }, + { + "collapsible": 1, + "collapsible_depends_on": "show_sidebar", + "fieldname": "section_break_4", + "fieldtype": "Section Break", + "label": "Sidebar Settings" + }, + { + "fieldname": "column_break_3", "fieldtype": "Column Break" }, { - "fieldname": "section_break_43", + "collapsible": 1, + "collapsible_depends_on": "eval: doc.success_title || doc.success_message || doc.success_url", + "fieldname": "section_break_5", "fieldtype": "Section Break", "label": "After Submission" }, { - "fieldname": "column_break_37", + "fieldname": "column_break_4", "fieldtype": "Column Break" + }, + { + "collapsible": 1, + "collapsible_depends_on": "eval: doc.client_script || doc.custom_css", + "fieldname": "section_break_6", + "fieldtype": "Section Break", + "label": "Scripting / Style" } ], "has_web_view": 1, "icon": "icon-edit", "is_published_field": "published", "links": [], - "modified": "2022-08-11 16:27:25.914627", + "modified": "2022-08-17 18:58:49.451658", "modified_by": "Administrator", "module": "Website", "name": "Web Form", diff --git a/frappe/website/doctype/web_form/web_form.py b/frappe/website/doctype/web_form/web_form.py index 718088212f..08e0c39ccd 100644 --- a/frappe/website/doctype/web_form/web_form.py +++ b/frappe/website/doctype/web_form/web_form.py @@ -124,7 +124,8 @@ def get_context(context): def get_context(self, context): """Build context to render the `web_form.html` template""" - context.is_form_editable = False + context.in_edit_mode = False + context.in_view_mode = False self.set_web_form_module() if frappe.form_dict.is_list: @@ -156,10 +157,14 @@ def get_context(context): frappe.redirect(f"/{self.route}/new") if frappe.form_dict.is_edit and not self.allow_edit: + context.in_view_mode = True frappe.redirect(f"/{self.route}/{frappe.form_dict.name}") if frappe.form_dict.is_edit: - context.is_form_editable = True + context.in_edit_mode = True + + if frappe.form_dict.is_read: + context.in_view_mode = True if ( not frappe.form_dict.is_edit @@ -167,7 +172,7 @@ def get_context(context): and self.allow_edit and frappe.form_dict.name ): - context.is_form_editable = True + context.in_edit_mode = True frappe.redirect(f"/{frappe.local.path}/edit") if ( @@ -179,6 +184,7 @@ def get_context(context): ): name = frappe.db.get_value(self.doc_type, {"owner": frappe.session.user}, "name") if name: + context.in_view_mode = True frappe.redirect(f"/{self.route}/{name}") # Show new form when @@ -203,7 +209,9 @@ def get_context(context): # load web form doc context.web_form_doc = self.as_dict(no_nulls=True) - context.web_form_doc.update(dict_with_keys(context, ["is_list", "is_new", "is_form_editable"])) + context.web_form_doc.update( + dict_with_keys(context, ["is_list", "is_new", "in_edit_mode", "in_view_mode"]) + ) if self.show_sidebar and self.website_sidebar: context.sidebar_items = get_sidebar_items(self.website_sidebar) @@ -278,17 +286,11 @@ def get_context(context): if frappe.form_dict.name: context.doc_name = frappe.form_dict.name context.reference_doc = frappe.get_doc(self.doc_type, context.doc_name) - context.title = strip_html( - context.reference_doc.get(context.reference_doc.meta.get_title_field()) + context.web_form_title = context.title + context.title = ( + strip_html(context.reference_doc.get(context.reference_doc.meta.get_title_field())) + or context.doc_name ) - if context.is_form_editable and context.parents: - context.parents.append( - { - "label": _(context.title), - "route": f"{self.route}/{context.doc_name}", - } - ) - context.title = _("Editing {0}").format(context.title) context.reference_doc.add_seen() context.reference_doctype = context.reference_doc.doctype context.reference_name = context.reference_doc.name @@ -309,7 +311,7 @@ def get_context(context): context.reference_doc.doctype, context.reference_doc.name ) - context.reference_doc = json.loads(context.reference_doc.as_json()) + context.reference_doc = context.reference_doc.as_dict(no_nulls=True) def add_custom_context_and_script(self, context): """Update context from module if standard and append script""" @@ -481,7 +483,7 @@ def accept(web_form, data, docname=None): for field in web_form.web_form_fields: fieldname = field.fieldname df = meta.get_field(fieldname) - value = data.get(fieldname, None) + value = data.get(fieldname, "") if df and df.fieldtype in ("Attach", "Attach Image"): if value and "data:" and "base64" in value: diff --git a/frappe/website/doctype/web_form_field/web_form_field.json b/frappe/website/doctype/web_form_field/web_form_field.json index dbadf52881..4fb566be88 100644 --- a/frappe/website/doctype/web_form_field/web_form_field.json +++ b/frappe/website/doctype/web_form_field/web_form_field.json @@ -32,20 +32,20 @@ "fieldname": "fieldname", "fieldtype": "Select", "in_list_view": 1, - "label": "Fieldname" + "label": "Field" }, { "fieldname": "fieldtype", "fieldtype": "Select", "in_list_view": 1, "label": "Fieldtype", - "options": "Attach\nAttach Image\nCheck\nCurrency\nData\nDate\nDatetime\nDuration\nFloat\nHTML\nInt\nLink\nPassword\nRating\nSelect\nSignature\nSmall Text\nText\nText Editor\nTable\nTime\nSection Break\nColumn Break\nPage Break" + "options": "Attach\nAttach Image\nCheck\nCurrency\nColor\nData\nDate\nDatetime\nDuration\nFloat\nHTML\nInt\nLink\nPassword\nRating\nSelect\nSignature\nSmall Text\nText\nText Editor\nTable\nTime\nSection Break\nColumn Break\nPage Break" }, { "fieldname": "label", "fieldtype": "Data", "in_list_view": 1, - "label": "Label" + "label": "Custom Label" }, { "default": "0", @@ -58,6 +58,7 @@ "default": "0", "fieldname": "reqd", "fieldtype": "Check", + "in_list_view": 1, "label": "Mandatory" }, { @@ -146,7 +147,7 @@ ], "istable": 1, "links": [], - "modified": "2022-08-10 12:59:51.170546", + "modified": "2022-08-22 17:22:39.026893", "modified_by": "Administrator", "module": "Website", "name": "Web Form Field", diff --git a/frappe/website/doctype/web_form_list_column/web_form_list_column.json b/frappe/website/doctype/web_form_list_column/web_form_list_column.json index e55aeadca6..8be724f426 100644 --- a/frappe/website/doctype/web_form_list_column/web_form_list_column.json +++ b/frappe/website/doctype/web_form_list_column/web_form_list_column.json @@ -15,14 +15,14 @@ "fieldname": "fieldname", "fieldtype": "Select", "in_list_view": 1, - "label": "Fieldname", + "label": "Field", "reqd": 1 }, { "fieldname": "label", "fieldtype": "Data", "in_list_view": 1, - "label": "Label" + "label": "Custom Label" }, { "fieldname": "fieldtype", @@ -35,7 +35,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-06-21 17:22:14.978947", + "modified": "2022-08-17 19:09:01.417841", "modified_by": "Administrator", "module": "Website", "name": "Web Form List Column",