fix: add new field btn in empty column

also created AddFieldButton component
This commit is contained in:
Shariq Ansari 2023-10-31 00:35:00 +05:30
parent bdbb11fbb9
commit 5f8887d794
5 changed files with 212 additions and 127 deletions

View 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>

View file

@ -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>

View file

@ -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>

View file

@ -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')"

View file

@ -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;
}
}
}