Co-authored-by: Shariq Ansari <30859809+shariquerik@users.noreply.github.com>version-14
@@ -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"); | |||
}); | |||
@@ -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); | |||
}, | |||
@@ -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> | |||
`); | |||
} | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -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 %} | |||
@@ -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> |
@@ -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", | |||
@@ -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), | |||