Browse Source

feat: Webform Success Page and some customization options (backport #17790) (#17809)

Co-authored-by: Shariq Ansari <30859809+shariquerik@users.noreply.github.com>
version-14
mergify[bot] 2 years ago
committed by GitHub
parent
commit
81e23e8862
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 386 additions and 205 deletions
  1. +3
    -3
      cypress/integration/web_form.js
  2. +14
    -0
      frappe/public/js/frappe/utils/utils.js
  3. +32
    -40
      frappe/public/js/frappe/web_form/web_form.js
  4. +179
    -113
      frappe/public/scss/website/web_form.scss
  5. +76
    -28
      frappe/website/doctype/web_form/templates/web_form.html
  6. +38
    -0
      frappe/website/doctype/web_form/templates/web_form_skeleton.html
  7. +43
    -20
      frappe/website/doctype/web_form/web_form.json
  8. +1
    -1
      frappe/website/doctype/web_form/web_form.py

+ 3
- 3
cypress/integration/web_form.js View File

@@ -142,10 +142,9 @@ context("Web Form", () => {
it("Custom Breadcrumbs", () => {
cy.visit("/app/web-form/note");

cy.findByRole("tab", { name: "Form Settings" }).click();
cy.get(".form-section .section-head").contains("Customization").click();
cy.findByRole("tab", { name: "Customization" }).click();
cy.fill_field("breadcrumbs", '[{"label": _("Notes"), "route":"note"}]', "Code");
cy.get(".form-section .section-head").contains("Customization").click();
cy.get(".form-tabs .nav-item .nav-link").contains("Customization").click();
cy.save();

cy.visit("/note/Note 1");
@@ -188,6 +187,7 @@ context("Web Form", () => {

cy.fill_field("title", " Edited");
cy.get(".web-form-actions button").contains("Save").click();
cy.get(".success-page .edit-button").click();
cy.get_field("title").should("have.value", "Note 1 Edited");
});



+ 14
- 0
frappe/public/js/frappe/utils/utils.js View File

@@ -982,6 +982,20 @@ Object.assign(frappe.utils, {
}
});
},
setup_timer(start, end, $element) {
const increment = end > start;
let counter = start;

let interval = setInterval(() => {
increment ? counter++ : counter--;
if (increment ? counter > end : counter < end) {
clearInterval(interval);
return;
}
$element.text(counter);
}, 1000);
},

deep_equal(a, b) {
return deep_equal(a, b);
},


+ 32
- 40
frappe/public/js/frappe/web_form/web_form.js View File

@@ -20,6 +20,7 @@ export default class WebForm extends frappe.ui.FieldGroup {
}

make() {
this.parent.empty();
super.make();
this.set_page_breaks();
this.set_field_values();
@@ -29,7 +30,6 @@ export default class WebForm extends frappe.ui.FieldGroup {
this.setup_primary_action();
}

this.setup_footer_actions();
this.setup_previous_next_button();
this.toggle_section();

@@ -73,14 +73,6 @@ export default class WebForm extends frappe.ui.FieldGroup {
this.is_multi_step_form = true;
}

setup_footer_actions() {
if (this.is_multi_step_form) return;

if ($(".web-form-container").height() > 600) {
$(".web-form-footer").removeClass("hide");
}
}

setup_previous_next_button() {
let me = this;

@@ -380,45 +372,45 @@ export default class WebForm extends frappe.ui.FieldGroup {
return false;
}

edit() {
window.location.href = window.location.pathname + "/edit";
}

cancel() {
let path = window.location.pathname;
if (this.is_new) {
path = path.replace("/new", "");
} else {
path = path.replace("/edit", "");
}
window.location.href = path;
}

handle_success(data) {
// TODO: remove this (used for payments app)
if (this.accept_payment && !this.doc.paid) {
window.location.href = data;
}

const success_message = this.success_message || __("Submitted");
if (!this.is_new) {
$(".success-title").text(__("Updated"));
$(".success-message").text(__("Your form has been successfully updated"));
}

frappe.toast({ message: success_message, indicator: "green" });
$(".web-form-container").hide();
$(".success-page").removeClass("hide");

// redirect
setTimeout(() => {
let path = window.location.pathname;
if (this.success_url) {
frappe.utils.setup_timer(5, 0, $(".time"));
setTimeout(() => {
window.location.href = this.success_url;
}, 5000);
} else {
this.render_success_page(data);
}
}

if (this.success_url) {
path = this.success_url;
} else if (this.login_required) {
if (this.is_new && data.name) {
path = path.replace("/new", "");
path = path + "/" + data.name;
} else if (this.is_form_editable) {
path = path.replace("/edit", "");
}
}
window.location.href = path;
}, 3000);
render_success_page(data) {
if (this.allow_edit && data.name) {
$(".success-page").append(`
<a href="/${this.route}/${data.name}/edit" class="edit-button btn btn-light btn-md ml-2">
${__("Edit your response", null, "Button in web form")}
</a>
`);
}

if (this.login_required && !this.allow_multiple && !this.show_list && data.name) {
$(".success-page").append(`
<a href="/${this.route}/${data.name}" class="view-button btn btn-light btn-md ml-2">
${__("View your response", null, "Button in web form")}
</a>
`);
}
}
}

+ 179
- 113
frappe/public/scss/website/web_form.scss View File

@@ -6,36 +6,69 @@
margin: auto;

h1 {
font-size: 1.9rem;
font-size: 2.25rem;
margin-top: 0;
margin-bottom: 0;
}

.web-form-container {
.web-form-banner-image {
margin: -4rem -14rem 5rem;
padding-top: 3rem;
position: relative;

img {
position: absolute;
object-fit: cover;
width: 100%;
height: 250px;
z-index: -1;
}
}

.web-form-header {
border: 1px solid var(--dark-border-color);
border-radius: var(--border-radius-md);
padding: 2rem;
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;

.web-form-header {
display: flex;
justify-content: space-between;
margin: 0 -2rem 1rem;
padding: 0 2rem 1rem;
border-bottom: 1px solid var(--border-color);
.breadcrumb-container {
padding: 0px;
margin: 0 0 2rem;

.web-form-actions {
align-self: center;
ol.breadcrumb {
padding: 0px;
}
}

.web-form-introduction {
color: var(--text-muted);
margin-bottom: 2rem;
.web-form-head {
border-bottom: 1px solid var(--dark-border-color);
padding-bottom: 1.25rem;

p {
.title {
display: flex;
justify-content: space-between;
}

.web-form-introduction {
color: var(--text-muted);
margin-top: 1.25rem;

p {
color: var(--text-muted);
}
}
}
}

.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 {
@@ -52,7 +85,7 @@
}

.form-column {
padding: 0 var(--padding-md);
padding: 0 var(--padding-sm);

&:first-child {
padding-left: 0;
@@ -66,6 +99,34 @@
padding: 0;
}
}

.web-form-skeleton {
.box-group {
display: flex;
gap: 20px;
margin-bottom: 15px;

.box-container {
width: 100%;

.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%;
}
}
}
}
}

.web-form-footer {
@@ -83,33 +144,115 @@
padding: 0.5rem;
display: flex;
align-items: center;

.slides-progress {
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);
}

.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-indicator {
display: none;
}

.slide-step-complete {
display: flex;

.icon use {
stroke-width: 2;
stroke: var(--white);
}
}
}
}
}
}
}
}
}

.attachments {
margin: 1rem -2rem 0;
padding: 1rem 2rem 0;
border-top: 1px solid var(--border-color);
.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;
max-width: 300px;
color: var(--text-muted);
font-size: var(--text-md);
.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;
}
&:hover {
text-decoration: none;
.file-name span {
text-decoration: underline;
}
}
}
}

.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;
}

h2 {
margin-top: 0;
margin-bottom: 0;
}

.success-message {
margin-bottom: 1.6rem;
}
}

.web-list-container {
min-height: 470px;
border: 1px solid var(--dark-border-color);
@@ -119,6 +262,8 @@
.web-list-header {
display: flex;
justify-content: space-between;
border-bottom: 1px solid var(--dark-border-color);
padding-bottom: 1.25rem;

.web-list-actions {
align-self: center;
@@ -128,9 +273,7 @@
.web-list-filters {
display: flex;
flex-wrap: wrap;
margin: 1rem -2rem 0;
padding: 1rem 2rem 0;
border-top: 1px solid var(--border-color);
margin: 1.25rem 0;
gap: 10px;

.form-group.frappe-control {
@@ -158,7 +301,6 @@

.web-list-table {
overflow: auto;
margin: 1rem -2rem 0;

.table {
border-bottom: 1px solid var(--border-color);
@@ -171,14 +313,6 @@
font-weight: normal;
color: var(--text-muted);

&:first-child {
padding-left: 1.5rem;
}

&:last-child {
padding-right: 1.5rem;
}

input[type="checkbox"] {
margin-bottom: -2px;
}
@@ -192,19 +326,10 @@
td {
font-size: 13px;
border-top: 1px solid var(--border-color);

&:first-child {
padding-left: 1.5rem;
}

&:last-child {
padding-right: 1.5rem;
}
}
}

input[type="checkbox"] {
margin-left: 0.5rem;
margin-top: 2px;
}

@@ -234,63 +359,4 @@
}
}
}

.slides-progress {
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);
}

.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-indicator {
display: none;
}

.slide-step-complete {
display: flex;

.icon use {
stroke-width: 2;
stroke: var(--white);
}
}
}
}
}
}

+ 76
- 28
frappe/website/doctype/web_form/templates/web_form.html View File

@@ -20,7 +20,7 @@
{% macro action_buttons() %}
{% if is_new or is_form_editable %}
<div class="left-area">
<!-- cancel button -->
<!-- clear button -->
<a href="/{{ path }}" class="clear-btn btn btn-light btn-md">
{% if is_form_editable %}
{{ _("Reset Form", null, "Button in web form") }}
@@ -38,33 +38,53 @@
{% endmacro %}

{% block page_content %}

<!-- breadcrumb -->
{% if has_header and login_required and show_list %}
{% include "templates/includes/breadcrumbs.html" %}
{% else %}
<div style="height: 3rem"></div>
<!-- banner image -->
{% if banner_image %}
<div class="web-form-banner-image">
<img src="{{ banner_image }}" alt="Banner Image">
</div>
{% endif %}

<!-- main card -->
<form role="form" class="web-form-container">
<!-- web form container -->
<div class="web-form-container">
<!-- breadcrumb -->
{% if not banner_image and has_header and login_required and show_list %}
{% include "templates/includes/breadcrumbs.html" %}
{% else %}
<div style="height: 3rem"></div>
{% endif %}

<!-- header -->
<div class="web-form-header">
<h1>{{ _(title) }}</h1>
<div class="web-form-actions">
{{ header_buttons() }}
{% if banner_image and has_header and login_required and show_list %}
{% include "templates/includes/breadcrumbs.html" %}
{% endif %}
<div class="web-form-head">
<div class="title">
<h1>{{ _(title) }}</h1>
<div class="web-form-actions">
{{ header_buttons() }}
</div>
</div>
{% if is_new and introduction_text %}
<div class="web-form-introduction">{{ introduction_text }}</div>
{% endif %}
</div>
</div>
<div class="web-form-body">
{% if is_new and introduction_text %}
<div class="web-form-introduction">{{ introduction_text }}</div>
{% endif %}
<div class="web-form-wrapper"></div>

<!-- main card -->
<form role="form" class="web-form">
<div class="web-form-body">
<div class="web-form-wrapper">
{% include "website/doctype/web_form/templates/web_form_skeleton.html" %}
</div>
</div>
<div class="web-form-footer">
<div class="web-form-actions">
{{ action_buttons() }}
</div>
</div>
</div>
</form>

<!-- attachments -->
{% if show_attachments and not is_new and attachments %}
@@ -81,17 +101,45 @@
{% endfor %}
</div>
{% endif %} {# attachments #}
</form>

<!-- comments -->
{% if allow_comments and not is_new and not is_list -%}
<div class="comments">
<h3>{{ _("Comments") }}</h3>
{% include 'templates/includes/comments/comments.html' %}
</div>
{%- else -%}
<div style="height: 3rem"></div>
{%- endif %} {# comments #}
<!-- comments -->
{% if allow_comments and not is_new and not is_list -%}
<div class="comments">
<h3>{{ _("Comments") }}</h3>
{% include 'templates/includes/comments/comments.html' %}
</div>
{%- else -%}
<div style="height: 3rem"></div>
{%- endif %} {# comments #}
</div>

<!-- success page -->
<div class="success-page hide">
<svg class="icon">
<use href="#icon-solid-success"></use>
</svg>
<h2 class="success-title">{{ _(success_title) or _("Submitted") }}</h2>
<p class="success-message">{{ _(success_message) or _("Thank you for spending your valuable time to fill this form") }}</p>

{% if success_url %}
<div class="success_url_message">
<p>
<span>Click on this </span>
<a href="{{ success_url }}">{{_("URL")}}</a>
<span> if you are not redirected within </span>
<span class="time">5</span>
<span> seconds.</span>
</p>
</div>
{% else %}
{% if show_list %}
<a href="/{{ route }}/list" class="show-list-button btn btn-light btn-md mr-2">{{ _("See previous responses", null, "Button in web form") }}</a>
{% endif %}
{% if not login_required or allow_multiple %}
<a href="/{{ route }}/new" class="new-btn btn btn-light btn-md">{{ _("Submit another response", null, "Button in web form") }}</a>
{% endif %}
{% endif %}
</div>

{% endblock page_content %}



+ 38
- 0
frappe/website/doctype/web_form/templates/web_form_skeleton.html View File

@@ -0,0 +1,38 @@
<div class="web-form-skeleton">
<div class="box-group">
<div class="box-container">
<div class="box box-label"></div>
<div class="box box-area"></div>
</div>
<div class="box-container">
<div class="box box-label"></div>
<div class="box box-area"></div>
</div>
</div>
<div class="box-group">
<div class="box-container">
<div class="box box-label"></div>
<div class="box box-area"></div>
</div>
</div>
<div class="box-group">
<div class="box-container">
<div class="box box-label"></div>
<div class="box box-area"></div>
</div>
<div class="box-container">
<div class="box box-label"></div>
<div class="box box-area"></div>
</div>
</div>
<div class="box-group">
<div class="box-container">
<div class="box box-label"></div>
<div class="box box-area"></div>
</div>
<div class="box-container">
<div class="box box-label"></div>
<div class="box box-area"></div>
</div>
</div>
</div>

+ 43
- 20
frappe/website/doctype/web_form/web_form.json View File

@@ -30,12 +30,6 @@
"form_fields",
"web_form_fields",
"max_attachment_size",
"actions",
"breadcrumbs",
"button_label",
"column_break_29",
"success_message",
"success_url",
"list_settings_tab",
"list_setting_message",
"show_list",
@@ -44,6 +38,16 @@
"sidebar_settings_tab",
"show_sidebar",
"website_sidebar",
"customization_tab",
"button_label",
"banner_image",
"column_break_37",
"breadcrumbs",
"section_break_43",
"success_title",
"success_url",
"column_break_41",
"success_message",
"scripting_style_tab",
"client_script",
"custom_css"
@@ -162,7 +166,7 @@
},
{
"fieldname": "introduction_text",
"fieldtype": "Small Text",
"fieldtype": "Text Editor",
"ignore_xss_filter": 1,
"label": "Introduction"
},
@@ -183,12 +187,6 @@
"fieldtype": "Code",
"label": "Client Script"
},
{
"collapsible": 1,
"fieldname": "actions",
"fieldtype": "Section Break",
"label": "Customization"
},
{
"default": "Save",
"fieldname": "button_label",
@@ -196,7 +194,7 @@
"label": "Submit Button Label"
},
{
"description": "Message to be displayed on successful completion (only for Guest users)",
"description": "Message to be displayed on successful completion",
"fieldname": "success_message",
"fieldtype": "Text",
"label": "Success Message"
@@ -217,7 +215,8 @@
"description": "List as [{\"label\": _(\"Jobs\"), \"route\":\"jobs\"}]",
"fieldname": "breadcrumbs",
"fieldtype": "Code",
"label": "Breadcrumbs"
"label": "Breadcrumbs",
"max_height": "140px"
},
{
"fieldname": "custom_css",
@@ -272,10 +271,6 @@
"label": "Website Sidebar",
"options": "Website Sidebar"
},
{
"fieldname": "column_break_29",
"fieldtype": "Column Break"
},
{
"fieldname": "list_setting_message",
"fieldtype": "HTML",
@@ -303,13 +298,41 @@
"fieldname": "scripting_style_tab",
"fieldtype": "Tab Break",
"label": "Scripting / Style"
},
{
"fieldname": "customization_tab",
"fieldtype": "Tab Break",
"label": "Customization"
},
{
"fieldname": "success_title",
"fieldtype": "Data",
"label": "Success Title"
},
{
"fieldname": "banner_image",
"fieldtype": "Attach Image",
"label": "Banner Image"
},
{
"fieldname": "column_break_41",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_43",
"fieldtype": "Section Break",
"label": "After Submission"
},
{
"fieldname": "column_break_37",
"fieldtype": "Column Break"
}
],
"has_web_view": 1,
"icon": "icon-edit",
"is_published_field": "published",
"links": [],
"modified": "2022-08-10 15:38:28.611328",
"modified": "2022-08-11 16:27:25.914627",
"modified_by": "Administrator",
"module": "Website",
"name": "Web Form",


+ 1
- 1
frappe/website/doctype/web_form/web_form.py View File

@@ -281,7 +281,7 @@ def get_context(context):
context.title = strip_html(
context.reference_doc.get(context.reference_doc.meta.get_title_field())
)
if context.is_form_editable:
if context.is_form_editable and context.parents:
context.parents.append(
{
"label": _(context.title),


Loading…
Cancel
Save