fix: add new field btn in empty column
also created AddFieldButton component
This commit is contained in:
parent
bdbb11fbb9
commit
5f8887d794
5 changed files with 212 additions and 127 deletions
127
frappe/public/js/form_builder/components/AddFieldButton.vue
Normal file
127
frappe/public/js/form_builder/components/AddFieldButton.vue
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
<template>
|
||||
<button
|
||||
ref="add_field_btn_ref"
|
||||
class="add-field-btn btn btn-xs btn-icon"
|
||||
:title="__('Add field')"
|
||||
@click="toggle_fieldtype_dropdown"
|
||||
>
|
||||
<slot>
|
||||
{{ __("Add a field") }}
|
||||
</slot>
|
||||
<div class="drop-down" ref="dropdown_ref">
|
||||
<Dropdown v-if="show" :items="fields" v-model="search_text" />
|
||||
</div>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Dropdown from "./Dropdown.vue";
|
||||
import { useStore } from "../store";
|
||||
import { clone_field } from "../utils";
|
||||
import { createPopper } from "@popperjs/core";
|
||||
import { computed, ref, watch } from "vue";
|
||||
|
||||
const store = useStore();
|
||||
|
||||
const props = defineProps({
|
||||
column: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
field: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:modelValue", "update_parent"]);
|
||||
|
||||
const selected = computed(() => {
|
||||
let fieldname = props.field ? props.field.df.name : props.column.df.name;
|
||||
return store.selected(fieldname);
|
||||
});
|
||||
|
||||
const show = ref(false);
|
||||
const search_text = ref("");
|
||||
const fields = computed(() => {
|
||||
let fields = frappe.model.all_fieldtypes
|
||||
.filter((df) => {
|
||||
if (in_list(frappe.model.layout_fields, df)) {
|
||||
return false;
|
||||
}
|
||||
if (search_text.value) {
|
||||
if (df.toLowerCase().includes(search_text.value.toLowerCase())) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
})
|
||||
.map((df) => {
|
||||
let out = {
|
||||
label: df,
|
||||
onClick: () => {
|
||||
let new_field = {
|
||||
df: store.get_df(df),
|
||||
table_columns: [],
|
||||
};
|
||||
|
||||
add_new_field(clone_field(new_field));
|
||||
},
|
||||
};
|
||||
return out;
|
||||
});
|
||||
return [...fields];
|
||||
});
|
||||
|
||||
const add_field_btn_ref = ref(null);
|
||||
const dropdown_ref = ref(null);
|
||||
const popper = ref(null);
|
||||
|
||||
function setupPopper() {
|
||||
if (!popper.value) {
|
||||
popper.value = createPopper(add_field_btn_ref.value, dropdown_ref.value, {
|
||||
placement: "bottom-end",
|
||||
modifiers: [
|
||||
{
|
||||
name: "offset",
|
||||
options: {
|
||||
offset: [0, 4],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
} else {
|
||||
popper.value.update();
|
||||
}
|
||||
}
|
||||
|
||||
function toggle_fieldtype_dropdown() {
|
||||
show.value = !show.value;
|
||||
search_text.value = "";
|
||||
setTimeout(() => setupPopper());
|
||||
}
|
||||
|
||||
function add_new_field(field) {
|
||||
// insert new field after current field
|
||||
let index = 0;
|
||||
if (props.field) {
|
||||
index = props.column.fields.indexOf(props.field);
|
||||
}
|
||||
props.column.fields.splice(index + 1, 0, field);
|
||||
store.form.selected_field = field.df;
|
||||
show.value = false;
|
||||
emit("update_parent");
|
||||
}
|
||||
|
||||
watch(selected, (val) => {
|
||||
if (!val) show.value = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.drop-down {
|
||||
z-index: 100;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,15 +1,17 @@
|
|||
<script setup>
|
||||
import draggable from "vuedraggable";
|
||||
import Field from "./Field.vue";
|
||||
import AddFieldButton from "./AddFieldButton.vue";
|
||||
import EditableInput from "./EditableInput.vue";
|
||||
import { ref } from "vue";
|
||||
import { computed, ref } from "vue";
|
||||
import { useStore } from "../store";
|
||||
import { move_children_to_parent, confirm_dialog } from "../utils";
|
||||
|
||||
const props = defineProps(["section", "column"]);
|
||||
let store = useStore();
|
||||
const store = useStore();
|
||||
|
||||
let hovered = ref(false);
|
||||
const hovered = ref(false);
|
||||
const selected = computed(() => store.selected(props.column.df.name));
|
||||
|
||||
function add_column() {
|
||||
// insert new column after the current column
|
||||
|
|
@ -29,7 +31,11 @@ function remove_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"),
|
||||
__(
|
||||
"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),
|
||||
|
|
@ -95,21 +101,14 @@ function move_columns_to_section() {
|
|||
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
'column',
|
||||
hovered ? 'hovered' : '',
|
||||
store.selected(column.df.name) ? 'selected' : ''
|
||||
]"
|
||||
:class="['column', selected ? 'selected' : hovered ? 'hovered' : '']"
|
||||
:title="column.df.fieldname"
|
||||
@click.stop="store.form.selected_field = column.df"
|
||||
@mouseover.stop="hovered = true"
|
||||
@mouseout.stop="hovered = false"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'column-header',
|
||||
column.df.label ? 'has-label' : '',
|
||||
]"
|
||||
:class="['column-header', column.df.label ? 'has-label' : '']"
|
||||
:hidden="!column.df.label && store.read_only"
|
||||
>
|
||||
<div class="column-label">
|
||||
|
|
@ -120,6 +119,9 @@ function move_columns_to_section() {
|
|||
/>
|
||||
</div>
|
||||
<div class="column-actions">
|
||||
<button class="btn btn-xs btn-icon" :title="__('Add Column')" @click="add_column">
|
||||
<div v-html="frappe.utils.icon('add', 'sm')"></div>
|
||||
</button>
|
||||
<button
|
||||
v-if="section.columns.indexOf(column)"
|
||||
class="btn btn-xs btn-icon"
|
||||
|
|
@ -128,9 +130,6 @@ function move_columns_to_section() {
|
|||
>
|
||||
<div v-html="frappe.utils.icon('move', 'sm')"></div>
|
||||
</button>
|
||||
<button class="btn btn-xs btn-icon" :title="__('Add Column')" @click="add_column">
|
||||
<div v-html="frappe.utils.icon('add', 'sm')"></div>
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-xs btn-icon"
|
||||
:title="__('Remove Column')"
|
||||
|
|
@ -145,7 +144,6 @@ function move_columns_to_section() {
|
|||
</div>
|
||||
<draggable
|
||||
class="column-container"
|
||||
:style="{ backgroundColor: column.fields.length ? '' : 'var(--field-placeholder-color)' }"
|
||||
v-model="column.fields"
|
||||
group="fields"
|
||||
:animation="200"
|
||||
|
|
@ -161,11 +159,19 @@ function move_columns_to_section() {
|
|||
/>
|
||||
</template>
|
||||
</draggable>
|
||||
<div
|
||||
class="empty-column"
|
||||
:hidden="store.read_only"
|
||||
:style="store.selected(column.df.name) ? { top: '35px' } : { top: 0 }"
|
||||
>
|
||||
<AddFieldButton :column="column" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.column {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
|
|
@ -250,6 +256,35 @@ function move_columns_to_section() {
|
|||
flex: 1;
|
||||
min-height: 2rem;
|
||||
border-radius: var(--border-radius);
|
||||
z-index: 1;
|
||||
|
||||
&:empty {
|
||||
& + .empty-column {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
gap: 5px;
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
|
||||
button {
|
||||
background-color: var(--bg-color);
|
||||
z-index: 2;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--btn-default-hover-bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& + .empty-column {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,75 +1,19 @@
|
|||
<script setup>
|
||||
import EditableInput from "./EditableInput.vue";
|
||||
import Dropdown from "./Dropdown.vue";
|
||||
import { useStore } from "../store";
|
||||
import { move_children_to_parent, clone_field } from "../utils";
|
||||
import { createPopper } from "@popperjs/core";
|
||||
import { ref, computed, watch } from "vue";
|
||||
import { ref, computed } from "vue";
|
||||
import AddFieldButton from "./AddFieldButton.vue";
|
||||
|
||||
const props = defineProps(["column", "field"]);
|
||||
const store = useStore();
|
||||
|
||||
const hovered = ref(false);
|
||||
const selected = computed(() => store.selected(props.field.df.name));
|
||||
const component = computed(() => {
|
||||
return props.field.df.fieldtype.replace(" ", "") + "Control";
|
||||
});
|
||||
|
||||
const show_fieldtype_dropdown = ref(false);
|
||||
const search_text = ref("");
|
||||
const fields = computed(() => {
|
||||
let fields = frappe.model.all_fieldtypes
|
||||
.filter((df) => {
|
||||
if (in_list(frappe.model.layout_fields, df)) {
|
||||
return false;
|
||||
}
|
||||
if (search_text.value) {
|
||||
if (df.toLowerCase().includes(search_text.value.toLowerCase())) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
})
|
||||
.map((df) => {
|
||||
let out = {
|
||||
label: df,
|
||||
onClick: () => {
|
||||
let new_field = {
|
||||
df: store.get_df(df),
|
||||
table_columns: [],
|
||||
};
|
||||
|
||||
add_new_field(clone_field(new_field));
|
||||
},
|
||||
};
|
||||
return out;
|
||||
});
|
||||
return [...fields];
|
||||
});
|
||||
|
||||
const add_field_btn_ref = ref(null);
|
||||
const dropdown_ref = ref(null);
|
||||
const popper = ref(null);
|
||||
|
||||
function setupPopper() {
|
||||
if (!popper.value) {
|
||||
popper.value = createPopper(add_field_btn_ref.value, dropdown_ref.value, {
|
||||
placement: "bottom-end",
|
||||
modifiers: [
|
||||
{
|
||||
name: "offset",
|
||||
options: {
|
||||
offset: [0, 4],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
} else {
|
||||
popper.value.update();
|
||||
}
|
||||
}
|
||||
|
||||
function remove_field() {
|
||||
if (store.is_customize_form && props.field.df.is_custom_field == 0) {
|
||||
frappe.msgprint(__("Cannot delete standard field. You can hide it if you want"));
|
||||
|
|
@ -111,36 +55,11 @@ function duplicate_field() {
|
|||
props.column.fields.splice(index + 1, 0, duplicate_field);
|
||||
store.form.selected_field = duplicate_field.df;
|
||||
}
|
||||
|
||||
function add_new_field(field) {
|
||||
// insert new field after current field
|
||||
let index = props.column.fields.indexOf(props.field);
|
||||
props.column.fields.splice(index + 1, 0, field);
|
||||
store.form.selected_field = field.df;
|
||||
show_fieldtype_dropdown.value = false;
|
||||
hovered.value = false;
|
||||
}
|
||||
|
||||
function toggle_fieldtype_dropdown() {
|
||||
show_fieldtype_dropdown.value = !show_fieldtype_dropdown.value;
|
||||
search_text.value = "";
|
||||
setupPopper();
|
||||
}
|
||||
|
||||
watch(hovered, (val) => {
|
||||
if (val && store.form.selected_field && store.form.selected_field != props.field.df) {
|
||||
show_fieldtype_dropdown.value = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
'field',
|
||||
hovered ? 'hovered' : '',
|
||||
store.selected(field.df.name) ? 'selected' : '',
|
||||
]"
|
||||
:class="['field', selected ? 'selected' : hovered ? 'hovered' : '']"
|
||||
:title="field.df.fieldname"
|
||||
@click.stop="store.form.selected_field = field.df"
|
||||
@mouseover.stop="hovered = true"
|
||||
|
|
@ -170,20 +89,13 @@ watch(hovered, (val) => {
|
|||
</template>
|
||||
<template #actions>
|
||||
<div class="field-actions" :hidden="store.read_only">
|
||||
<button
|
||||
ref="add_field_btn_ref"
|
||||
class="add-field-btn btn btn-xs btn-icon"
|
||||
@click="toggle_fieldtype_dropdown"
|
||||
<AddFieldButton
|
||||
:column="column"
|
||||
:field="field"
|
||||
@update_parent="hovered = false"
|
||||
>
|
||||
<div v-html="frappe.utils.icon('add', 'sm')" />
|
||||
<div class="drop-down" ref="dropdown_ref">
|
||||
<Dropdown
|
||||
v-if="show_fieldtype_dropdown"
|
||||
:items="fields"
|
||||
v-model="search_text"
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
</AddFieldButton>
|
||||
<button
|
||||
v-if="column.fields.indexOf(field)"
|
||||
class="btn btn-xs btn-icon"
|
||||
|
|
@ -194,10 +106,18 @@ watch(hovered, (val) => {
|
|||
>
|
||||
<div v-html="frappe.utils.icon('move', 'sm')"></div>
|
||||
</button>
|
||||
<button class="btn btn-xs btn-icon" @click.stop="duplicate_field">
|
||||
<button
|
||||
class="btn btn-xs btn-icon"
|
||||
:title="__('Duplicate field')"
|
||||
@click.stop="duplicate_field"
|
||||
>
|
||||
<div v-html="frappe.utils.icon('duplicate', 'sm')"></div>
|
||||
</button>
|
||||
<button class="btn btn-xs btn-icon" @click.stop="remove_field">
|
||||
<button
|
||||
class="btn btn-xs btn-icon"
|
||||
:title="__('Remove field')"
|
||||
@click.stop="remove_field"
|
||||
>
|
||||
<div v-html="frappe.utils.icon('remove', 'sm')"></div>
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -122,6 +122,13 @@ function move_sections_to_tab() {
|
|||
></div>
|
||||
</div>
|
||||
<div class="section-actions" :hidden="store.read_only">
|
||||
<button
|
||||
class="btn btn-xs btn-section"
|
||||
:title="__('Add section above')"
|
||||
@click="add_section_above"
|
||||
>
|
||||
<div v-html="frappe.utils.icon('add', 'sm')"></div>
|
||||
</button>
|
||||
<button
|
||||
v-if="tab.sections.indexOf(section)"
|
||||
class="btn btn-xs btn-section"
|
||||
|
|
@ -130,13 +137,6 @@ function move_sections_to_tab() {
|
|||
>
|
||||
<div v-html="frappe.utils.icon('move', 'sm')"></div>
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-xs btn-section"
|
||||
:title="__('Add section above')"
|
||||
@click="add_section_above"
|
||||
>
|
||||
<div v-html="frappe.utils.icon('add', 'sm')"></div>
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-xs btn-section"
|
||||
:title="__('Remove section')"
|
||||
|
|
|
|||
|
|
@ -289,13 +289,14 @@ function delete_tab(with_children) {
|
|||
position: relative;
|
||||
|
||||
&.active {
|
||||
display: block;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.tab-content-container {
|
||||
flex: 1;
|
||||
min-height: 4rem;
|
||||
background-color: var(--field-placeholder-color);
|
||||
border-radius: var(--border-radius);
|
||||
z-index: 1;
|
||||
|
||||
&:empty {
|
||||
height: 7rem;
|
||||
|
|
@ -305,14 +306,16 @@ function delete_tab(with_children) {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
gap: 5px;
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
|
||||
&button:hover {
|
||||
background-color: var(--border-color);
|
||||
button {
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue