360 lines
6.7 KiB
Vue
360 lines
6.7 KiB
Vue
<template>
|
|
<div class="field" :title="df.fieldname" @click="editing = true">
|
|
<div class="field-controls">
|
|
<div>
|
|
<div
|
|
class="custom-html"
|
|
v-if="df.fieldtype == 'HTML' && df.html"
|
|
v-html="df.html"
|
|
></div>
|
|
<div
|
|
class="custom-html"
|
|
v-if="df.fieldtype == 'Field Template'"
|
|
>
|
|
{{ df.label }}
|
|
</div>
|
|
<input
|
|
v-else-if="editing && df.fieldtype != 'HTML'"
|
|
ref="label-input"
|
|
class="label-input"
|
|
type="text"
|
|
:placeholder="__('Label')"
|
|
v-model="df.label"
|
|
@keydown.enter="editing = false"
|
|
@blur="editing = false"
|
|
/>
|
|
<span v-else-if="df.label">{{ df.label }}</span>
|
|
<i class="text-muted" v-else>
|
|
{{ __("No Label") }} ({{ df.fieldname }})
|
|
</i>
|
|
</div>
|
|
<div class="field-actions">
|
|
<button
|
|
v-if="df.fieldtype == 'HTML'"
|
|
class="btn btn-xs btn-icon"
|
|
@click="edit_html"
|
|
>
|
|
<svg class="icon icon-sm">
|
|
<use xlink:href="#icon-edit"></use>
|
|
</svg>
|
|
</button>
|
|
<button
|
|
v-if="df.fieldtype == 'Table'"
|
|
class="btn btn-xs btn-default"
|
|
@click="configure_columns"
|
|
>
|
|
Configure columns
|
|
</button>
|
|
<button
|
|
class="btn btn-xs btn-icon"
|
|
@click="$set(df, 'remove', true)"
|
|
>
|
|
<svg class="icon icon-sm">
|
|
<use xlink:href="#icon-close"></use>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div
|
|
v-if="df.fieldtype == 'Table'"
|
|
class="table-controls row no-gutters"
|
|
:style="{ opacity: 1 }"
|
|
>
|
|
<div
|
|
class="table-column"
|
|
:style="{ width: tf.width + '%' }"
|
|
v-for="(tf, i) in df.table_columns"
|
|
:key="tf.fieldname"
|
|
>
|
|
<div class="table-field">
|
|
{{ tf.label }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
import draggable from "vuedraggable";
|
|
import ConfigureColumnsVue from "./ConfigureColumns.vue";
|
|
import { storeMixin } from "./store";
|
|
|
|
export default {
|
|
name: "Field",
|
|
mixins: [storeMixin],
|
|
props: ["df"],
|
|
components: {
|
|
draggable
|
|
},
|
|
data() {
|
|
return {
|
|
editing: false
|
|
};
|
|
},
|
|
watch: {
|
|
editing(value) {
|
|
if (value) {
|
|
this.$nextTick(() => this.$refs["label-input"].focus());
|
|
}
|
|
},
|
|
"df.table_columns": {
|
|
deep: true,
|
|
handler() {
|
|
this.validate_table_columns();
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
edit_html() {
|
|
let d = new frappe.ui.Dialog({
|
|
title: __("Edit HTML"),
|
|
fields: [
|
|
{
|
|
label: __("HTML"),
|
|
fieldname: "html",
|
|
fieldtype: "Code",
|
|
options: "HTML"
|
|
}
|
|
],
|
|
primary_action: ({ html }) => {
|
|
html = frappe.dom.remove_script_and_style(html);
|
|
this.$set(this.df, "html", html);
|
|
d.hide();
|
|
}
|
|
});
|
|
d.set_value("html", this.df.html);
|
|
d.show();
|
|
},
|
|
configure_columns() {
|
|
let dialog = new frappe.ui.Dialog({
|
|
title: __("Configure columns for {0}", [this.df.label]),
|
|
fields: [
|
|
{
|
|
fieldtype: "HTML",
|
|
fieldname: "columns_area"
|
|
},
|
|
{
|
|
label: "",
|
|
fieldtype: "Autocomplete",
|
|
placeholder: __("Add Column"),
|
|
fieldname: "add_column",
|
|
options: this.get_all_columns(),
|
|
onchange: () => {
|
|
let fieldname = dialog.get_value("add_column");
|
|
if (fieldname) {
|
|
let column = this.get_column_to_add(fieldname);
|
|
if (column) {
|
|
this.df.table_columns.push(column);
|
|
this.$set(
|
|
this.df,
|
|
"table_columns",
|
|
this.df.table_columns
|
|
);
|
|
dialog.set_value("add_column", "");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
],
|
|
on_page_show: () => {
|
|
new Vue({
|
|
el: dialog.get_field("columns_area").$wrapper.get(0),
|
|
render: h =>
|
|
h(ConfigureColumnsVue, {
|
|
props: {
|
|
df: this.df
|
|
}
|
|
})
|
|
});
|
|
},
|
|
on_hide: () => {
|
|
this.$set(
|
|
this.df,
|
|
"table_columns",
|
|
this.df.table_columns.filter(col => !col.invalid_width)
|
|
);
|
|
}
|
|
});
|
|
dialog.show();
|
|
},
|
|
get_all_columns() {
|
|
let meta = frappe.get_meta(this.df.options);
|
|
let more_columns = [
|
|
{
|
|
label: __("Sr No."),
|
|
value: "idx"
|
|
}
|
|
];
|
|
return more_columns.concat(
|
|
meta.fields
|
|
.map(tf => {
|
|
if (frappe.model.no_value_type.includes(tf.fieldtype)) {
|
|
return;
|
|
}
|
|
return {
|
|
label: tf.label,
|
|
value: tf.fieldname
|
|
};
|
|
})
|
|
.filter(Boolean)
|
|
);
|
|
},
|
|
get_column_to_add(fieldname) {
|
|
let standard_columns = {
|
|
idx: {
|
|
label: __("Sr No."),
|
|
fieldtype: "Data",
|
|
fieldname: "idx",
|
|
width: 10
|
|
}
|
|
};
|
|
|
|
if (fieldname in standard_columns) {
|
|
return standard_columns[fieldname];
|
|
}
|
|
|
|
return {
|
|
...frappe.meta.get_docfield(this.df.options, fieldname),
|
|
width: 10
|
|
};
|
|
},
|
|
validate_table_columns() {
|
|
if (this.df.fieldtype != "Table") return;
|
|
|
|
let columns = this.df.table_columns;
|
|
let total_width = 0;
|
|
for (let column of columns) {
|
|
if (!column.width) {
|
|
column.width = 10;
|
|
}
|
|
total_width += column.width;
|
|
if (total_width > 100) {
|
|
column.invalid_width = true;
|
|
} else {
|
|
column.invalid_width = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
<style>
|
|
.field {
|
|
text-align: left;
|
|
width: 100%;
|
|
background-color: var(--bg-light-gray);
|
|
border-radius: var(--border-radius);
|
|
border: 1px dashed var(--gray-400);
|
|
padding: 0.5rem 0.75rem;
|
|
font-size: var(--text-sm);
|
|
}
|
|
|
|
.field-controls {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.field:not(:first-child) {
|
|
margin-top: 0.5rem;
|
|
}
|
|
|
|
.custom-html {
|
|
padding-right: var(--padding-xs);
|
|
word-break: break-all;
|
|
}
|
|
|
|
.label-input {
|
|
background-color: transparent;
|
|
border: none;
|
|
padding: 0;
|
|
}
|
|
|
|
.label-input:focus {
|
|
outline: none;
|
|
}
|
|
|
|
.field:focus-within {
|
|
border-style: solid;
|
|
border-color: var(--gray-600);
|
|
}
|
|
|
|
.field-actions {
|
|
flex: none;
|
|
}
|
|
|
|
.field-actions .btn {
|
|
opacity: 0;
|
|
}
|
|
|
|
.field-actions .btn-icon {
|
|
box-shadow: none;
|
|
}
|
|
|
|
.btn-icon {
|
|
padding: 2px;
|
|
}
|
|
|
|
.btn-icon:hover {
|
|
background-color: white;
|
|
}
|
|
|
|
.field:hover .btn {
|
|
opacity: 1;
|
|
}
|
|
|
|
.table-controls {
|
|
display: flex;
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
.table-column {
|
|
position: relative;
|
|
}
|
|
|
|
.table-field {
|
|
text-align: left;
|
|
width: 100%;
|
|
background-color: white;
|
|
border-radius: var(--border-radius);
|
|
border: 1px dashed var(--gray-400);
|
|
padding: 0.5rem 0.75rem;
|
|
font-size: var(--text-sm);
|
|
user-select: none;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.column-resize {
|
|
position: absolute;
|
|
right: 0;
|
|
top: 0;
|
|
width: 6px;
|
|
border-radius: 2px;
|
|
height: 80%;
|
|
background-color: var(--gray-600);
|
|
transform: translate(50%, 10%);
|
|
z-index: 999;
|
|
cursor: col-resize;
|
|
}
|
|
|
|
.column-resize-actions {
|
|
position: absolute;
|
|
top: 0;
|
|
right: 0;
|
|
height: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
padding-right: 0.25rem;
|
|
}
|
|
|
|
.column-resize-actions .btn-icon {
|
|
background: white;
|
|
}
|
|
.column-resize-actions .btn-icon:hover {
|
|
background: var(--bg-light-gray);
|
|
}
|
|
|
|
.columns-input {
|
|
padding: var(--padding-sm);
|
|
}
|
|
</style>
|