diff --git a/cypress/integration/form_builder.js b/cypress/integration/form_builder.js index 53c45cd379..10a41820f9 100644 --- a/cypress/integration/form_builder.js +++ b/cypress/integration/form_builder.js @@ -56,12 +56,18 @@ context("Form Builder", () => { cy.visit(`/app/doctype/${doctype_name}`); cy.findByRole("tab", { name: "Form" }).click(); - let first_field = - ".tab-content.active .section-columns-container:first .column:first .field:first"; + let first_column = ".tab-content.active .section-columns-container:first .column:first"; - cy.get(".fields-container .field[title='Table']").drag(first_field, { - target: { x: 100, y: 10 }, - }); + let first_field = first_column + " .field:first"; + let last_field = first_column + " .field:last"; + + let add_new_field_btn = first_field + " .field-actions .add-field-btn"; + + // add new field + cy.get(add_new_field_btn).click(); + + // type table and press enter + cy.get(".combo-box-options:first .search-box > input").type("table{enter}"); // save cy.click_doc_primary_button("Save"); @@ -70,7 +76,7 @@ context("Form Builder", () => { cy.get_open_dialog().find(".msgprint").should("contain", "Options is required"); cy.hide_dialog(); - cy.get(first_field).click({ force: true }); + cy.get(last_field).click({ force: true }); cy.get(".sidebar-container .frappe-control[data-fieldname='options'] input") .click() @@ -78,13 +84,10 @@ context("Form Builder", () => { cy.get("@input").clear({ force: true }).type("Web Form Field", { delay: 200 }); cy.wait("@search_link"); - cy.get(first_field).click({ force: true }); + cy.get(last_field).click({ force: true }); - cy.get(first_field) - .find(".table-controls .table-column") - .contains("Field") - .should("exist"); - cy.get(first_field) + cy.get(last_field).find(".table-controls .table-column").contains("Field").should("exist"); + cy.get(last_field) .find(".table-controls .table-column") .contains("Fieldtype") .should("exist"); @@ -98,7 +101,7 @@ context("Form Builder", () => { cy.get_open_dialog().find(".msgprint").should("contain", "In List View"); cy.hide_dialog(); - cy.get(first_field).click({ force: true }); + cy.get(last_field).click({ force: true }); cy.get(".sidebar-container .field label .label-area").contains("In List View").click(); // validate In Global Search @@ -188,7 +191,7 @@ context("Form Builder", () => { // add new column cy.get(first_section).find(".column:first").click(15, 10); cy.get(first_section).find(".column:first .column-actions button:first").click(); - cy.get(first_section).find(".column").should("have.length", 3); + cy.get(first_section).find(".column").should("have.length", 2); }); it("Remove Tab/Section/Column", () => { @@ -197,7 +200,7 @@ context("Form Builder", () => { // remove column cy.get(first_section).find(".column:first").click(15, 10); cy.get(first_section).find(".column:first .column-actions button:last").click(); - cy.get(first_section).find(".column").should("have.length", 2); + cy.get(first_section).find(".column").should("have.length", 1); // remove section cy.get(first_section).click(15, 10); @@ -205,7 +208,7 @@ context("Form Builder", () => { cy.get(".tab-content.active .form-section-container").should("have.length", 1); // remove tab - cy.get(".tab-header").realHover().find(".tab-actions .remove-tab-btn").click(); + cy.get(".tab-header .tab:last").realHover().find(".remove-tab-btn").click(); cy.get(".tab-header .tabs .tab").should("have.length", 2); }); @@ -231,14 +234,20 @@ context("Form Builder", () => { cy.visit(`/app/doctype/${doctype_name}`); cy.findByRole("tab", { name: "Form" }).click(); - let first_field = - ".tab-content.active .section-columns-container:first .column:first .field:first"; + let first_column = ".tab-content.active .section-columns-container:first .column:first"; - cy.get(".fields-container .field[title='Data']").drag(first_field, { - target: { x: 100, y: 10 }, - }); + let first_field = first_column + " .field:first"; + let last_field = first_column + " .field:last"; - cy.get(first_field).click(); + let add_new_field_btn = first_field + " .field-actions .add-field-btn"; + + // add new field + cy.get(add_new_field_btn).click(); + + // type data and press enter + cy.get(".combo-box-options:first .search-box > input").type("data{enter}"); + + cy.get(last_field).click(); // validate duplicate name cy.get(".sidebar-container .frappe-control[data-fieldname='fieldname'] input") @@ -251,7 +260,7 @@ context("Form Builder", () => { cy.click_doc_primary_button("Save"); cy.get_open_dialog().find(".msgprint").should("contain", "appears multiple times"); cy.hide_dialog(); - cy.get(first_field).click(); + cy.get(last_field).click(); cy.get(".sidebar-container .frappe-control[data-fieldname='fieldname'] input").clear({ force: true, }); diff --git a/frappe/core/doctype/doctype/doctype.js b/frappe/core/doctype/doctype/doctype.js index 929244c977..c21654a109 100644 --- a/frappe/core/doctype/doctype/doctype.js +++ b/frappe/core/doctype/doctype/doctype.js @@ -2,6 +2,12 @@ // MIT License. See license.txt frappe.ui.form.on("DocType", { + onload: function (frm) { + if (frm.is_new()) { + frappe.listview_settings["DocType"].new_doctype_dialog(); + } + }, + before_save: function (frm) { let form_builder = frappe.form_builder; if (form_builder?.store) { @@ -13,6 +19,7 @@ frappe.ui.form.on("DocType", { } } }, + after_save: function (frm) { if ( frappe.form_builder && @@ -22,6 +29,7 @@ frappe.ui.form.on("DocType", { frappe.form_builder.store.fetch(); } }, + refresh: function (frm) { frm.set_query("role", "permissions", function (doc) { if (doc.custom && frappe.session.user != "Administrator") { @@ -119,6 +127,20 @@ frappe.ui.form.on("DocType", { setup_default_views: (frm) => { frappe.model.set_default_views_for_doctype(frm.doc.name, frm); }, + + on_tab_change: (frm) => { + let current_tab = frm.get_active_tab().label; + + if (current_tab === "Form") { + frm.footer.wrapper.hide(); + frm.form_wrapper.find(".form-message").hide(); + frm.form_wrapper.addClass("mb-1"); + } else { + frm.footer.wrapper.show(); + frm.form_wrapper.find(".form-message").show(); + frm.form_wrapper.removeClass("mb-1"); + } + }, }); frappe.ui.form.on("DocField", { diff --git a/frappe/core/doctype/doctype/doctype.json b/frappe/core/doctype/doctype/doctype.json index 21d5fbfac8..082471da7d 100644 --- a/frappe/core/doctype/doctype/doctype.json +++ b/frappe/core/doctype/doctype/doctype.json @@ -8,6 +8,9 @@ "document_type": "Document", "engine": "InnoDB", "field_order": [ + "form_builder_tab", + "form_builder", + "settings_tab", "sb0", "module", "is_submittable", @@ -32,32 +35,6 @@ "column_break_15", "description", "documentation", - "sb2", - "permissions", - "restrict_to_domain", - "read_only", - "in_create", - "actions_section", - "actions", - "links_section", - "links", - "document_states_section", - "states", - "web_view", - "has_web_view", - "allow_guest_to_view", - "index_web_pages_for_search", - "route", - "is_published_field", - "website_search_field", - "advanced", - "engine", - "migration_hash", - "form_builder_tab", - "form_builder", - "fields_section", - "fields", - "settings_tab", "form_settings_section", "image_field", "timeline_field", @@ -92,6 +69,29 @@ "email_append_to", "sender_field", "subject_field", + "sb2", + "permissions", + "restrict_to_domain", + "read_only", + "in_create", + "actions_section", + "actions", + "links_section", + "links", + "document_states_section", + "states", + "web_view", + "has_web_view", + "allow_guest_to_view", + "index_web_pages_for_search", + "route", + "is_published_field", + "website_search_field", + "advanced", + "engine", + "migration_hash", + "fields_section", + "fields", "connections_tab" ], "fields": [ @@ -640,6 +640,7 @@ "label": "Settings" }, { + "depends_on": "eval:!doc.__islocal", "fieldname": "form_builder_tab", "fieldtype": "Tab Break", "label": "Form" @@ -742,7 +743,7 @@ "link_fieldname": "reference_doctype" } ], - "modified": "2023-08-29 12:27:06.587523", + "modified": "2023-11-01 16:45:14.960949", "modified_by": "Administrator", "module": "Core", "name": "DocType", diff --git a/frappe/core/doctype/doctype/doctype_list.js b/frappe/core/doctype/doctype/doctype_list.js new file mode 100644 index 0000000000..963e863380 --- /dev/null +++ b/frappe/core/doctype/doctype/doctype_list.js @@ -0,0 +1,122 @@ +frappe.listview_settings["DocType"] = { + primary_action: function () { + this.new_doctype_dialog(); + }, + + new_doctype_dialog() { + let non_developer = frappe.session.user !== "Administrator" || !frappe.boot.developer_mode; + let fields = [ + { + label: __("DocType Name"), + fieldname: "name", + fieldtype: "Data", + reqd: 1, + }, + { fieldtype: "Column Break" }, + { + label: __("Module"), + fieldname: "module", + fieldtype: "Link", + options: "Module Def", + reqd: 1, + }, + { fieldtype: "Section Break" }, + { + label: __("Is Submittable"), + fieldname: "is_submittable", + fieldtype: "Check", + description: __( + "Once submitted, submittable documents cannot be changed. They can only be Cancelled and Amended." + ), + depends_on: "eval:!doc.istable && !doc.issingle", + }, + { + label: __("Is Child Table"), + fieldname: "istable", + fieldtype: "Check", + description: __("Child Tables are shown as a Grid in other DocTypes"), + depends_on: "eval:!doc.is_submittable && !doc.issingle", + }, + { + label: __("Editable Grid"), + fieldname: "editable_grid", + fieldtype: "Check", + depends_on: "istable", + default: 1, + }, + { + label: __("Is Single"), + fieldname: "issingle", + fieldtype: "Check", + description: __( + "Single Types have only one record no tables associated. Values are stored in tabSingles" + ), + depends_on: "eval:!doc.istable && !doc.is_submittable", + }, + { + label: "Is Tree", + fieldname: "is_tree", + fieldtype: "Check", + default: "0", + depends_on: "eval:!doc.istable", + description: "Tree structures are implemented using Nested Set", + }, + { + label: __("Custom?"), + fieldname: "custom", + fieldtype: "Check", + default: non_developer, + read_only: non_developer, + }, + ]; + + if (!non_developer) { + fields.push({ + label: "Is Virtual", + fieldname: "is_virtual", + fieldtype: "Check", + default: "0", + }); + } + + let new_d = new frappe.ui.Dialog({ + title: __("Create New DocType"), + fields: fields, + primary_action_label: __("Create & Continue"), + primary_action(values) { + if (!values.istable) values.editable_grid = 0; + frappe.db + .insert({ + doctype: "DocType", + ...values, + permissions: [ + { + create: 1, + delete: 1, + email: 1, + export: 1, + print: 1, + read: 1, + report: 1, + role: "System Manager", + share: 1, + write: 1, + }, + ], + fields: [{ fieldtype: "Section Break" }], + }) + .then((doc) => { + frappe.set_route("Form", "DocType", doc.name); + }); + }, + secondary_action_label: __("Cancel"), + secondary_action() { + new_d.hide(); + if (frappe.get_route()[0] === "Form") { + frappe.set_route("List", "DocType"); + } + }, + }); + new_d.show(); + }, +}; diff --git a/frappe/custom/doctype/customize_form/customize_form.json b/frappe/custom/doctype/customize_form/customize_form.json index 8d4d194338..32fd3302ec 100644 --- a/frappe/custom/doctype/customize_form/customize_form.json +++ b/frappe/custom/doctype/customize_form/customize_form.json @@ -180,6 +180,7 @@ "depends_on": "doc_type", "fieldname": "fields_section_break", "fieldtype": "Section Break", + "hidden": 1, "label": "Fields" }, { @@ -393,7 +394,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-08-29 12:31:55.808848", + "modified": "2023-10-31 02:04:25.955931", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form", diff --git a/frappe/public/js/form_builder/FormBuilder.vue b/frappe/public/js/form_builder/FormBuilder.vue index 427b93584b..ff9cb9bdd4 100644 --- a/frappe/public/js/form_builder/FormBuilder.vue +++ b/frappe/public/js/form_builder/FormBuilder.vue @@ -30,22 +30,23 @@ onMounted(() => store.fetch()); class="form-builder-container" @click="store.form.selected_field = null" > -
-
- -
-
+
+
+ +
+
+
diff --git a/frappe/public/js/form_builder/components/AddFieldButton.vue b/frappe/public/js/form_builder/components/AddFieldButton.vue new file mode 100644 index 0000000000..885ccc77d8 --- /dev/null +++ b/frappe/public/js/form_builder/components/AddFieldButton.vue @@ -0,0 +1,135 @@ + + + + + diff --git a/frappe/public/js/form_builder/components/Autocomplete.vue b/frappe/public/js/form_builder/components/Autocomplete.vue new file mode 100644 index 0000000000..2ded948e27 --- /dev/null +++ b/frappe/public/js/form_builder/components/Autocomplete.vue @@ -0,0 +1,159 @@ + + + + + diff --git a/frappe/public/js/form_builder/components/Column.vue b/frappe/public/js/form_builder/components/Column.vue index 54dd49d5bf..10afe3f495 100644 --- a/frappe/public/js/form_builder/components/Column.vue +++ b/frappe/public/js/form_builder/components/Column.vue @@ -1,15 +1,26 @@