Merge pull request #19646 from shariquerik/form-builder-fixes-2

This commit is contained in:
Shariq Ansari 2023-01-20 12:43:12 +05:30 committed by GitHub
commit 07df794ea4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 175 additions and 64 deletions

View file

@ -4,7 +4,7 @@ import Field from "./Field.vue";
import EditableInput from "./EditableInput.vue";
import { ref } from "vue";
import { useStore } from "../store";
import { move_children_to_parent } from "../utils";
import { move_children_to_parent, confirm_dialog } from "../utils";
let props = defineProps(["section", "column"]);
let store = useStore();
@ -24,32 +24,61 @@ function remove_column() {
if (store.is_customize_form && props.column.df.is_custom_field == 0) {
frappe.msgprint(__("Cannot delete standard field. You can hide it if you want"));
throw "cannot delete standard field";
} else if (props.column.fields.length == 0 || store.has_standard_field(props.column)) {
delete_column();
} else {
confirm_dialog(
__("Delete Column", null, "Title of confirmation dialog"),
__("Are you sure you want to delete the column? All the fields in the column will be moved to the previous column.", null, "Confirmation dialog message"),
() => delete_column(),
__("Delete column", null, "Button text"),
() => delete_column(true),
__("Delete entire column with fields", null, "Button text")
);
}
}
function delete_column(with_children) {
// move all fields to previous column
let columns = props.section.columns;
let index = columns.indexOf(props.column);
if (index > 0) {
let prev_column = columns[index - 1];
prev_column.fields = [...prev_column.fields, ...props.column.fields];
} else {
if (props.column.fields.length != 0) {
// create a new column if current column has fields and push fields to it
columns.unshift({
df: store.get_df("Column Break"),
fields: props.column.fields,
is_first: true,
});
index++;
if (with_children && index == 0 && columns.length == 1) {
if (props.column.fields.length == 0) {
frappe.msgprint(__("Section must have at least one column"));
throw "section must have at least one column";
}
columns.unshift({
df: store.get_df("Column Break"),
fields: [],
is_first: true,
});
index++;
}
if (!with_children) {
if (index > 0) {
let prev_column = columns[index - 1];
prev_column.fields = [...prev_column.fields, ...props.column.fields];
} else {
// set next column as first column
let next_column = columns[index + 1];
if (next_column) {
next_column.is_first = true;
if (props.column.fields.length == 0) {
// set next column as first column
let next_column = columns[index + 1];
if (next_column) {
next_column.is_first = true;
} else {
frappe.msgprint(__("Section must have at least one column"));
throw "section must have at least one column";
}
} else {
frappe.msgprint(__("Section must have at least one column"));
throw "section must have at least one column";
// create a new column if current column has fields and push fields to it
columns.unshift({
df: store.get_df("Column Break"),
fields: props.column.fields,
is_first: true,
});
index++;
}
}
}

View file

@ -32,10 +32,21 @@ function move_fields_to_column() {
function duplicate_field() {
let duplicate_field = clone_field(props.field);
if (store.is_customize_form) {
duplicate_field.df.is_custom_field = 1;
}
if (duplicate_field.df.label) {
duplicate_field.df.label = duplicate_field.df.label + " Copy";
}
duplicate_field.df.fieldname = "";
duplicate_field.df.__islocal = 1;
duplicate_field.df.__unsaved = 1;
duplicate_field.df.owner = frappe.session.user;
delete duplicate_field.df.creation;
delete duplicate_field.df.modified;
delete duplicate_field.df.modified_by;
// push duplicate_field after props.field in the same column
let index = props.column.fields.indexOf(props.field);

View file

@ -109,6 +109,12 @@ onMounted(() => {
box-shadow: var(--card-shadow);
background-color: var(--card-bg);
:deep(.section-columns.has-one-column .field) {
input.form-control, .signature-field {
width: calc(50% - 19px);
}
}
:deep(.column-container .field.sortable-chosen) {
background-color: var(--bg-light-gray);
border-radius: var(--border-radius-sm);
@ -191,6 +197,8 @@ onMounted(() => {
}
:deep(.preview) {
--field-placeholder-color: var(--fg-bg-color);
.tab, .column, .field, [data-is-custom="1"] {
background-color: var(--fg-color);
}
@ -221,6 +229,12 @@ onMounted(() => {
.section-columns {
margin-top: 8px;
&.has-one-column .field {
input.form-control, .signature-field {
width: calc(50% - 15px);
}
}
.section-columns-container {
.column {
padding-left: 15px;

View file

@ -4,7 +4,7 @@ import Column from "./Column.vue";
import EditableInput from "./EditableInput.vue";
import { ref } from "vue";
import { useStore } from "../store";
import { section_boilerplate, move_children_to_parent } from "../utils";
import { section_boilerplate, move_children_to_parent, confirm_dialog } from "../utils";
let props = defineProps(["tab", "section"]);
let store = useStore();
@ -27,25 +27,42 @@ function remove_section() {
if (store.is_customize_form && props.section.df.is_custom_field == 0) {
frappe.msgprint(__("Cannot delete standard field. You can hide it if you want"));
throw "cannot delete standard field";
} else if (store.has_standard_field(props.section)) {
delete_section();
} else if (is_section_empty()) {
delete_section(true);
} else {
confirm_dialog(
__("Delete Section", null, "Title of confirmation dialog"),
__("Are you sure you want to delete the section? All the columns along with fields in the section will be moved to the previous section.", null, "Confirmation dialog message"),
() => delete_section(),
__("Delete section", null, "Button text"),
() => delete_section(true),
__("Delete entire section with columns", null, "Button text")
);
}
}
function delete_section(with_children) {
let sections = props.tab.sections;
let index = sections.indexOf(props.section);
if (index > 0) {
let prev_section = sections[index - 1];
if (!is_section_empty()) {
// move all columns from current section to previous section
prev_section.columns = [...prev_section.columns, ...props.section.columns];
if (!with_children) {
if (index > 0) {
let prev_section = sections[index - 1];
if (!is_section_empty()) {
// move all columns from current section to previous section
prev_section.columns = [...prev_section.columns, ...props.section.columns];
}
} else if (index == 0 && !is_section_empty()) {
// create a new section and push columns to it
sections.unshift({
df: store.get_df("Section Break"),
columns: props.section.columns,
is_first: true,
});
index++;
}
} else if (index == 0 && !is_section_empty()) {
// create a new section and push columns to it
sections.unshift({
df: store.get_df("Section Break"),
columns: props.section.columns,
is_first: true,
});
index++;
}
// remove section
@ -130,7 +147,13 @@ function move_sections_to_tab() {
</div>
</div>
<div v-if="section.df.description" class="section-description">{{ section.df.description }}</div>
<div class="section-columns" :class="{ hidden: section.df.collapsible && collapsed }">
<div
class="section-columns"
:class="{
hidden: section.df.collapsible && collapsed,
'has-one-column': section.columns.length === 1
}"
>
<draggable
class="section-columns-container"
:style="{

View file

@ -3,7 +3,7 @@ import Section from "./Section.vue";
import EditableInput from "./EditableInput.vue";
import draggable from "vuedraggable";
import { useStore } from "../store";
import { section_boilerplate } from "../utils";
import { section_boilerplate, confirm_dialog } from "../utils";
import { ref, computed, nextTick } from "vue";
let store = useStore();
@ -51,44 +51,51 @@ function add_new_section() {
function is_current_tab_empty() {
// check if sections have columns and it contains fields
return !store.current_tab.sections.some(section => {
// if section doesnt have fields remove the section
let has_fields = section.columns.some(column => column.fields.length);
if (!has_fields) {
// remove section if empty
let index = store.current_tab.sections.indexOf(section);
store.current_tab.sections.splice(index, 1);
has_fields = true;
}
return has_fields;
});
return !store.current_tab.sections.some(
section => section.columns.some(column => column.fields.length)
);
}
function remove_tab() {
if (store.is_customize_form && store.current_tab.df.is_custom_field == 0) {
frappe.msgprint(__("Cannot delete standard field. You can hide it if you want"));
throw "cannot delete standard field";
} else if (store.has_standard_field(store.current_tab)) {
delete_tab();
} else if (is_current_tab_empty()) {
delete_tab(true);
} else {
confirm_dialog(
__("Delete Tab", null, "Title of confirmation dialog"),
__("Are you sure you want to delete the tab? All the sections along with fields in the tab will be moved to the previous tab.", null, "Confirmation dialog message"),
() => delete_tab(),
__("Delete tab", null, "Button text"),
() => delete_tab(true),
__("Delete entire tab with sections", null, "Button text")
);
}
}
function delete_tab(with_children) {
let tabs = layout.value.tabs;
let index = tabs.indexOf(store.current_tab);
if (index > 0) {
let prev_tab = tabs[index - 1];
if (!is_current_tab_empty()) {
// move all sections from current tab to previous tab
prev_tab.sections = [...prev_tab.sections, ...store.current_tab.sections];
if (!with_children) {
if (index > 0) {
let prev_tab = tabs[index - 1];
if (!is_current_tab_empty()) {
// move all sections from current tab to previous tab
prev_tab.sections = [...prev_tab.sections, ...store.current_tab.sections];
}
} else {
// create a new tab and push sections to it
tabs.unshift({
df: store.get_df("Tab Break", "", __("Details")),
sections: store.current_tab.sections,
is_first: true,
});
index++;
}
} else {
// create a new tab and push sections to it
tabs.unshift({
df: store.get_df("Tab Break", "", __("Details")),
sections: store.current_tab.sections,
is_first: true,
});
index++;
}
// remove tab
@ -185,7 +192,7 @@ function remove_tab() {
</template>
</draggable>
<div class="empty-tab" :hidden="store.read_only">
<div>{{ __("Drag & Drop a section here") }}</div>
<div>{{ __("Drag & Drop a section here from another tab") }}</div>
<div>{{ __("OR") }}</div>
<button class="btn btn-default btn-sm" @click="add_new_section">
{{ __("Add a new section") }}

View file

@ -182,8 +182,10 @@ export const useStore = defineStore("form-builder-store", {
} else {
this.doc.fields = this.get_updated_fields();
this.validate_fields(this.doc.fields, this.doc.istable);
await frappe.call("frappe.client.save", { doc: this.doc });
frappe.toast("Fields Table Updated");
await frappe.call({
method: "frappe.desk.form.save.savedocs",
args: { doc: this.doc, action: "Save" },
});
}
this.fetch();
} catch (e) {

View file

@ -324,3 +324,28 @@ export function clone_field(field) {
cloned_field.df.name = frappe.utils.get_random(8);
return cloned_field;
}
export function confirm_dialog(
title,
message,
primary_action,
primary_action_label,
secondary_action,
secondary_action_label
) {
let d = new frappe.ui.Dialog({
title: title,
primary_action_label: primary_action_label || __("Yes"),
primary_action: () => {
primary_action && primary_action();
d.hide();
},
secondary_action_label: secondary_action_label || __("No"),
secondary_action: () => {
secondary_action && secondary_action();
d.hide();
},
});
d.show();
d.set_message(message);
}