Merge pull request #21377 from shariquerik/move-form-builder-in-doctype-form
This commit is contained in:
commit
d423dedcd3
23 changed files with 360 additions and 671 deletions
|
|
@ -9,38 +9,23 @@ context("Form Builder", () => {
|
|||
|
||||
it("Open Form Builder for Web Form Doctype/Customize Form", () => {
|
||||
// doctype
|
||||
cy.visit("/app/form-builder/Web Form");
|
||||
cy.visit("/app/doctype/Web Form");
|
||||
cy.findByRole("tab", { name: "Form" }).click();
|
||||
cy.get(".form-builder-container").should("exist");
|
||||
|
||||
// customize form
|
||||
cy.visit("/app/form-builder/Web Form/customize");
|
||||
cy.visit("/app/customize-form?doc_type=Web%20Form");
|
||||
cy.findByRole("tab", { name: "Form" }).click();
|
||||
cy.get(".form-builder-container").should("exist");
|
||||
});
|
||||
|
||||
it("Change Doctype using page title dialog", () => {
|
||||
cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
|
||||
|
||||
cy.visit(`/app/form-builder/Web Form`);
|
||||
cy.get(".form-builder-container").should("exist");
|
||||
|
||||
cy.get(".page-title").click();
|
||||
|
||||
cy.get(".frappe-control[data-fieldname='doctype'] input").click().as("input");
|
||||
cy.get("@input").type("{rightArrow}Web Form Field", { delay: 200 });
|
||||
cy.wait("@search_link");
|
||||
cy.get("@input").type("{enter}").blur();
|
||||
|
||||
cy.click_modal_primary_button("Edit");
|
||||
|
||||
cy.get(".page-title .title-text").should("have.text", "Web Form Field");
|
||||
});
|
||||
|
||||
it("Save without change, check form dirty and reset changes", () => {
|
||||
cy.visit(`/app/form-builder/${doctype_name}`);
|
||||
it("Save without change, check form dirty", () => {
|
||||
cy.visit(`/app/doctype/${doctype_name}`);
|
||||
cy.findByRole("tab", { name: "Form" }).click();
|
||||
|
||||
// Save without change
|
||||
cy.click_doc_primary_button("Save");
|
||||
cy.get(".desk-alert.orange .alert-message").should("have.text", "No changes to save");
|
||||
cy.get(".desk-alert.orange .alert-message").should("have.text", "No changes in document");
|
||||
|
||||
// Check form dirty
|
||||
cy.get(".tab-content.active .section-columns-container:first .column:first .field:first")
|
||||
|
|
@ -48,14 +33,11 @@ context("Form Builder", () => {
|
|||
.dblclick()
|
||||
.type("Dirty");
|
||||
cy.get(".title-area .indicator-pill.orange").should("have.text", "Not Saved");
|
||||
|
||||
// Reset changes
|
||||
cy.get(".page-actions .custom-actions .btn").contains("Reset Changes").click();
|
||||
cy.get(".title-area .indicator-pill.orange").should("not.exist");
|
||||
});
|
||||
|
||||
it("Add empty section and save", () => {
|
||||
cy.visit(`/app/form-builder/${doctype_name}`);
|
||||
cy.visit(`/app/doctype/${doctype_name}`);
|
||||
cy.findByRole("tab", { name: "Form" }).click();
|
||||
|
||||
let first_section = ".tab-content.active .form-section-container:first";
|
||||
|
||||
|
|
@ -71,7 +53,8 @@ context("Form Builder", () => {
|
|||
it("Add Table field and check if columns are rendered", () => {
|
||||
cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
|
||||
|
||||
cy.visit(`/app/form-builder/${doctype_name}`);
|
||||
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";
|
||||
|
|
@ -126,20 +109,23 @@ context("Form Builder", () => {
|
|||
});
|
||||
|
||||
it("Drag Field/Column/Section & Tab", () => {
|
||||
cy.visit(`/app/form-builder/${doctype_name}`);
|
||||
cy.visit(`/app/doctype/${doctype_name}`);
|
||||
cy.findByRole("tab", { name: "Form" }).click();
|
||||
|
||||
let first_column = ".tab-content.active .section-columns-container:first .column:first";
|
||||
let first_field = first_column + " .field:first";
|
||||
let label = "div[title='Double click to edit label'] span:first";
|
||||
|
||||
cy.get(".tab-header .tabs .tab:first").click();
|
||||
|
||||
// drag first tab to second position
|
||||
cy.get(".tabs .tab:first").drag(".tabs .tab:nth-child(2)", {
|
||||
cy.get(".tab-header .tabs .tab:first").drag(".tab-header .tabs .tab:nth-child(2)", {
|
||||
target: { x: 10, y: 10 },
|
||||
force: true,
|
||||
});
|
||||
cy.get(".tabs .tab:first").find(label).should("have.text", "Tab 2");
|
||||
cy.get(".tab-header .tabs .tab:first").find(label).should("have.text", "Tab 2");
|
||||
|
||||
cy.get(".tabs .tab:first").click();
|
||||
cy.get(".tab-header .tabs .tab:first").click();
|
||||
cy.get(".sidebar-container .tab:first").click();
|
||||
|
||||
// drag check field to first column
|
||||
|
|
@ -151,7 +137,7 @@ context("Form Builder", () => {
|
|||
cy.get(first_field)
|
||||
.find("div[title='Double click to edit label']")
|
||||
.dblclick()
|
||||
.type("Test Check{enter}");
|
||||
.type("Test Check");
|
||||
cy.get(first_field).find(label).should("have.text", "Test Check");
|
||||
|
||||
// drag the first field to second position
|
||||
|
|
@ -184,13 +170,14 @@ context("Form Builder", () => {
|
|||
});
|
||||
|
||||
it("Add New Tab/Section/Column to Form", () => {
|
||||
cy.visit(`/app/form-builder/${doctype_name}`);
|
||||
cy.visit(`/app/doctype/${doctype_name}`);
|
||||
cy.findByRole("tab", { name: "Form" }).click();
|
||||
|
||||
let first_section = ".tab-content.active .form-section-container:first";
|
||||
|
||||
// add new tab
|
||||
cy.get(".tab-header").realHover().find(".tab-actions .new-tab-btn").click();
|
||||
cy.get(".tabs .tab").should("have.length", 3);
|
||||
cy.get(".tab-header .tabs .tab").should("have.length", 3);
|
||||
|
||||
// add new section
|
||||
cy.get(first_section).click(15, 10);
|
||||
|
|
@ -218,11 +205,12 @@ context("Form Builder", () => {
|
|||
|
||||
// remove tab
|
||||
cy.get(".tab-header").realHover().find(".tab-actions .remove-tab-btn").click();
|
||||
cy.get(".tabs .tab").should("have.length", 2);
|
||||
cy.get(".tab-header .tabs .tab").should("have.length", 2);
|
||||
});
|
||||
|
||||
it("Update Title field Label to New Title through Customize Form", () => {
|
||||
cy.visit(`/app/form-builder/${doctype_name}`);
|
||||
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";
|
||||
|
|
@ -239,7 +227,8 @@ context("Form Builder", () => {
|
|||
});
|
||||
|
||||
it("Validate Duplicate Name & reqd + hidden without default logic", () => {
|
||||
cy.visit(`/app/form-builder/${doctype_name}`);
|
||||
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";
|
||||
|
|
@ -275,10 +264,11 @@ context("Form Builder", () => {
|
|||
});
|
||||
|
||||
it("Undo/Redo", () => {
|
||||
cy.visit(`/app/form-builder/${doctype_name}`);
|
||||
cy.visit(`/app/doctype/${doctype_name}`);
|
||||
cy.findByRole("tab", { name: "Form" }).click();
|
||||
|
||||
// click on second tab
|
||||
cy.get(".tabs .tab:last").click();
|
||||
cy.get(".tab-header .tabs .tab:last").click();
|
||||
|
||||
let first_column = ".tab-content.active .section-columns-container:first .column:first";
|
||||
let first_field = first_column + " .field:first";
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ context("Grid Configuration", () => {
|
|||
cy.visit("/app/doctype/User");
|
||||
});
|
||||
it("Set user wise grid settings", () => {
|
||||
cy.findByRole("tab", { name: "Form" }).click();
|
||||
cy.get('.form-section[data-fieldname="fields_section"]').click();
|
||||
cy.wait(100);
|
||||
cy.get('.frappe-control[data-fieldname="fields"]').as("table");
|
||||
cy.get("@table").find(".icon-sm").click();
|
||||
|
|
|
|||
|
|
@ -27,60 +27,70 @@ context("Sidebar", () => {
|
|||
});
|
||||
|
||||
it("Verify attachment visibility config", () => {
|
||||
verify_attachment_visibility("doctype/Blog Post", true);
|
||||
cy.call("frappe.tests.ui_test_helpers.create_todo", {
|
||||
description: "Sidebar Attachment ToDo",
|
||||
}).then((todo) => {
|
||||
verify_attachment_visibility(`todo/${todo.message.name}`, true);
|
||||
});
|
||||
verify_attachment_visibility("blog-post/test-blog-attachment-post", false);
|
||||
});
|
||||
|
||||
it('Test for checking "Assigned To" counter value, adding filter and adding & removing an assignment', () => {
|
||||
cy.visit("/app/doctype");
|
||||
cy.click_sidebar_button("Assigned To");
|
||||
cy.call("frappe.tests.ui_test_helpers.create_todo", {
|
||||
description: "Sidebar Attachment ToDo",
|
||||
}).then((todo) => {
|
||||
let todo_name = todo.message.name;
|
||||
cy.visit("/app/todo");
|
||||
cy.click_sidebar_button("Assigned To");
|
||||
|
||||
//To check if no filter is available in "Assigned To" dropdown
|
||||
cy.get(".empty-state").should("contain", "No filters found");
|
||||
//To check if no filter is available in "Assigned To" dropdown
|
||||
cy.get(".empty-state").should("contain", "No filters found");
|
||||
|
||||
//Assigning a doctype to a user
|
||||
cy.visit("/app/doctype/ToDo");
|
||||
cy.get(".form-assignments > .flex > .text-muted").click();
|
||||
cy.get_field("assign_to_me", "Check").click();
|
||||
cy.get(".modal-footer > .standard-actions > .btn-primary").click();
|
||||
cy.visit("/app/doctype");
|
||||
cy.click_sidebar_button("Assigned To");
|
||||
//Assigning a doctype to a user
|
||||
cy.visit(`/app/todo/${todo_name}`);
|
||||
cy.get(".form-assignments > .flex > .text-muted").click();
|
||||
cy.get_field("assign_to_me", "Check").click();
|
||||
cy.get(".modal-footer > .standard-actions > .btn-primary").click();
|
||||
cy.visit("/app/todo");
|
||||
cy.click_sidebar_button("Assigned To");
|
||||
|
||||
//To check if filter is added in "Assigned To" dropdown after assignment
|
||||
cy.get(".group-by-field.show > .dropdown-menu > .group-by-item > .dropdown-item").should(
|
||||
"contain",
|
||||
"1"
|
||||
);
|
||||
//To check if filter is added in "Assigned To" dropdown after assignment
|
||||
cy.get(
|
||||
".group-by-field.show > .dropdown-menu > .group-by-item > .dropdown-item"
|
||||
).should("contain", "1");
|
||||
|
||||
//To check if there is no filter added to the listview
|
||||
cy.get(".filter-button").should("contain", "Filter");
|
||||
//To check if there is no filter added to the listview
|
||||
cy.get(".filter-button").should("contain", "Filter");
|
||||
|
||||
//To add a filter to display data into the listview
|
||||
cy.get(".group-by-field.show > .dropdown-menu > .group-by-item > .dropdown-item").click();
|
||||
//To add a filter to display data into the listview
|
||||
cy.get(
|
||||
".group-by-field.show > .dropdown-menu > .group-by-item > .dropdown-item"
|
||||
).click();
|
||||
|
||||
//To check if filter is applied
|
||||
cy.click_filter_button().should("contain", "1 filter");
|
||||
cy.get(".fieldname-select-area > .awesomplete > .form-control").should(
|
||||
"have.value",
|
||||
"Assigned To"
|
||||
);
|
||||
cy.get(".condition").should("have.value", "like");
|
||||
cy.get(".filter-field > .form-group > .input-with-feedback").should(
|
||||
"have.value",
|
||||
`%${cy.config("testUser")}%`
|
||||
);
|
||||
cy.click_filter_button();
|
||||
//To check if filter is applied
|
||||
cy.click_filter_button().should("contain", "1 filter");
|
||||
cy.get(".fieldname-select-area > .awesomplete > .form-control").should(
|
||||
"have.value",
|
||||
"Assigned To"
|
||||
);
|
||||
cy.get(".condition").should("have.value", "like");
|
||||
cy.get(".filter-field > .form-group > .input-with-feedback").should(
|
||||
"have.value",
|
||||
`%${cy.config("testUser")}%`
|
||||
);
|
||||
cy.click_filter_button();
|
||||
|
||||
//To remove the applied filter
|
||||
cy.clear_filters();
|
||||
//To remove the applied filter
|
||||
cy.clear_filters();
|
||||
|
||||
//To remove the assignment
|
||||
cy.visit("/app/doctype/ToDo");
|
||||
cy.get(".assignments > .avatar-group > .avatar > .avatar-frame").click();
|
||||
cy.get(".remove-btn").click({ force: true });
|
||||
cy.hide_dialog();
|
||||
cy.visit("/app/doctype");
|
||||
cy.click_sidebar_button("Assigned To");
|
||||
cy.get(".empty-state").should("contain", "No filters found");
|
||||
//To remove the assignment
|
||||
cy.visit(`/app/todo/${todo_name}`);
|
||||
cy.get(".assignments > .avatar-group > .avatar > .avatar-frame").click();
|
||||
cy.get(".remove-btn").click({ force: true });
|
||||
cy.hide_dialog();
|
||||
cy.visit("/app/todo");
|
||||
cy.click_sidebar_button("Assigned To");
|
||||
cy.get(".empty-state").should("contain", "No filters found");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,6 +2,26 @@
|
|||
// MIT License. See license.txt
|
||||
|
||||
frappe.ui.form.on("DocType", {
|
||||
before_save: function (frm) {
|
||||
let form_builder = frappe.form_builder;
|
||||
if (form_builder?.store) {
|
||||
let fields = form_builder.store.update_fields();
|
||||
|
||||
// if fields is a string, it means there is an error
|
||||
if (typeof fields === "string") {
|
||||
frappe.throw(fields);
|
||||
}
|
||||
}
|
||||
},
|
||||
after_save: function (frm) {
|
||||
if (
|
||||
frappe.form_builder &&
|
||||
frappe.form_builder.doctype === frm.doc.name &&
|
||||
frappe.form_builder.store
|
||||
) {
|
||||
frappe.form_builder.store.fetch();
|
||||
}
|
||||
},
|
||||
refresh: function (frm) {
|
||||
frm.set_query("role", "permissions", function (doc) {
|
||||
if (doc.custom && frappe.session.user != "Administrator") {
|
||||
|
|
@ -21,8 +41,6 @@ frappe.ui.form.on("DocType", {
|
|||
frm.toggle_enable("beta", 0);
|
||||
}
|
||||
|
||||
render_form_builder_message(frm);
|
||||
|
||||
if (!frm.is_new() && !frm.doc.istable) {
|
||||
if (frm.doc.issingle) {
|
||||
frm.add_custom_button(__("Go to {0}", [__(frm.doc.name)]), () => {
|
||||
|
|
@ -72,6 +90,8 @@ frappe.ui.form.on("DocType", {
|
|||
frm.cscript.autoname(frm);
|
||||
frm.cscript.set_naming_rule_description(frm);
|
||||
frm.trigger("setup_default_views");
|
||||
|
||||
render_form_builder(frm);
|
||||
},
|
||||
|
||||
istable: (frm) => {
|
||||
|
|
@ -142,4 +162,30 @@ function render_form_builder_message(frm) {
|
|||
}
|
||||
}
|
||||
|
||||
function render_form_builder(frm) {
|
||||
if (frappe.form_builder && frappe.form_builder.doctype === frm.doc.name) {
|
||||
frappe.form_builder.setup_page_actions();
|
||||
frappe.form_builder.store.fetch();
|
||||
return;
|
||||
}
|
||||
|
||||
if (frappe.form_builder) {
|
||||
frappe.form_builder.wrapper = $(frm.fields_dict["form_builder"].wrapper);
|
||||
frappe.form_builder.frm = frm;
|
||||
frappe.form_builder.doctype = frm.doc.name;
|
||||
frappe.form_builder.customize = false;
|
||||
frappe.form_builder.init(true);
|
||||
frappe.form_builder.store.fetch();
|
||||
} else {
|
||||
frappe.require("form_builder.bundle.js").then(() => {
|
||||
frappe.form_builder = new frappe.ui.FormBuilder({
|
||||
wrapper: $(frm.fields_dict["form_builder"].wrapper),
|
||||
frm: frm,
|
||||
doctype: frm.doc.name,
|
||||
customize: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extend_cscript(cur_frm.cscript, new frappe.model.DocTypeController({ frm: cur_frm }));
|
||||
|
|
|
|||
|
|
@ -25,9 +25,6 @@
|
|||
"beta",
|
||||
"is_virtual",
|
||||
"queue_in_background",
|
||||
"fields_section_break",
|
||||
"try_form_builder_html",
|
||||
"fields",
|
||||
"sb1",
|
||||
"naming_rule",
|
||||
"autoname",
|
||||
|
|
@ -35,6 +32,32 @@
|
|||
"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",
|
||||
|
|
@ -68,28 +91,7 @@
|
|||
"column_break_51",
|
||||
"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"
|
||||
"subject_field"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -195,12 +197,6 @@
|
|||
"fieldtype": "Check",
|
||||
"label": "Beta"
|
||||
},
|
||||
{
|
||||
"fieldname": "fields_section_break",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Fields",
|
||||
"oldfieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "fields",
|
||||
"fieldtype": "Table",
|
||||
|
|
@ -633,9 +629,25 @@
|
|||
"label": "Is Calendar and Gantt"
|
||||
},
|
||||
{
|
||||
"fieldname": "try_form_builder_html",
|
||||
"fieldname": "settings_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "form_builder_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Form"
|
||||
},
|
||||
{
|
||||
"fieldname": "form_builder",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Try Form Builder HTML"
|
||||
"label": "Form Builder"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "fields_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Fields"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-bolt",
|
||||
|
|
@ -718,7 +730,7 @@
|
|||
"link_fieldname": "reference_doctype"
|
||||
}
|
||||
],
|
||||
"modified": "2023-05-15 14:07:51.526257",
|
||||
"modified": "2023-07-12 13:56:26.185637",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocType",
|
||||
|
|
@ -755,4 +767,4 @@
|
|||
"states": [],
|
||||
"track_changes": 1,
|
||||
"translated_doctype": 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
frappe.listview_settings["DocType"] = {
|
||||
onload: function (me) {
|
||||
me.page.btn_primary.addClass("hidden");
|
||||
this.setup_select_primary_button(me);
|
||||
},
|
||||
|
||||
setup_select_primary_button: function (me) {
|
||||
let actions = [
|
||||
{
|
||||
label: __("Add DocType (Form Builder)"),
|
||||
description: __("Use the form builder to create a new DocType"),
|
||||
action: () => frappe.set_route("form-builder", "new-doctype"),
|
||||
},
|
||||
{
|
||||
label: __("Add DocType"),
|
||||
description: __("Create a new DocType"),
|
||||
action: () => frappe.new_doc("DocType"),
|
||||
},
|
||||
];
|
||||
|
||||
frappe.utils.add_select_group_button(
|
||||
me.page.btn_primary.parent(),
|
||||
actions,
|
||||
"btn-primary",
|
||||
"add"
|
||||
);
|
||||
},
|
||||
};
|
||||
|
|
@ -100,8 +100,6 @@ frappe.ui.form.on("Customize Form", {
|
|||
frm.page.set_title(__("Customize Form - {0}", [frm.doc.doc_type]));
|
||||
frappe.customize_form.set_primary_action(frm);
|
||||
|
||||
render_form_builder_message(frm);
|
||||
|
||||
frm.add_custom_button(
|
||||
__("Go to {0} List", [__(frm.doc.doc_type)]),
|
||||
function () {
|
||||
|
|
@ -149,6 +147,8 @@ frappe.ui.form.on("Customize Form", {
|
|||
["queue_in_background"],
|
||||
frappe.get_meta(frm.doc.doc_type).is_submittable || 0
|
||||
);
|
||||
|
||||
render_form_builder(frm);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -334,37 +334,6 @@ frappe.ui.form.on("DocType State", {
|
|||
},
|
||||
});
|
||||
|
||||
frappe.customize_form.validate_fieldnames = async function (frm) {
|
||||
for (let i = 0; i < frm.doc.fields.length; i++) {
|
||||
let field = frm.doc.fields[i];
|
||||
|
||||
let fieldname = field.label && frappe.model.scrub(field.label).toLowerCase();
|
||||
if (
|
||||
field.label &&
|
||||
!field.fieldname &&
|
||||
in_list(frappe.model.restricted_fields, fieldname)
|
||||
) {
|
||||
let message = __(
|
||||
"For field <b>{0}</b> in row <b>{1}</b>, fieldname <b>{2}</b> is restricted it will be renamed as <b>{2}1</b>. Do you want to continue?",
|
||||
[field.label, field.idx, fieldname]
|
||||
);
|
||||
await pause_to_confirm(message);
|
||||
}
|
||||
}
|
||||
|
||||
function pause_to_confirm(message) {
|
||||
return new Promise((resolve) => {
|
||||
frappe.confirm(
|
||||
message,
|
||||
() => resolve(),
|
||||
() => {
|
||||
frm.page.btn_primary.prop("disabled", false);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
frappe.customize_form.save_customization = function (frm) {
|
||||
if (frm.doc.doc_type) {
|
||||
return frm.call({
|
||||
|
|
@ -383,9 +352,22 @@ frappe.customize_form.save_customization = function (frm) {
|
|||
}
|
||||
};
|
||||
|
||||
frappe.customize_form.update_fields_from_form_builder = function (frm) {
|
||||
let form_builder = frappe.form_builder;
|
||||
if (form_builder?.store) {
|
||||
let fields = form_builder.store.update_fields();
|
||||
|
||||
// if fields is a string, it means there is an error
|
||||
if (typeof fields === "string") {
|
||||
frappe.throw(fields);
|
||||
}
|
||||
frm.refresh_fields();
|
||||
}
|
||||
};
|
||||
|
||||
frappe.customize_form.set_primary_action = function (frm) {
|
||||
frm.page.set_primary_action(__("Update"), async () => {
|
||||
await this.validate_fieldnames(frm);
|
||||
frm.page.set_primary_action(__("Update"), () => {
|
||||
this.update_fields_from_form_builder(frm);
|
||||
this.save_customization(frm);
|
||||
});
|
||||
};
|
||||
|
|
@ -433,30 +415,29 @@ frappe.customize_form.clear_locals_and_refresh = function (frm) {
|
|||
frm.refresh();
|
||||
};
|
||||
|
||||
function render_form_builder_message(frm) {
|
||||
$(frm.fields_dict["try_form_builder_html"].wrapper).empty();
|
||||
if (!frm.is_new() && frm.fields_dict["try_form_builder_html"]) {
|
||||
let title = __("Use Form Builder to visually customize your form layout");
|
||||
let msg = __(
|
||||
"You can drag and drop fields to create your form layout, add tabs, sections and columns to organize your form and update field properties all from one screen."
|
||||
);
|
||||
function render_form_builder(frm) {
|
||||
if (frappe.form_builder && frappe.form_builder.doctype === frm.doc.doc_type) {
|
||||
frappe.form_builder.setup_page_actions();
|
||||
frappe.form_builder.store.fetch();
|
||||
return;
|
||||
}
|
||||
|
||||
let message = `
|
||||
<div class="flex form-message blue p-3">
|
||||
<div class="mr-3"><img style="border-radius: var(--border-radius-md)" width="275" src="/assets/frappe/images/form-builder.gif"></div>
|
||||
<div>
|
||||
<p style="font-size: var(--text-lg)">${title}</p>
|
||||
<p>${msg}</p>
|
||||
<div>
|
||||
<a class="btn btn-primary btn-sm" href="/app/form-builder/${frm.doc.doc_type}/customize">
|
||||
${__("Form Builder")} ${frappe.utils.icon("right", "xs")}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
$(frm.fields_dict["try_form_builder_html"].wrapper).html(message);
|
||||
if (frappe.form_builder) {
|
||||
frappe.form_builder.wrapper = $(frm.fields_dict["form_builder"].wrapper);
|
||||
frappe.form_builder.frm = frm;
|
||||
frappe.form_builder.doctype = frm.doc.doc_type;
|
||||
frappe.form_builder.customize = true;
|
||||
frappe.form_builder.init(true);
|
||||
frappe.form_builder.store.fetch();
|
||||
} else {
|
||||
frappe.require("form_builder.bundle.js").then(() => {
|
||||
frappe.form_builder = new frappe.ui.FormBuilder({
|
||||
wrapper: $(frm.fields_dict["form_builder"].wrapper),
|
||||
frm: frm,
|
||||
doctype: frm.doc.doc_type,
|
||||
customize: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,12 +21,20 @@
|
|||
"allow_auto_repeat",
|
||||
"allow_import",
|
||||
"queue_in_background",
|
||||
"fields_section_break",
|
||||
"try_form_builder_html",
|
||||
"fields",
|
||||
"naming_section",
|
||||
"naming_rule",
|
||||
"autoname",
|
||||
"document_actions_section",
|
||||
"actions",
|
||||
"document_links_section",
|
||||
"links",
|
||||
"document_states_section",
|
||||
"states",
|
||||
"form_tab",
|
||||
"form_builder",
|
||||
"fields_section_break",
|
||||
"fields",
|
||||
"settings_tab",
|
||||
"form_settings_section",
|
||||
"image_field",
|
||||
"max_attachments",
|
||||
|
|
@ -48,12 +56,6 @@
|
|||
"email_append_to",
|
||||
"sender_field",
|
||||
"subject_field",
|
||||
"document_actions_section",
|
||||
"actions",
|
||||
"document_links_section",
|
||||
"links",
|
||||
"document_states_section",
|
||||
"states",
|
||||
"section_break_8",
|
||||
"sort_field",
|
||||
"column_break_10",
|
||||
|
|
@ -174,8 +176,8 @@
|
|||
"options": "ASC\nDESC"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"depends_on": "doc_type",
|
||||
"description": "Customize Label, Print Hide, Default etc.",
|
||||
"fieldname": "fields_section_break",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Fields"
|
||||
|
|
@ -369,9 +371,19 @@
|
|||
"label": "Is Calendar and Gantt"
|
||||
},
|
||||
{
|
||||
"fieldname": "try_form_builder_html",
|
||||
"fieldname": "settings_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "form_builder",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Try Form Builder HTML"
|
||||
"label": "Form Builder"
|
||||
},
|
||||
{
|
||||
"fieldname": "form_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Form"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
|
|
@ -380,7 +392,7 @@
|
|||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-05-15 16:03:19.872532",
|
||||
"modified": "2023-07-16 13:25:46.201184",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Customize Form",
|
||||
|
|
|
|||
|
|
@ -1,211 +0,0 @@
|
|||
frappe.pages["form-builder"].on_page_load = function (wrapper) {
|
||||
frappe.ui.make_app_page({
|
||||
parent: wrapper,
|
||||
title: __("Form Builder"),
|
||||
single_column: true,
|
||||
});
|
||||
|
||||
// hot reload in development
|
||||
if (frappe.boot.developer_mode) {
|
||||
frappe.hot_update = frappe.hot_update || [];
|
||||
frappe.hot_update.push(() => load_form_builder(wrapper));
|
||||
}
|
||||
};
|
||||
|
||||
frappe.pages["form-builder"].on_page_show = function (wrapper) {
|
||||
load_form_builder(wrapper);
|
||||
};
|
||||
|
||||
function load_form_builder(wrapper) {
|
||||
let route = frappe.get_route();
|
||||
route = route.filter((a) => a);
|
||||
|
||||
if (route.length > 1 && route[1] === "new-doctype") {
|
||||
frappe.pages["form-builder"].new_doctype(route[2]);
|
||||
} else if (route.length > 1) {
|
||||
let doctype = route[1];
|
||||
let is_customize_form = route[2] === "customize";
|
||||
|
||||
if (frappe.form_builder?.doctype) {
|
||||
frappe.form_builder.doctype = frappe.form_builder.store.doctype = doctype;
|
||||
frappe.form_builder.customize = frappe.form_builder.store.is_customize_form =
|
||||
is_customize_form;
|
||||
frappe.form_builder.init(true);
|
||||
frappe.form_builder.store.fetch();
|
||||
return;
|
||||
}
|
||||
|
||||
let $parent = $(wrapper).find(".layout-main-section");
|
||||
$parent.empty();
|
||||
|
||||
frappe.require("form_builder.bundle.js").then(() => {
|
||||
frappe.form_builder = new frappe.ui.FormBuilder({
|
||||
wrapper: $parent,
|
||||
page: wrapper.page,
|
||||
doctype: doctype,
|
||||
customize: is_customize_form,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
frappe.pages["form-builder"].select_doctype();
|
||||
}
|
||||
}
|
||||
|
||||
frappe.pages["form-builder"].select_doctype = function () {
|
||||
let d = new frappe.ui.Dialog({
|
||||
title: __("Select DocType"),
|
||||
fields: [
|
||||
{
|
||||
label: __("Select DocType"),
|
||||
fieldname: "doctype",
|
||||
fieldtype: "Link",
|
||||
options: "DocType",
|
||||
only_select: 1,
|
||||
},
|
||||
{
|
||||
label: __("Customize"),
|
||||
fieldname: "customize",
|
||||
fieldtype: "Check",
|
||||
},
|
||||
],
|
||||
primary_action_label: __("Edit"),
|
||||
primary_action({ doctype, customize }) {
|
||||
if (customize) {
|
||||
frappe.model.with_doctype(doctype).then(() => {
|
||||
let meta = frappe.get_meta(doctype);
|
||||
if (in_list(frappe.model.core_doctypes_list, this.doctype))
|
||||
frappe.throw(__("Core DocTypes cannot be customized."));
|
||||
|
||||
if (meta.issingle) frappe.throw(__("Single DocTypes cannot be customized."));
|
||||
|
||||
if (meta.custom)
|
||||
frappe.throw(
|
||||
__(
|
||||
"Only standard DocTypes are allowed to be customized from Customize Form."
|
||||
)
|
||||
);
|
||||
frappe.set_route("form-builder", doctype, "customize");
|
||||
});
|
||||
} else {
|
||||
frappe.set_route("form-builder", doctype);
|
||||
}
|
||||
},
|
||||
secondary_action_label: __("Create New DocType"),
|
||||
secondary_action() {
|
||||
let doctype = d.get_value("doctype") || "";
|
||||
d.hide();
|
||||
frappe.set_route("form-builder", "new-doctype", doctype);
|
||||
},
|
||||
});
|
||||
|
||||
d.show();
|
||||
};
|
||||
|
||||
frappe.pages["form-builder"].new_doctype = function (doctype) {
|
||||
let non_developer = frappe.session.user !== "Administrator" || !frappe.boot.developer_mode;
|
||||
let new_d = new frappe.ui.Dialog({
|
||||
title: __("Create New DocType"),
|
||||
fields: [
|
||||
{
|
||||
label: __("DocType Name"),
|
||||
fieldname: "doctype_name",
|
||||
fieldtype: "Data",
|
||||
default: doctype,
|
||||
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: __("Custom?"),
|
||||
fieldname: "custom",
|
||||
fieldtype: "Check",
|
||||
default: non_developer,
|
||||
read_only: non_developer,
|
||||
},
|
||||
],
|
||||
primary_action_label: __("Create & Continue"),
|
||||
primary_action(values) {
|
||||
if (!values.istable) values.editable_grid = 0;
|
||||
frappe.db
|
||||
.insert({
|
||||
doctype: "DocType",
|
||||
name: values.doctype_name,
|
||||
module: values.module,
|
||||
istable: values.istable,
|
||||
editable_grid: values.editable_grid,
|
||||
issingle: values.issingle,
|
||||
custom: values.custom,
|
||||
is_submittable: values.is_submittable,
|
||||
permissions: [
|
||||
{
|
||||
create: 1,
|
||||
delete: 1,
|
||||
email: 1,
|
||||
export: 1,
|
||||
print: 1,
|
||||
read: 1,
|
||||
report: 1,
|
||||
role: "System Manager",
|
||||
share: 1,
|
||||
write: 1,
|
||||
},
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
label: "Title",
|
||||
fieldname: "title",
|
||||
fieldtype: "Data",
|
||||
},
|
||||
],
|
||||
})
|
||||
.then((doc) => {
|
||||
frappe.set_route("form-builder", doc.name);
|
||||
});
|
||||
},
|
||||
secondary_action_label: __("Back"),
|
||||
secondary_action() {
|
||||
new_d.hide();
|
||||
window.history.back();
|
||||
},
|
||||
});
|
||||
new_d.show();
|
||||
};
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"content": null,
|
||||
"creation": "2022-10-10 22:42:53.597423",
|
||||
"docstatus": 0,
|
||||
"doctype": "Page",
|
||||
"idx": 0,
|
||||
"modified": "2022-10-10 22:42:53.597423",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "form-builder",
|
||||
"owner": "Administrator",
|
||||
"page_name": "form-builder",
|
||||
"roles": [],
|
||||
"script": null,
|
||||
"standard": "Yes",
|
||||
"style": null,
|
||||
"system_page": 0,
|
||||
"title": "Form Builder"
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ import Sidebar from "./components/Sidebar.vue"
|
|||
import Tabs from "./components/Tabs.vue";
|
||||
import { computed, onMounted, watch, ref } from "vue";
|
||||
import { useStore } from "./store";
|
||||
import { onClickOutside, useMagicKeys, whenever } from "@vueuse/core";
|
||||
import { onClickOutside } from "@vueuse/core";
|
||||
|
||||
let store = useStore();
|
||||
|
||||
|
|
@ -14,19 +14,6 @@ let should_render = computed(() => {
|
|||
let container = ref(null);
|
||||
onClickOutside(container, () => store.form.selected_field = null);
|
||||
|
||||
// cmd/ctrl + s to save the form
|
||||
const { meta_s, ctrl_s } = useMagicKeys();
|
||||
whenever(() => meta_s.value || ctrl_s.value, () => {
|
||||
if (store.dirty) {
|
||||
store.save_changes();
|
||||
}
|
||||
});
|
||||
|
||||
function setup_change_doctype_dialog() {
|
||||
store.page.$title_area.on("click", () => {
|
||||
frappe.pages["form-builder"].select_doctype();
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => store.form.layout,
|
||||
|
|
@ -34,10 +21,7 @@ watch(
|
|||
{ deep: true }
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
store.fetch();
|
||||
setup_change_doctype_dialog();
|
||||
});
|
||||
onMounted(() => store.fetch());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -62,9 +46,8 @@ onMounted(() => {
|
|||
|
||||
<style lang="scss" scoped>
|
||||
.form-builder-container {
|
||||
margin-bottom: -60px;
|
||||
margin: -12px -20px -5px;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
|
||||
&.resizing {
|
||||
user-select: none;
|
||||
|
|
@ -79,12 +62,20 @@ onMounted(() => {
|
|||
flex: 1;
|
||||
}
|
||||
|
||||
.form-sidebar,
|
||||
.form-sidebar {
|
||||
border-right: 1px solid var(--border-color);
|
||||
border-bottom-left-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.form-main {
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: var(--card-shadow);
|
||||
background-color: var(--card-bg);
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.form-sidebar,
|
||||
.form-main {
|
||||
:deep(.section-columns.has-one-column .field) {
|
||||
input.form-control, .signature-field {
|
||||
width: calc(50% - 19px);
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ function on_drag_end(evt) {
|
|||
|
||||
<style lang="scss" scoped>
|
||||
.fields-container {
|
||||
height: calc(100vh - 250px);
|
||||
height: calc(100vh - 233px);
|
||||
overflow-y: auto;
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ watch(
|
|||
opacity: 0;
|
||||
background-color: var(--bg-gray);
|
||||
transition: opacity 0.2s ease;
|
||||
z-index: 10;
|
||||
z-index: 4;
|
||||
cursor: col-resize;
|
||||
|
||||
&:hover, &.resizing {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ function activate_tab(tab) {
|
|||
nextTick(() => {
|
||||
$(".tabs .tab.active")[0].scrollIntoView({
|
||||
behavior: "smooth",
|
||||
inline: "center"
|
||||
inline: "center",
|
||||
block: "nearest",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -280,6 +281,7 @@ function delete_tab(with_children) {
|
|||
.tab-contents {
|
||||
max-height: calc(100vh - 210px);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
border-radius: var(--border-radius);
|
||||
min-height: 70px;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,9 +5,10 @@ import FormBuilderComponent from "./FormBuilder.vue";
|
|||
import { registerGlobalComponents } from "./globals.js";
|
||||
|
||||
class FormBuilder {
|
||||
constructor({ wrapper, page, doctype, customize }) {
|
||||
constructor({ wrapper, frm, doctype, customize }) {
|
||||
this.$wrapper = $(wrapper);
|
||||
this.page = page;
|
||||
this.frm = frm;
|
||||
this.page = frm.page;
|
||||
this.doctype = doctype;
|
||||
this.customize = customize;
|
||||
this.read_only = false;
|
||||
|
|
@ -21,21 +22,14 @@ class FormBuilder {
|
|||
|
||||
this.setup_page_actions();
|
||||
!refresh && this.setup_app();
|
||||
refresh && this.update_store();
|
||||
this.watch_changes();
|
||||
}
|
||||
|
||||
async setup_page_actions() {
|
||||
// clear actions
|
||||
this.page.clear_actions();
|
||||
this.page.clear_menu();
|
||||
this.page.clear_custom_actions();
|
||||
|
||||
// setup page actions
|
||||
this.primary_btn = this.page.set_primary_action(__("Save"), () =>
|
||||
this.store.save_changes()
|
||||
);
|
||||
|
||||
setup_page_actions() {
|
||||
this.preview_btn?.remove();
|
||||
this.preview_btn = this.page.add_button(__("Show Preview"), () => {
|
||||
this.store.frm.layout.tabs.find((tab) => tab.label === "Form").set_active();
|
||||
this.store.preview = !this.store.preview;
|
||||
|
||||
if (this.store.read_only && !this.read_only) {
|
||||
|
|
@ -44,34 +38,10 @@ class FormBuilder {
|
|||
|
||||
this.store.read_only = this.store.preview;
|
||||
this.read_only = true;
|
||||
});
|
||||
|
||||
this.reset_changes_btn = this.page.add_button(__("Reset Changes"), () => {
|
||||
this.store.reset_changes();
|
||||
// toggle preview btn text
|
||||
this.preview_btn.text(this.store.preview ? __("Hide Preview") : __("Show Preview"));
|
||||
});
|
||||
|
||||
this.go_to_doctype_list_btn = this.page.add_button(
|
||||
__("Go to {0} List", [__(this.doctype)]),
|
||||
() => {
|
||||
window.open(`/app/${frappe.router.slug(this.doctype)}`);
|
||||
}
|
||||
);
|
||||
|
||||
this.customize_form_btn = this.page.add_menu_item(__("Switch to Customize"), () => {
|
||||
frappe.set_route("form-builder", this.doctype, "customize");
|
||||
});
|
||||
this.doctype_form_btn = this.page.add_menu_item(__("Switch to DocType"), () => {
|
||||
frappe.set_route("form-builder", this.doctype);
|
||||
});
|
||||
|
||||
this.go_to_doctype_btn = this.page.add_menu_item(__("Go to DocType"), () =>
|
||||
frappe.set_route("Form", "DocType", this.doctype)
|
||||
);
|
||||
this.go_to_customize_form_btn = this.page.add_menu_item(__("Go to Customize Form"), () =>
|
||||
frappe.set_route("Form", "Customize Form", {
|
||||
doc_type: this.doctype,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
setup_app() {
|
||||
|
|
@ -85,9 +55,7 @@ class FormBuilder {
|
|||
|
||||
// create a store
|
||||
this.store = useStore();
|
||||
this.store.doctype = this.doctype;
|
||||
this.store.is_customize_form = this.customize;
|
||||
this.store.page = this.page;
|
||||
this.update_store();
|
||||
|
||||
// register global components
|
||||
registerGlobalComponents(app);
|
||||
|
|
@ -96,69 +64,21 @@ class FormBuilder {
|
|||
this.$form_builder = app.mount(this.$wrapper.get(0));
|
||||
}
|
||||
|
||||
update_store() {
|
||||
this.store.doctype = this.doctype;
|
||||
this.store.is_customize_form = this.customize;
|
||||
this.store.page = this.page;
|
||||
this.store.frm = this.frm;
|
||||
}
|
||||
|
||||
watch_changes() {
|
||||
watchEffect(() => {
|
||||
if (this.store.dirty) {
|
||||
this.page.set_indicator(__("Not Saved"), "orange");
|
||||
this.reset_changes_btn.show();
|
||||
if (this.store.dirty || this.frm.is_dirty()) {
|
||||
this.frm.dirty();
|
||||
} else {
|
||||
this.page.clear_indicator();
|
||||
this.reset_changes_btn.hide();
|
||||
}
|
||||
|
||||
// hide all buttons
|
||||
this.go_to_doctype_list_btn.hide();
|
||||
this.customize_form_btn.hide();
|
||||
this.doctype_form_btn.hide();
|
||||
this.go_to_doctype_btn.hide();
|
||||
this.go_to_customize_form_btn.hide();
|
||||
|
||||
this.page.menu_btn_group.show();
|
||||
let hide_menu = true;
|
||||
|
||||
// show customize form & Go to customize form btn
|
||||
if (
|
||||
this.store.doc &&
|
||||
!this.store.doc.custom &&
|
||||
!this.store.doc.issingle &&
|
||||
!this.store.is_customize_form &&
|
||||
!in_list(frappe.model.core_doctypes_list, this.doctype)
|
||||
) {
|
||||
this.customize_form_btn.show();
|
||||
this.go_to_customize_form_btn.show();
|
||||
hide_menu = false;
|
||||
}
|
||||
|
||||
// show doctype form & Go to doctype form btn
|
||||
if (
|
||||
this.store.doc &&
|
||||
!this.store.doc.custom &&
|
||||
!this.store.doc.issingle &&
|
||||
this.store.is_customize_form
|
||||
) {
|
||||
this.doctype_form_btn.show();
|
||||
this.go_to_doctype_btn.show();
|
||||
hide_menu = false;
|
||||
}
|
||||
|
||||
// show Go to {0} List or Go to {0} button
|
||||
if (this.store.doc && !this.store.doc.istable) {
|
||||
let label = this.store.doc.issingle
|
||||
? __("Go to {0}", [__(this.doctype)])
|
||||
: __("Go to {0} List", [__(this.doctype)]);
|
||||
|
||||
this.go_to_doctype_list_btn.text(label).show();
|
||||
}
|
||||
|
||||
if (hide_menu && window.matchMedia("(min-device-width: 992px)").matches) {
|
||||
this.page.menu_btn_group.hide();
|
||||
}
|
||||
|
||||
// toggle preview btn text
|
||||
this.preview_btn.text(this.store.preview ? __("Hide Preview") : __("Show Preview"));
|
||||
|
||||
// toggle primary btn and show indicator based on read_only state
|
||||
this.primary_btn.toggle(!this.store.read_only);
|
||||
if (this.store.read_only) {
|
||||
let message = this.store.preview ? __("Preview Mode") : __("Read Only");
|
||||
this.page.set_indicator(message, "orange");
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { useDebouncedRefHistory, onKeyDown } from "@vueuse/core";
|
|||
|
||||
export const useStore = defineStore("form-builder-store", () => {
|
||||
let doctype = ref("");
|
||||
let frm = ref(null);
|
||||
let doc = ref(null);
|
||||
let docfields = ref([]);
|
||||
let custom_docfields = ref([]);
|
||||
|
|
@ -69,17 +70,9 @@ export const useStore = defineStore("form-builder-store", () => {
|
|||
}
|
||||
|
||||
async function fetch() {
|
||||
await frappe.model.clear_doc("DocType", doctype.value);
|
||||
await frappe.model.with_doctype(doctype.value);
|
||||
|
||||
if (is_customize_form.value) {
|
||||
await frappe.model.with_doc("Customize Form");
|
||||
let _doc = frappe.get_doc("Customize Form");
|
||||
_doc.doc_type = doctype.value;
|
||||
let r = await frappe.call({ method: "fetch_to_customize", doc: _doc });
|
||||
doc.value = r.docs[0];
|
||||
} else {
|
||||
doc.value = await frappe.db.get_doc("DocType", doctype.value);
|
||||
doc.value = frm.value.doc;
|
||||
if (doctype.value.startsWith("new-doctype-")) {
|
||||
doc.value.fields = [get_df("Data", "", __("Title"))];
|
||||
}
|
||||
|
||||
if (!get_docfields.value.length) {
|
||||
|
|
@ -99,18 +92,19 @@ export const useStore = defineStore("form-builder-store", () => {
|
|||
|
||||
nextTick(() => {
|
||||
dirty.value = false;
|
||||
frm.value.doc.__unsaved = 0;
|
||||
frm.value.page.clear_indicator();
|
||||
read_only.value =
|
||||
!is_customize_form.value && !frappe.boot.developer_mode && !doc.value.custom;
|
||||
preview.value = false;
|
||||
});
|
||||
|
||||
setup_undo_redo();
|
||||
setup_breadcrumbs();
|
||||
}
|
||||
|
||||
let undo_redo_keyboard_event = onKeyDown(true, (e) => {
|
||||
if (!ref_history.value) return;
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
if (frm.value.get_active_tab().label == "Form" && (e.ctrlKey || e.metaKey)) {
|
||||
if (e.key === "z" && !e.shiftKey && ref_history.value.canUndo) {
|
||||
ref_history.value.undo();
|
||||
} else if (e.key === "z" && e.shiftKey && ref_history.value.canRedo) {
|
||||
|
|
@ -125,30 +119,17 @@ export const useStore = defineStore("form-builder-store", () => {
|
|||
undo_redo_keyboard_event;
|
||||
}
|
||||
|
||||
function setup_breadcrumbs() {
|
||||
!is_customize_form.value && frappe.model.init_doctype("DocType");
|
||||
let breadcrumbs = `
|
||||
<li><a href="/app/doctype">${__("DocType")}</a></li>
|
||||
<li><a href="/app/doctype/${doctype.value}">${__(doctype.value)}</a></li>
|
||||
`;
|
||||
if (is_customize_form.value) {
|
||||
breadcrumbs = `
|
||||
<li><a href="/app/customize-form?doc_type=${doctype.value}">
|
||||
${__("Customize Form")}
|
||||
</a></li>
|
||||
`;
|
||||
}
|
||||
breadcrumbs += `<li class="disabled"><a href="#">${__("Form Builder")}</a></li>`;
|
||||
frappe.breadcrumbs.clear();
|
||||
frappe.breadcrumbs.$breadcrumbs.append(breadcrumbs);
|
||||
}
|
||||
|
||||
function reset_changes() {
|
||||
fetch();
|
||||
}
|
||||
|
||||
function validate_fields(fields, is_table) {
|
||||
fields = scrub_field_names(fields);
|
||||
let error_message = "";
|
||||
|
||||
let has_fields = fields.some((df) => {
|
||||
return !["Section Break", "Tab Break", "Column Break"].includes(df.fieldtype);
|
||||
});
|
||||
|
||||
if (!has_fields) {
|
||||
error_message = __("DocType must have atleast one field");
|
||||
}
|
||||
|
||||
let not_allowed_in_list_view = ["Attach Image", ...frappe.model.no_value_type];
|
||||
if (is_table) {
|
||||
|
|
@ -168,70 +149,56 @@ export const useStore = defineStore("form-builder-store", () => {
|
|||
// check if fieldname already exist
|
||||
let duplicate = fields.filter((f) => f.fieldname == df.fieldname);
|
||||
if (duplicate.length > 1) {
|
||||
frappe.throw(__("Fieldname {0} appears multiple times", get_field_data(df)));
|
||||
error_message = __("Fieldname {0} appears multiple times", get_field_data(df));
|
||||
}
|
||||
|
||||
// Link & Table fields should always have options set
|
||||
if (in_list(["Link", ...frappe.model.table_fields], df.fieldtype) && !df.options) {
|
||||
frappe.throw(
|
||||
__("Options is required for field {0} of type {1}", get_field_data(df))
|
||||
error_message = __(
|
||||
"Options is required for field {0} of type {1}",
|
||||
get_field_data(df)
|
||||
);
|
||||
}
|
||||
|
||||
// Do not allow if field is hidden & required but doesn't have default value
|
||||
if (df.hidden && df.reqd && !df.default) {
|
||||
frappe.throw(
|
||||
__(
|
||||
"{0} cannot be hidden and mandatory without any default value",
|
||||
get_field_data(df)
|
||||
)
|
||||
error_message = __(
|
||||
"{0} cannot be hidden and mandatory without any default value",
|
||||
get_field_data(df)
|
||||
);
|
||||
}
|
||||
|
||||
// In List View is not allowed for some fieldtypes
|
||||
if (df.in_list_view && in_list(not_allowed_in_list_view, df.fieldtype)) {
|
||||
frappe.throw(
|
||||
__(
|
||||
"'In List View' is not allowed for field {0} of type {1}",
|
||||
get_field_data(df)
|
||||
)
|
||||
error_message = __(
|
||||
"'In List View' is not allowed for field {0} of type {1}",
|
||||
get_field_data(df)
|
||||
);
|
||||
}
|
||||
|
||||
// In Global Search is not allowed for no_value_type fields
|
||||
if (df.in_global_search && in_list(frappe.model.no_value_type, df.fieldtype)) {
|
||||
frappe.throw(
|
||||
__(
|
||||
"'In Global Search' is not allowed for field {0} of type {1}",
|
||||
get_field_data(df)
|
||||
)
|
||||
error_message = __(
|
||||
"'In Global Search' is not allowed for field {0} of type {1}",
|
||||
get_field_data(df)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return error_message;
|
||||
}
|
||||
|
||||
async function save_changes() {
|
||||
if (!dirty.value) {
|
||||
frappe.show_alert({ message: __("No changes to save"), indicator: "orange" });
|
||||
return;
|
||||
}
|
||||
function update_fields() {
|
||||
if (!dirty.value && !frm.value.is_new()) return;
|
||||
|
||||
frappe.dom.freeze(__("Saving..."));
|
||||
|
||||
try {
|
||||
if (is_customize_form.value) {
|
||||
let _doc = frappe.get_doc("Customize Form");
|
||||
_doc.doc_type = doctype.value;
|
||||
_doc.fields = get_updated_fields();
|
||||
validate_fields(_doc.fields, _doc.istable);
|
||||
await frappe.call({ method: "save_customization", doc: _doc });
|
||||
} else {
|
||||
doc.value.fields = get_updated_fields();
|
||||
validate_fields(doc.value.fields, doc.value.istable);
|
||||
await frappe.call("frappe.client.save", { doc: doc.value });
|
||||
frappe.toast("Fields Table Updated");
|
||||
}
|
||||
fetch();
|
||||
let fields = get_updated_fields();
|
||||
let has_error = validate_fields(fields, doc.value.istable);
|
||||
if (has_error) return has_error;
|
||||
doc.value.fields = fields;
|
||||
return fields;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
|
|
@ -242,6 +209,9 @@ export const useStore = defineStore("form-builder-store", () => {
|
|||
function get_updated_fields() {
|
||||
let fields = [];
|
||||
let idx = 0;
|
||||
let new_field_name = is_customize_form.value
|
||||
? "new-customize-form-field-"
|
||||
: "new-docfield-";
|
||||
|
||||
let layout_fields = JSON.parse(JSON.stringify(form.value.layout.tabs));
|
||||
|
||||
|
|
@ -252,6 +222,9 @@ export const useStore = defineStore("form-builder-store", () => {
|
|||
) {
|
||||
idx++;
|
||||
tab.df.idx = idx;
|
||||
if (tab.df.__unsaved && tab.df.__islocal) {
|
||||
tab.df.name = new_field_name + idx;
|
||||
}
|
||||
fields.push(tab.df);
|
||||
}
|
||||
|
||||
|
|
@ -265,6 +238,9 @@ export const useStore = defineStore("form-builder-store", () => {
|
|||
if ((j == 0 && is_df_updated(section.df, get_df("Section Break"))) || j > 0) {
|
||||
idx++;
|
||||
section.df.idx = idx;
|
||||
if (section.df.__unsaved && section.df.__islocal) {
|
||||
section.df.name = new_field_name + idx;
|
||||
}
|
||||
fields.push(section.df);
|
||||
}
|
||||
|
||||
|
|
@ -277,12 +253,18 @@ export const useStore = defineStore("form-builder-store", () => {
|
|||
) {
|
||||
idx++;
|
||||
column.df.idx = idx;
|
||||
if (column.df.__unsaved && column.df.__islocal) {
|
||||
column.df.name = new_field_name + idx;
|
||||
}
|
||||
fields.push(column.df);
|
||||
}
|
||||
|
||||
column.fields.forEach((field) => {
|
||||
idx++;
|
||||
field.df.idx = idx;
|
||||
if (field.df.__unsaved && field.df.__islocal) {
|
||||
field.df.name = new_field_name + idx;
|
||||
}
|
||||
fields.push(field.df);
|
||||
section.has_fields = true;
|
||||
});
|
||||
|
|
@ -300,9 +282,11 @@ export const useStore = defineStore("form-builder-store", () => {
|
|||
}
|
||||
|
||||
function is_df_updated(df, new_df) {
|
||||
delete df.name;
|
||||
delete new_df.name;
|
||||
return JSON.stringify(df) != JSON.stringify(new_df);
|
||||
let df_copy = JSON.parse(JSON.stringify(df));
|
||||
let new_df_copy = JSON.parse(JSON.stringify(new_df));
|
||||
delete df_copy.name;
|
||||
delete new_df_copy.name;
|
||||
return JSON.stringify(df_copy) != JSON.stringify(new_df_copy);
|
||||
}
|
||||
|
||||
function get_layout() {
|
||||
|
|
@ -311,6 +295,7 @@ export const useStore = defineStore("form-builder-store", () => {
|
|||
|
||||
return {
|
||||
doctype,
|
||||
frm,
|
||||
doc,
|
||||
form,
|
||||
dirty,
|
||||
|
|
@ -326,9 +311,8 @@ export const useStore = defineStore("form-builder-store", () => {
|
|||
has_standard_field,
|
||||
is_user_generated_field,
|
||||
fetch,
|
||||
reset_changes,
|
||||
validate_fields,
|
||||
save_changes,
|
||||
update_fields,
|
||||
get_updated_fields,
|
||||
is_df_updated,
|
||||
get_layout,
|
||||
|
|
|
|||
|
|
@ -61,8 +61,6 @@ export function create_layout(fields) {
|
|||
if (df.fieldname) {
|
||||
// make a copy to avoid mutation bugs
|
||||
df = JSON.parse(JSON.stringify(df));
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (df.fieldtype === "Tab Break") {
|
||||
|
|
@ -71,7 +69,7 @@ export function create_layout(fields) {
|
|||
set_section(df);
|
||||
} else if (df.fieldtype === "Column Break") {
|
||||
set_column(df);
|
||||
} else if (df.name) {
|
||||
} else {
|
||||
if (!column) set_column();
|
||||
|
||||
let field = { df: df };
|
||||
|
|
|
|||
|
|
@ -134,8 +134,6 @@ frappe.model.DocTypeController = class DocTypeController extends frappe.ui.form.
|
|||
|
||||
setTimeout(() => (this.frm.__from_autoname = false), 500);
|
||||
}
|
||||
|
||||
this.frm.set_df_property("fields", "reqd", this.frm.doc.autoname !== "Prompt");
|
||||
}
|
||||
|
||||
setup_fetch_from_fields(doc, doctype, docname) {
|
||||
|
|
|
|||
|
|
@ -77,9 +77,12 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
// wrapper
|
||||
this.wrapper = this.parent;
|
||||
this.$wrapper = $(this.wrapper);
|
||||
|
||||
let is_single_column = this.doctype === "DocType" ? true : this.meta.hide_toolbar;
|
||||
|
||||
frappe.ui.make_app_page({
|
||||
parent: this.wrapper,
|
||||
single_column: this.meta.hide_toolbar,
|
||||
single_column: is_single_column,
|
||||
});
|
||||
this.page = this.wrapper.page;
|
||||
this.layout_main = this.page.main.get(0);
|
||||
|
|
@ -157,12 +160,14 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
action: () => this.undo_manager.undo(),
|
||||
page: this.page,
|
||||
description: __("Undo last action"),
|
||||
condition: () => !this.is_form_builder(),
|
||||
});
|
||||
frappe.ui.keys.add_shortcut({
|
||||
shortcut: "shift+ctrl+z",
|
||||
action: () => this.undo_manager.redo(),
|
||||
page: this.page,
|
||||
description: __("Redo last action"),
|
||||
condition: () => !this.is_form_builder(),
|
||||
});
|
||||
frappe.ui.keys.add_shortcut({
|
||||
shortcut: "ctrl+y",
|
||||
|
|
@ -1361,6 +1366,13 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
return this.doc.__islocal;
|
||||
}
|
||||
|
||||
is_form_builder() {
|
||||
return (
|
||||
in_list(["DocType", "Customize Form"], this.doctype) &&
|
||||
this.get_active_tab().label == "Form"
|
||||
);
|
||||
}
|
||||
|
||||
get_perm(permlevel, access_type) {
|
||||
return this.perm[permlevel] ? this.perm[permlevel][access_type] : null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -364,7 +364,10 @@ frappe.ui.form.Layout = class Layout {
|
|||
const section = $(this).removeClass("empty-section visible-section");
|
||||
if (section.find(".frappe-control:not(.hide-control)").length) {
|
||||
section.addClass("visible-section");
|
||||
} else {
|
||||
} else if (
|
||||
section.parent().hasClass("tab-pane") ||
|
||||
section.parent().hasClass("form-page")
|
||||
) {
|
||||
// nothing visible, hide the section
|
||||
section.addClass("empty-section");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
<script setup>
|
||||
import { ref, computed, nextTick } from "vue";
|
||||
import { useStore } from "../store";
|
||||
import { useVueFlow } from "@vue-flow/core";
|
||||
|
||||
let store = useStore();
|
||||
|
||||
let { nodes } = useVueFlow();
|
||||
|
||||
let title = ref("Workflow Details");
|
||||
|
||||
let doc = computed(() => {
|
||||
|
|
@ -37,17 +34,6 @@ let properties = computed(() => {
|
|||
df.options = ["Draft", "Submitted", "Cancelled"];
|
||||
df.description = "";
|
||||
}
|
||||
if (df.fieldname == "state") {
|
||||
let filter = nodes.value
|
||||
.filter(state => state.type == "state")
|
||||
.map(node => node.data.state);
|
||||
if (doc.value.state) {
|
||||
filter = filter.filter(state => state !== doc.value.state);
|
||||
}
|
||||
df.filters = {
|
||||
name: ["not in", filter]
|
||||
};
|
||||
}
|
||||
if (df.fieldname == "update_field") {
|
||||
df.options = store.workflow_doc_fields;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -582,7 +582,7 @@ def create_kanban():
|
|||
|
||||
@whitelist_for_tests
|
||||
def create_todo(description):
|
||||
frappe.get_doc({"doctype": "ToDo", "description": description}).insert()
|
||||
return frappe.get_doc({"doctype": "ToDo", "description": description}).insert()
|
||||
|
||||
|
||||
@whitelist_for_tests
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue