diff --git a/frappe/public/js/frappe/web_form/web_form.js b/frappe/public/js/frappe/web_form/web_form.js index 964a8ad0bb..8c975136dd 100644 --- a/frappe/public/js/frappe/web_form/web_form.js +++ b/frappe/public/js/frappe/web_form/web_form.js @@ -9,6 +9,7 @@ export default class WebForm extends frappe.ui.FieldGroup { frappe.web_form = this; frappe.web_form.events = {}; Object.assign(frappe.web_form.events, EventEmitterMixin); + this.current_section = 0; } prepare(web_form_doc, doc) { @@ -19,12 +20,16 @@ export default class WebForm extends frappe.ui.FieldGroup { make() { super.make(); + this.set_sections(); this.set_field_values(); + this.setup_listeners(); if (this.introduction_text) this.set_form_description(this.introduction_text); if (this.allow_print && !this.is_new) this.setup_print_button(); if (this.allow_delete && !this.is_new) this.setup_delete_button(); if (this.is_new) this.setup_cancel_button(); this.setup_primary_action(); + this.setup_previous_next_button(); + this.toggle_section(); $(".link-btn").remove(); // webform client script @@ -40,6 +45,79 @@ export default class WebForm extends frappe.ui.FieldGroup { }; } + 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; + } + + for (let field of $(".input-with-feedback")) { + $(field).change((e) => { + setTimeout(() => { + e.stopPropagation(); + me.toggle_buttons(); + }, 200); + }); + } + } + + set_sections() { + if (this.sections.length) return; + + this.sections = $(`.form-section`); + } + + setup_previous_next_button() { + let me = this; + + if (!me.is_multi_step_form) { + return; + } + + $('.web-form-footer').after(` +
+ `); + + $('.btn-previous').on('click', function () { + let is_validated = me.validate_section(); + + if (!is_validated) return; + + for (let idx = me.current_section; idx < me.sections.length; idx--) { + let is_empty = me.is_previous_section_empty(idx); + me.current_section = me.current_section > 0 ? me.current_section - 1 : me.current_section; + + if (!is_empty) { + break + } + } + me.toggle_section(); + }); + + $('.btn-next').on('click', function () { + let is_validated = me.validate_section(); + + if (!is_validated) return; + + for (let idx = me.current_section; idx < me.sections.length; idx++) { + let is_empty = me.is_next_section_empty(idx); + me.current_section = me.current_section < me.sections.length ? me.current_section + 1 : me.current_section; + + if (!is_empty) { + break + } + } + me.toggle_section(); + }); + } + set_field_values() { if (this.doc.name) this.set_values(this.doc); else return; @@ -104,6 +182,103 @@ export default class WebForm extends frappe.ui.FieldGroup { ); } + validate_section() { + if (this.allow_incomplete) return true; + + let fields = $(`.form-section:eq(${this.current_section}) .form-control`); + let errors = [] + + for (let field of fields) { + let fieldname = $(field).attr("data-fieldname"); + if (!fieldname) continue; + + field = this.fields_dict[fieldname]; + + if (field.get_value) { + let value = field.get_value(); + if (field.df.reqd && is_null(typeof value === 'string' ? strip_html(value) : value)) errors.push(__(field.df.label)); + + if (field.df.reqd && field.df.fieldtype === 'Text Editor' && is_null(strip_html(cstr(value)))) errors.push(__(field.df.label)); + } + } + + if (errors.length) { + frappe.msgprint({ + title: __('Missing Values Required'), + message: __('Following fields have missing values:') + + '