{"version":3,"file":"web_form.min.js","sources":["../../../apps/frappe/frappe/public/js/frappe/utils/datetime.js","../../../apps/frappe/frappe/public/js/frappe/web_form/web_form_list.js","../../../apps/frappe/frappe/public/js/frappe/event_emitter.js","../../../apps/frappe/frappe/public/js/frappe/web_form/web_form.js","../../../apps/frappe/frappe/public/js/frappe/web_form/webform_script.js"],"sourcesContent":["// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors\n// MIT License. See license.txt\n\nfrappe.provide('frappe.datetime');\n\nfrappe.defaultDateFormat = \"YYYY-MM-DD\";\nfrappe.defaultTimeFormat = \"HH:mm:ss\";\nfrappe.defaultDatetimeFormat = frappe.defaultDateFormat + \" \" + frappe.defaultTimeFormat;\nmoment.defaultFormat = frappe.defaultDateFormat;\n\nfrappe.provide(\"frappe.datetime\");\n\n$.extend(frappe.datetime, {\n\tconvert_to_user_tz: function(date, format) {\n\t\t// format defaults to true\n\t\tif(frappe.sys_defaults.time_zone) {\n\t\t\tvar date_obj = moment.tz(date, frappe.sys_defaults.time_zone).local();\n\t\t} else {\n\t\t\tvar date_obj = moment(date);\n\t\t}\n\n\t\treturn (format===false) ? date_obj : date_obj.format(frappe.defaultDatetimeFormat);\n\t},\n\n\tconvert_to_system_tz: function(date, format) {\n\t\t// format defaults to true\n\n\t\tif(frappe.sys_defaults.time_zone) {\n\t\t\tvar date_obj = moment(date).tz(frappe.sys_defaults.time_zone);\n\t\t} else {\n\t\t\tvar date_obj = moment(date);\n\t\t}\n\n\t\treturn (format===false) ? date_obj : date_obj.format(frappe.defaultDatetimeFormat);\n\t},\n\n\tis_timezone_same: function() {\n\t\tif(frappe.sys_defaults.time_zone) {\n\t\t\treturn moment().tz(frappe.sys_defaults.time_zone).utcOffset() === moment().utcOffset();\n\t\t} else {\n\t\t\treturn true;\n\t\t}\n\t},\n\n\tstr_to_obj: function(d) {\n\t\treturn moment(d, frappe.defaultDatetimeFormat)._d;\n\t},\n\n\tobj_to_str: function(d) {\n\t\treturn moment(d).locale(\"en\").format();\n\t},\n\n\tobj_to_user: function(d) {\n\t\treturn moment(d).format(frappe.datetime.get_user_date_fmt().toUpperCase());\n\t},\n\n\tget_diff: function(d1, d2) {\n\t\treturn moment(d1).diff(d2, \"days\");\n\t},\n\n\tget_hour_diff: function(d1, d2) {\n\t\treturn moment(d1).diff(d2, \"hours\");\n\t},\n\n\tget_day_diff: function(d1, d2) {\n\t\treturn moment(d1).diff(d2, \"days\");\n\t},\n\n\tadd_days: function(d, days) {\n\t\treturn moment(d).add(days, \"days\").format();\n\t},\n\n\tadd_months: function(d, months) {\n\t\treturn moment(d).add(months, \"months\").format();\n\t},\n\n\tweek_start: function() {\n\t\treturn moment().startOf(\"week\").format();\n\t},\n\n\tweek_end: function() {\n\t\treturn moment().endOf(\"week\").format();\n\t},\n\n\tmonth_start: function() {\n\t\treturn moment().startOf(\"month\").format();\n\t},\n\n\tmonth_end: function() {\n\t\treturn moment().endOf(\"month\").format();\n\t},\n\n\tquarter_start: function() {\n\t\treturn moment().startOf(\"quarter\").format();\n\t},\n\n\tquarter_end: function() {\n\t\treturn moment().endOf(\"quarter\").format();\n\t},\n\n\tyear_start: function(){\n\t\treturn moment().startOf(\"year\").format();\n\t},\n\n\tyear_end: function(){\n\t\treturn moment().endOf(\"year\").format();\n\t},\n\n\tget_user_time_fmt: function() {\n\t\treturn frappe.sys_defaults && frappe.sys_defaults.time_format || \"HH:mm:ss\";\n\t},\n\n\tget_user_date_fmt: function() {\n\t\treturn frappe.sys_defaults && frappe.sys_defaults.date_format || \"yyyy-mm-dd\";\n\t},\n\n\tget_user_fmt: function() { // For backwards compatibility only\n\t\treturn frappe.sys_defaults && frappe.sys_defaults.date_format || \"yyyy-mm-dd\";\n\t},\n\n\tstr_to_user: function(val, only_time = false) {\n\t\tif(!val) return \"\";\n\n\t\tvar user_time_fmt = frappe.datetime.get_user_time_fmt();\n\t\tif(only_time) {\n\t\t\treturn moment(val, frappe.defaultTimeFormat)\n\t\t\t\t.format(user_time_fmt);\n\t\t}\n\n\t\tvar user_date_fmt = frappe.datetime.get_user_date_fmt().toUpperCase();\n\t\tif(typeof val !== \"string\" || val.indexOf(\" \")===-1) {\n\t\t\treturn moment(val).format(user_date_fmt);\n\t\t} else {\n\t\t\treturn moment(val, \"YYYY-MM-DD HH:mm:ss\").format(user_date_fmt + \" \" + user_time_fmt);\n\t\t}\n\t},\n\n\tget_datetime_as_string: function(d) {\n\t\treturn moment(d).format(\"YYYY-MM-DD HH:mm:ss\");\n\t},\n\n\tuser_to_str: function(val, only_time = false) {\n\n\t\tvar user_time_fmt = frappe.datetime.get_user_time_fmt();\n\t\tif(only_time) {\n\t\t\treturn moment(val, user_time_fmt)\n\t\t\t\t.format(frappe.defaultTimeFormat);\n\t\t}\n\n\t\tvar user_fmt = frappe.datetime.get_user_date_fmt().toUpperCase();\n\t\tvar system_fmt = \"YYYY-MM-DD\";\n\n\t\tif(val.indexOf(\" \")!==-1) {\n\t\t\tuser_fmt += \" \" + user_time_fmt;\n\t\t\tsystem_fmt += \" HH:mm:ss\";\n\t\t}\n\n\t\t// user_fmt.replace(\"YYYY\", \"YY\")? user might only input 2 digits of the year, which should also be parsed\n\t\treturn moment(val, [user_fmt.replace(\"YYYY\", \"YY\"),\n\t\t\tuser_fmt]).locale(\"en\").format(system_fmt);\n\t},\n\n\tuser_to_obj: function(d) {\n\t\treturn frappe.datetime.str_to_obj(frappe.datetime.user_to_str(d));\n\t},\n\n\tglobal_date_format: function(d) {\n\t\tvar m = moment(d);\n\t\tif(m._f && m._f.indexOf(\"HH\")!== -1) {\n\t\t\treturn m.format(\"Do MMMM YYYY, h:mma\")\n\t\t} else {\n\t\t\treturn m.format('Do MMMM YYYY');\n\t\t}\n\t},\n\n\tnow_date: function(as_obj = false) {\n\t\treturn frappe.datetime._date(frappe.defaultDateFormat, as_obj);\n\t},\n\n\tnow_time: function(as_obj = false) {\n\t\treturn frappe.datetime._date(frappe.defaultTimeFormat, as_obj);\n\t},\n\n\tnow_datetime: function(as_obj = false) {\n\t\treturn frappe.datetime._date(frappe.defaultDatetimeFormat, as_obj);\n\t},\n\n\t_date: function(format, as_obj = false) {\n\t\tconst time_zone = frappe.sys_defaults && frappe.sys_defaults.time_zone;\n\t\tlet date;\n\t\tif (time_zone) {\n\t\t\tdate = moment.tz(time_zone);\n\t\t} else {\n\t\t\tdate = moment();\n\t\t}\n\t\tif (as_obj) {\n\t\t\treturn frappe.datetime.moment_to_date_obj(date);\n\t\t} else {\n\t\t\treturn date.format(format);\n\t\t}\n\t},\n\n\tmoment_to_date_obj: function(moment) {\n\t\tconst date_obj = new Date();\n\t\tconst date_array = moment.toArray();\n\t\tdate_obj.setFullYear(date_array[0]);\n\t\tdate_obj.setMonth(date_array[1]);\n\t\tdate_obj.setDate(date_array[2]);\n\t\tdate_obj.setHours(date_array[3]);\n\t\tdate_obj.setMinutes(date_array[4]);\n\t\tdate_obj.setSeconds(date_array[5]);\n\t\tdate_obj.setMilliseconds(date_array[6]);\n\t\treturn date_obj;\n\t},\n\n\tnowdate: function() {\n\t\treturn frappe.datetime.now_date();\n\t},\n\n\tget_today: function() {\n\t\treturn frappe.datetime.now_date();\n\t},\n\n\tget_time: (timestamp) => {\n\t\t// return time with AM/PM\n\t\treturn moment(timestamp).format('hh:mm A');\n\t},\n\n\tvalidate: function(d) {\n\t\treturn moment(d, [\n\t\t\tfrappe.defaultDateFormat,\n\t\t\tfrappe.defaultDatetimeFormat,\n\t\t\tfrappe.defaultTimeFormat\n\t\t], true).isValid();\n\t},\n\n\tget_first_day_of_the_week_index() {\n\t\tconst first_day_of_the_week = frappe.sys_defaults.first_day_of_the_week || \"Sunday\";\n\t\treturn moment.weekdays().indexOf(first_day_of_the_week);\n\t}\n\n});\n\n// Proxy for dateutil and get_today\nObject.defineProperties(window, {\n\t'dateutil': {\n\t\tget: function() {\n\t\t\tconsole.warn('Please use `frappe.datetime` instead of `dateutil`. It will be deprecated soon.');\n\t\t\treturn frappe.datetime;\n\t\t},\n\t\tconfigurable: true\n\t},\n\t'date': {\n\t\tget: function() {\n\t\t\tconsole.warn('Please use `frappe.datetime` instead of `date`. It will be deprecated soon.');\n\t\t\treturn frappe.datetime;\n\t\t},\n\t\tconfigurable: true\n\t},\n\t'get_today': {\n\t\tget: function() {\n\t\t\tconsole.warn('Please use `frappe.datetime.get_today` instead of `get_today`. It will be deprecated soon.');\n\t\t\treturn frappe.datetime.get_today;\n\t\t},\n\t\tconfigurable: true\n\t}\n});\n","frappe.provide(\"frappe.ui\");\nfrappe.provide(\"frappe.views\");\nfrappe.provide(\"frappe.web_form_list\");\n\nexport default class WebFormList {\n\tconstructor(opts) {\n\t\tObject.assign(this, opts);\n\t\tfrappe.web_form_list = this;\n\t\tthis.wrapper = document.getElementById(\"datatable\");\n\t\tthis.make_actions();\n\t\tthis.make_filters();\n\t\t$('.link-btn').remove();\n\t}\n\n\trefresh() {\n\t\tif (this.table) {\n\t\t\tArray.from(this.table.tBodies).forEach(tbody => tbody.remove());\n\t\t\tlet check = document.getElementById('select-all');\n\t\t\tcheck.checked = false;\n\t\t}\n\t\tthis.rows = [];\n\t\tthis.page_length = 20;\n\t\tthis.web_list_start = 0;\n\n\t\tfrappe.run_serially([\n\t\t\t() => this.get_list_view_fields(),\n\t\t\t() => this.get_data(),\n\t\t\t() => this.make_table(),\n\t\t\t() => this.create_more()\n\t\t]);\n\t}\n\n\tmake_filters() {\n\t\tthis.filters = {};\n\t\tthis.filter_input = [];\n\t\tconst filter_area = document.getElementById('list-filters');\n\n\t\tfrappe.call('frappe.website.doctype.web_form.web_form.get_web_form_filters', {\n\t\t\tweb_form_name: this.web_form_name\n\t\t}).then(response => {\n\t\t\tlet fields = response.message;\n\t\t\tfields.forEach(field => {\n\t\t\t\tlet col = document.createElement('div.col-sm-4');\n\t\t\t\tcol.classList.add('col', 'col-sm-3');\n\t\t\t\tfilter_area.appendChild(col);\n\t\t\t\tif (field.default) this.add_filter(field.fieldname, field.default, field.fieldtype);\n\n\t\t\t\tlet input = frappe.ui.form.make_control({\n\t\t\t\t\tdf: {\n\t\t\t\t\t\tfieldtype: field.fieldtype,\n\t\t\t\t\t\tfieldname: field.fieldname,\n\t\t\t\t\t\toptions: field.options,\n\t\t\t\t\t\tonly_select: true,\n\t\t\t\t\t\tlabel: __(field.label),\n\t\t\t\t\t\tonchange: (event) => {\n\t\t\t\t\t\t\t$('#more').remove();\n\t\t\t\t\t\t\tthis.add_filter(field.fieldname, input.value, field.fieldtype);\n\t\t\t\t\t\t\tthis.refresh();\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tparent: col,\n\t\t\t\t\tvalue: field.default,\n\t\t\t\t\trender_input: 1,\n\t\t\t\t});\n\t\t\t\tthis.filter_input.push(input);\n\t\t\t});\n\t\t\tthis.refresh();\n\t\t});\n\t}\n\n\tadd_filter(field, value, fieldtype) {\n\t\tif (!value) {\n\t\t\tdelete this.filters[field];\n\t\t} else {\n\t\t\tif (fieldtype === 'Data') value = ['like', value + '%'];\n\t\t\tObject.assign(this.filters, Object.fromEntries([[field, value]]));\n\t\t}\n\t}\n\n\tget_list_view_fields() {\n\t\treturn frappe\n\t\t\t.call({\n\t\t\t\tmethod:\n\t\t\t\t\t\"frappe.website.doctype.web_form.web_form.get_in_list_view_fields\",\n\t\t\t\targs: { doctype: this.doctype }\n\t\t\t})\n\t\t\t.then(response => (this.fields_list = response.message));\n\t}\n\n\tfetch_data() {\n\t\treturn frappe.call({\n\t\t\tmethod: \"frappe.www.list.get_list_data\",\n\t\t\targs: {\n\t\t\t\tdoctype: this.doctype,\n\t\t\t\tfields: this.fields_list.map(df => df.fieldname),\n\t\t\t\tlimit_start: this.web_list_start,\n\t\t\t\tweb_form_name: this.web_form_name,\n\t\t\t\t...this.filters\n\t\t\t}\n\t\t});\n\t}\n\n\tasync get_data() {\n\t\tlet response = await this.fetch_data();\n\t\tthis.data = await response.message;\n\t}\n\n\tmore() {\n\t\tthis.web_list_start += this.page_length;\n\t\tthis.fetch_data().then((res) => {\n\t\t\tif (res.message.length === 0) {\n\t\t\t\tfrappe.msgprint(__(\"No more items to display\"));\n\t\t\t}\n\t\t\tthis.append_rows(res.message);\n\t\t});\n\n\t}\n\n\tmake_table() {\n\t\tthis.columns = this.fields_list.map(df => {\n\t\t\treturn {\n\t\t\t\tlabel: df.label,\n\t\t\t\tfieldname: df.fieldname,\n\t\t\t\tfieldtype: df.fieldtype\n\t\t\t};\n\t\t});\n\n\t\tif (!this.table) {\n\t\t\tthis.table = document.createElement(\"table\");\n\t\t\tthis.table.classList.add(\"table\");\n\t\t\tthis.make_table_head();\n\t\t}\n\n\t\tthis.append_rows(this.data);\n\n\t\tthis.wrapper.appendChild(this.table);\n\t}\n\n\tmake_table_head() {\n\t\t// Create Heading\n\t\tlet thead = this.table.createTHead();\n\t\tthead.style.backgroundColor = \"#f7fafc\";\n\t\tthead.style.color = \"#8d99a6\";\n\t\tlet row = thead.insertRow();\n\n\t\tlet th = document.createElement(\"th\");\n\n\t\tlet checkbox = document.createElement(\"input\");\n\t\tcheckbox.type = \"checkbox\";\n\t\tcheckbox.id = \"select-all\";\n\t\tcheckbox.onclick = event =>\n\t\t\tthis.toggle_select_all(event.target.checked);\n\n\t\tth.appendChild(checkbox);\n\t\trow.appendChild(th);\n\n\t\tadd_heading(row, __(\"Sr\"));\n\t\tthis.columns.forEach(col => {\n\t\t\tadd_heading(row, __(col.label));\n\t\t});\n\n\t\tfunction add_heading(row, label) {\n\t\t\tlet th = document.createElement(\"th\");\n\t\t\tth.innerText = label;\n\t\t\trow.appendChild(th);\n\t\t}\n\t}\n\n\tappend_rows(row_data) {\n\t\tconst tbody = this.table.childNodes[1] || this.table.createTBody();\n\t\trow_data.forEach((data_item) => {\n\t\t\tlet row_element = tbody.insertRow();\n\t\t\trow_element.setAttribute(\"id\", data_item.name);\n\n\t\t\tlet row = new frappe.ui.WebFormListRow({\n\t\t\t\trow: row_element,\n\t\t\t\tdoc: data_item,\n\t\t\t\tcolumns: this.columns,\n\t\t\t\tserial_number: this.rows.length + 1,\n\t\t\t\tevents: {\n\t\t\t\t\tonEdit: () => this.open_form(data_item.name),\n\t\t\t\t\tonSelect: () => this.toggle_delete()\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tthis.rows.push(row);\n\t\t});\n\t}\n\n\tmake_actions() {\n\t\tconst actions = document.querySelector(\".list-view-actions\");\n\n\t\tfrappe.has_permission(this.doctype, \"\", \"delete\", () => {\n\t\t\tthis.addButton(actions, \"delete-rows\", \"danger\", true, \"Delete\", () =>\n\t\t\t\tthis.delete_rows()\n\t\t\t);\n\t\t});\n\n\t\tthis.addButton(\n\t\t\tactions,\n\t\t\t\"new\",\n\t\t\t\"primary\",\n\t\t\tfalse,\n\t\t\t\"New\",\n\t\t\t() => (window.location.href = window.location.pathname + \"?new=1\")\n\t\t);\n\t}\n\n\taddButton(wrapper, id, type, hidden, name, action) {\n\t\tif (document.getElementById(id)) return;\n\t\tconst button = document.createElement(\"button\");\n\t\tif (type == \"secondary\") {\n\t\t\tbutton.classList.add(\n\t\t\t\t\"btn\",\n\t\t\t\t\"btn-secondary\",\n\t\t\t\t\"btn-sm\",\n\t\t\t\t\"ml-2\",\n\t\t\t\t\"text-white\"\n\t\t\t);\n\t\t}\n\t\telse if (type == \"danger\") {\n\t\t\tbutton.classList.add(\n\t\t\t\t\"btn\",\n\t\t\t\t\"btn-danger\",\n\t\t\t\t\"button-delete\",\n\t\t\t\t\"btn-sm\",\n\t\t\t\t\"ml-2\"\n\t\t\t);\n\t\t}\n\t\telse {\n\t\t\tbutton.classList.add(\"btn\", \"btn-primary\", \"btn-sm\", \"ml-2\");\n\t\t}\n\n\t\tbutton.id = id;\n\t\tbutton.innerText = name;\n\t\tbutton.hidden = hidden;\n\n\t\tbutton.onclick = action;\n\t\twrapper.appendChild(button);\n\t}\n\n\tcreate_more() {\n\t\tif (this.rows.length >= this.page_length) {\n\t\t\tconst footer = document.querySelector(\".list-view-footer\");\n\t\t\tthis.addButton(footer, \"more\", \"secondary\", false, \"More\", () => this.more());\n\t\t}\n\t}\n\n\ttoggle_select_all(checked) {\n\t\tthis.rows.forEach(row => row.toggle_select(checked));\n\t}\n\n\topen_form(name) {\n\t\twindow.location.href = window.location.pathname + \"?name=\" + name;\n\t}\n\n\tget_selected() {\n\t\treturn this.rows.filter(row => row.is_selected());\n\t}\n\n\ttoggle_delete() {\n\t\tif (!this.settings.allow_delete) return\n\t\tlet btn = document.getElementById(\"delete-rows\");\n\t\tbtn.hidden = !this.get_selected().length;\n\t}\n\n\tdelete_rows() {\n\t\tif (!this.settings.allow_delete) return\n\t\tfrappe\n\t\t\t.call({\n\t\t\t\ttype: \"POST\",\n\t\t\t\tmethod:\n\t\t\t\t\t\"frappe.website.doctype.web_form.web_form.delete_multiple\",\n\t\t\t\targs: {\n\t\t\t\t\tweb_form_name: this.web_form_name,\n\t\t\t\t\tdocnames: this.get_selected().map(row => row.doc.name)\n\t\t\t\t}\n\t\t\t})\n\t\t\t.then(() => {\n\t\t\t\tthis.refresh()\n\t\t\t\tthis.toggle_delete()\n\t\t\t});\n\t}\n};\n\nfrappe.ui.WebFormListRow = class WebFormListRow {\n\tconstructor({ row, doc, columns, serial_number, events, options }) {\n\t\tObject.assign(this, { row, doc, columns, serial_number, events });\n\t\tthis.make_row();\n\t}\n\n\tmake_row() {\n\t\t// Add Checkboxes\n\t\tlet cell = this.row.insertCell();\n\n\t\tthis.checkbox = document.createElement(\"input\");\n\t\tthis.checkbox.type = \"checkbox\";\n\t\tthis.checkbox.onclick = event => {\n\t\t\tthis.toggle_select(event.target.checked);\n\t\t\tevent.stopImmediatePropagation();\n\t\t}\n\n\t\tcell.appendChild(this.checkbox);\n\n\t\t// Add Serial Number\n\t\tlet serialNo = this.row.insertCell();\n\t\tserialNo.innerText = this.serial_number;\n\n\t\tthis.columns.forEach(field => {\n\t\t\tlet cell = this.row.insertCell();\n\t\t\tlet formatter = frappe.form.get_formatter(field.fieldtype);\n\t\t\tcell.innerHTML = this.doc[field.fieldname] &&\n\t\t\t\t__(formatter(this.doc[field.fieldname], field, {only_value: 1}, this.doc)) || \"\";\n\t\t});\n\n\t\tthis.row.onclick = () => this.events.onEdit();\n\t\tthis.row.style.cursor = \"pointer\";\n\t}\n\n\ttoggle_select(checked) {\n\t\tthis.checkbox.checked = checked;\n\t\tthis.events.onSelect(checked);\n\t}\n\n\tis_selected() {\n\t\treturn this.checkbox.checked;\n\t}\n};\n","frappe.provide('frappe.utils');\n/**\n * Simple EventEmitterMixin which uses jQuery's event system\n */\nconst EventEmitterMixin = {\n\tinit() {\n\t\tthis.jq = jQuery({});\n\t},\n\n\ttrigger(evt, data) {\n\t\t!this.jq && this.init();\n\t\tthis.jq.trigger(evt, data);\n\t},\n\n\tonce(evt, handler) {\n\t\t!this.jq && this.init();\n\t\tthis.jq.one(evt, (e, data) => handler(data));\n\t},\n\n\ton(evt, handler) {\n\t\t!this.jq && this.init();\n\t\tthis.jq.bind(evt, (e, data) => handler(data));\n\t},\n\n\toff(evt, handler) {\n\t\t!this.jq && this.init();\n\t\tthis.jq.unbind(evt, (e, data) => handler(data));\n\t}\n}\n\nfrappe.utils.make_event_emitter = function(object) {\n\tObject.assign(object, EventEmitterMixin);\n\treturn object;\n};\n\nexport default EventEmitterMixin;\n","frappe.provide(\"frappe.ui\");\nfrappe.provide(\"frappe.web_form\");\nimport EventEmitterMixin from '../../frappe/event_emitter';\n\nexport default class WebForm extends frappe.ui.FieldGroup {\n\tconstructor(opts) {\n\t\tsuper();\n\t\tObject.assign(this, opts);\n\t\tfrappe.web_form = this;\n\t\tfrappe.web_form.events = {};\n\t\tObject.assign(frappe.web_form.events, EventEmitterMixin);\n\t\tthis.current_section = 0;\n\t}\n\n\tprepare(web_form_doc, doc) {\n\t\tObject.assign(this, web_form_doc);\n\t\tthis.fields = web_form_doc.web_form_fields;\n\t\tthis.doc = doc;\n\t}\n\n\tmake() {\n\t\tsuper.make();\n\t\tthis.set_sections();\n\t\tthis.set_field_values();\n\t\tthis.setup_listeners();\n\t\tif (this.introduction_text) this.set_form_description(this.introduction_text);\n\t\tif (this.allow_print && !this.is_new) this.setup_print_button();\n\t\tif (this.allow_delete && !this.is_new) this.setup_delete_button();\n\t\tif (this.is_new) this.setup_cancel_button();\n\t\tthis.setup_primary_action();\n\t\tthis.setup_previous_next_button();\n\t\tthis.toggle_section();\n\t\t$(\".link-btn\").remove();\n\n\t\t// webform client script\n\t\tfrappe.init_client_script && frappe.init_client_script();\n\t\tfrappe.web_form.events.trigger('after_load');\n\t\tthis.after_load && this.after_load();\n\t}\n\n\ton(fieldname, handler) {\n\t\tlet field = this.fields_dict[fieldname];\n\t\tfield.df.change = () => {\n\t\t\thandler(field, field.value);\n\t\t};\n\t}\n\n\tsetup_listeners() {\n\t\t// Event listener for triggering Save/Next button for Multi Step Forms\n\t\t// Do not use `on` event here since that can be used by user which will render this function useless\n\t\t// setTimeout has 200ms delay so that all the base_control triggers for the fields have been run\n\t\tlet me = this;\n\n\t\tif (!me.is_multi_step_form) {\n\t\t\treturn;\n\t\t}\n\n\t\tfor (let field of $(\".input-with-feedback\")) {\n\t\t\t$(field).change((e) => {\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\te.stopPropagation();\n\t\t\t\t\tme.toggle_buttons();\n\t\t\t\t}, 200);\n\t\t\t});\n\t\t}\n\t}\n\n\tset_sections() {\n\t\tif (this.sections.length) return;\n\n\t\tthis.sections = $(`.form-section`);\n\t}\n\n\tsetup_previous_next_button() {\n\t\tlet me = this;\n\n\t\tif (!me.is_multi_step_form) {\n\t\t\treturn;\n\t\t}\n\n\t\t$('.web-form-footer').after(`\n\t\t\t
\n\t\t`);\n\n\t\t$('.btn-previous').on('click', function () {\n\t\t\tlet is_validated = me.validate_section();\n\n\t\t\tif (!is_validated) return;\n\n\t\t\t/**\n\t\t\t\tThe eslint utility cannot figure out if this is an infinite loop in backwards and\n\t\t\t\tthrows an error. Disabling for-direction just for this section.\n\t\t\t\tfor-direction doesnt throw an error if the values are hardcoded in the\n\t\t\t\treverse for-loop, but in this case its a dynamic loop.\n\t\t\t\thttps://eslint.org/docs/rules/for-direction\n\t\t\t*/\n\t\t\t/* eslint-disable for-direction */\n\t\t\tfor (let idx = me.current_section; idx < me.sections.length; idx--) {\n\t\t\t\tlet is_empty = me.is_previous_section_empty(idx);\n\t\t\t\tme.current_section = me.current_section > 0 ? me.current_section - 1 : me.current_section;\n\n\t\t\t\tif (!is_empty) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\t/* eslint-enable for-direction */\n\t\t\tme.toggle_section();\n\t\t});\n\n\t\t$('.btn-next').on('click', function () {\n\t\t\tlet is_validated = me.validate_section();\n\n\t\t\tif (!is_validated) return;\n\n\t\t\tfor (let idx = me.current_section; idx < me.sections.length; idx++) {\n\t\t\t\tlet is_empty = me.is_next_section_empty(idx);\n\t\t\t\tme.current_section = me.current_section < me.sections.length ? me.current_section + 1 : me.current_section;\n\n\t\t\t\tif (!is_empty) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tme.toggle_section();\n\t\t});\n\t}\n\n\tset_field_values() {\n\t\tif (this.doc.name) this.set_values(this.doc);\n\t\telse return;\n\t}\n\n\tset_default_values() {\n\t\tlet values = frappe.utils.get_query_params();\n\t\tdelete values.new;\n\t\tthis.set_values(values);\n\t}\n\n\tset_form_description(intro) {\n\t\tlet intro_wrapper = document.getElementById('introduction');\n\t\tintro_wrapper.innerHTML = intro;\n\t}\n\n\tadd_button(name, type, action, wrapper_class=\".web-form-actions\") {\n\t\tconst button = document.createElement(\"button\");\n\t\tbutton.classList.add(\"btn\", \"btn-\" + type, \"btn-sm\", \"ml-2\");\n\t\tbutton.innerHTML = name;\n\t\tbutton.onclick = action;\n\t\tdocument.querySelector(wrapper_class).appendChild(button);\n\t}\n\n\tadd_button_to_footer(name, type, action) {\n\t\tthis.add_button(name, type, action, '.web-form-footer');\n\t}\n\n\tadd_button_to_header(name, type, action) {\n\t\tthis.add_button(name, type, action, '.web-form-actions');\n\t}\n\n\tsetup_primary_action() {\n\t\tthis.add_button_to_header(this.button_label || __(\"Save\", null, \"Button in web form\"), \"primary\", () =>\n\t\t\tthis.save()\n\t\t);\n\n\t\tthis.add_button_to_footer(this.button_label || __(\"Save\", null, \"Button in web form\"), \"primary\", () =>\n\t\t\tthis.save()\n\t\t);\n\t}\n\n\tsetup_cancel_button() {\n\t\tthis.add_button_to_header(__(\"Cancel\", null, \"Button in web form\"), \"light\", () => this.cancel());\n\t}\n\n\tsetup_delete_button() {\n\t\tfrappe.has_permission(this.doc_type, \"\", \"delete\", () => {\n\t\t\tthis.add_button_to_header(\n\t\t\t\tfrappe.utils.icon('delete'),\n\t\t\t\t\"danger\",\n\t\t\t\t() => this.delete()\n\t\t\t);\n\t\t});\n\t}\n\n\tsetup_print_button() {\n\t\tthis.add_button_to_header(\n\t\t\tfrappe.utils.icon('print'),\n\t\t\t\"light\",\n\t\t\t() => this.print()\n\t\t);\n\t}\n\n\tvalidate_section() {\n\t\tif (this.allow_incomplete) return true;\n\n\t\tlet fields = $(`.form-section:eq(${this.current_section}) .form-control`);\n\t\tlet errors = [];\n\t\tlet invalid_values = [];\n\n\t\tfor (let field of fields) {\n\t\t\tlet fieldname = $(field).attr(\"data-fieldname\");\n\t\t\tif (!fieldname) continue;\n\n\t\t\tfield = this.fields_dict[fieldname];\n\n\t\t\tif (field.get_value) {\n\t\t\t\tlet value = field.get_value();\n\t\t\t\tif (field.df.reqd && is_null(typeof value === 'string' ? strip_html(value) : value)) errors.push(__(field.df.label));\n\n\t\t\t\tif (field.df.reqd && field.df.fieldtype === 'Text Editor' && is_null(strip_html(cstr(value)))) errors.push(__(field.df.label));\n\n\t\t\t\tif (field.df.invalid) invalid_values.push(__(field.df.label));\n\t\t\t}\n\t\t}\n\n\t\tlet message = '';\n\t\tif (invalid_values.length) {\n\t\t\tmessage += __('Invalid values for fields:') + '