Merge pull request #19646 from shariquerik/form-builder-fixes-2
This commit is contained in:
commit
07df794ea4
7 changed files with 175 additions and 64 deletions
|
|
@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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="{
|
||||
|
|
|
|||
|
|
@ -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") }}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue