- Print Format Builder Beta page - Add margin fields in Print Format - Using vuedraggable for drag and dropversion-14
@@ -19,6 +19,10 @@ | |||
"html", | |||
"raw_commands", | |||
"section_break_9", | |||
"margin_top", | |||
"margin_bottom", | |||
"margin_left", | |||
"margin_right", | |||
"align_labels_right", | |||
"show_section_headings", | |||
"line_breaks", | |||
@@ -31,7 +35,8 @@ | |||
"section_break_13", | |||
"print_format_help", | |||
"format_data", | |||
"print_format_builder" | |||
"print_format_builder", | |||
"print_format_builder_beta" | |||
], | |||
"fields": [ | |||
{ | |||
@@ -205,13 +210,43 @@ | |||
"fieldname": "absolute_value", | |||
"fieldtype": "Check", | |||
"label": "Show Absolute Values" | |||
}, | |||
{ | |||
"default": "0", | |||
"fieldname": "print_format_builder_beta", | |||
"fieldtype": "Check", | |||
"label": "Print Format Builder Beta" | |||
}, | |||
{ | |||
"default": "15", | |||
"fieldname": "margin_top", | |||
"fieldtype": "Float", | |||
"label": "Margin Top" | |||
}, | |||
{ | |||
"default": "15", | |||
"fieldname": "margin_bottom", | |||
"fieldtype": "Float", | |||
"label": "Margin Bottom" | |||
}, | |||
{ | |||
"default": "15", | |||
"fieldname": "margin_left", | |||
"fieldtype": "Float", | |||
"label": "Margin Left" | |||
}, | |||
{ | |||
"default": "15", | |||
"fieldname": "margin_right", | |||
"fieldtype": "Float", | |||
"label": "Margin Right" | |||
} | |||
], | |||
"icon": "fa fa-print", | |||
"idx": 1, | |||
"index_web_pages_for_search": 1, | |||
"links": [], | |||
"modified": "2021-03-01 15:25:46.578863", | |||
"modified": "2021-07-11 11:53:52.028982", | |||
"modified_by": "Administrator", | |||
"module": "Printing", | |||
"name": "Print Format", | |||
@@ -38,6 +38,10 @@ class PrintFormat(Document): | |||
def extract_images(self): | |||
from frappe.core.doctype.file.file import extract_images_from_html | |||
if self.print_format_builder_beta: | |||
return | |||
if self.format_data: | |||
data = json.loads(self.format_data) | |||
for df in data: | |||
@@ -258,6 +258,11 @@ frappe.ui.form.PrintView = class { | |||
fieldtype: 'Read Only', | |||
default: print_format.name || 'Standard', | |||
}, | |||
{ | |||
label: __('Use the new Print Format Builder Beta'), | |||
fieldname: 'beta', | |||
fieldtype: 'Check' | |||
}, | |||
], | |||
(data) => { | |||
frappe.route_options = { | |||
@@ -265,6 +270,7 @@ frappe.ui.form.PrintView = class { | |||
doctype: this.frm.doctype, | |||
name: data.print_format_name, | |||
based_on: data.based_on, | |||
beta: data.beta | |||
}; | |||
frappe.set_route('print-format-builder'); | |||
this.print_sel.val(data.print_format_name); | |||
@@ -12,9 +12,9 @@ frappe.pages['print-format-builder'].on_page_show = function(wrapper) { | |||
}); | |||
} else if(frappe.route_options) { | |||
if(frappe.route_options.make_new) { | |||
let { doctype, name, based_on } = frappe.route_options; | |||
let { doctype, name, based_on, beta } = frappe.route_options; | |||
frappe.route_options = null; | |||
frappe.print_format_builder.setup_new_print_format(doctype, name, based_on); | |||
frappe.print_format_builder.setup_new_print_format(doctype, name, based_on, beta); | |||
} else { | |||
frappe.print_format_builder.print_format = frappe.route_options.doc; | |||
frappe.route_options = null; | |||
@@ -126,18 +126,22 @@ frappe.PrintFormatBuilder = class PrintFormatBuilder { | |||
}); | |||
} | |||
setup_new_print_format(doctype, name, based_on) { | |||
setup_new_print_format(doctype, name, based_on, beta) { | |||
frappe.call({ | |||
method: 'frappe.printing.page.print_format_builder.print_format_builder.create_custom_format', | |||
args: { | |||
doctype: doctype, | |||
name: name, | |||
based_on: based_on | |||
based_on: based_on, | |||
beta: Boolean(beta) | |||
}, | |||
callback: (r) => { | |||
if(!r.exc) { | |||
if(r.message) { | |||
this.print_format = r.message; | |||
if(r.message) { | |||
let print_format = r.message; | |||
if (print_format.print_format_builder_beta) { | |||
frappe.set_route('print-format-builder-beta', print_format.name); | |||
} else { | |||
this.print_format = print_format; | |||
this.refresh(); | |||
} | |||
} | |||
@@ -1,11 +1,16 @@ | |||
import frappe | |||
@frappe.whitelist() | |||
def create_custom_format(doctype, name, based_on='Standard'): | |||
def create_custom_format(doctype, name, based_on='Standard', beta=False): | |||
doc = frappe.new_doc('Print Format') | |||
doc.doc_type = doctype | |||
doc.name = name | |||
doc.print_format_builder = 1 | |||
beta = frappe.parse_json(beta) | |||
if beta: | |||
doc.print_format_builder_beta = 1 | |||
else: | |||
doc.print_format_builder = 1 | |||
doc.format_data = frappe.db.get_value('Print Format', based_on, 'format_data') \ | |||
if based_on != 'Standard' else None | |||
doc.insert() |
@@ -0,0 +1,3 @@ | |||
.layout-main-section-wrapper { | |||
margin-bottom: 0; | |||
} |
@@ -0,0 +1,31 @@ | |||
frappe.pages["print-format-builder-beta"].on_page_load = function(wrapper) { | |||
var page = frappe.ui.make_app_page({ | |||
parent: wrapper, | |||
title: __("Print Format Builder"), | |||
single_column: true | |||
}); | |||
function load_print_format_builder_beta() { | |||
let route = frappe.get_route(); | |||
let $parent = $(wrapper).find(".layout-main-section"); | |||
$parent.empty(); | |||
if (route.length > 1) { | |||
frappe.require("print_format_builder.bundle.js").then(() => { | |||
frappe.print_format_builder = new frappe.ui.PrintFormatBuilder({ | |||
wrapper: $parent, | |||
page, | |||
print_format: route[1] | |||
}); | |||
}); | |||
} | |||
} | |||
load_print_format_builder_beta(); | |||
// hot reload in development | |||
if (frappe.boot.developer_mode) { | |||
frappe.hot_update = frappe.hot_update || []; | |||
frappe.hot_update.push(load_print_format_builder_beta); | |||
} | |||
}; |
@@ -0,0 +1,22 @@ | |||
{ | |||
"content": null, | |||
"creation": "2021-07-10 12:22:16.138485", | |||
"docstatus": 0, | |||
"doctype": "Page", | |||
"idx": 0, | |||
"modified": "2021-07-10 12:22:16.138485", | |||
"modified_by": "Administrator", | |||
"module": "Printing", | |||
"name": "print-format-builder-beta", | |||
"owner": "Administrator", | |||
"page_name": "Print Format Builder Beta", | |||
"roles": [ | |||
{ | |||
"role": "System Manager" | |||
} | |||
], | |||
"script": null, | |||
"standard": "Yes", | |||
"style": null, | |||
"system_page": 0 | |||
} |
@@ -0,0 +1,73 @@ | |||
<template> | |||
<div class="print-format-main" :style="rootStyles"> | |||
<draggable | |||
v-model="layout.sections" | |||
group="sections" | |||
filter=".section-columns, .column, .field" | |||
:animation="200" | |||
> | |||
<PrintFormatSection | |||
v-for="(section, i) in layout.sections" | |||
:key="i" | |||
:section="section" | |||
@add_section_above="add_section_above(section)" | |||
/> | |||
</draggable> | |||
</div> | |||
</template> | |||
<script> | |||
import draggable from "vuedraggable"; | |||
import PrintFormatSection from "./PrintFormatSection.vue"; | |||
export default { | |||
name: "PrintFormat", | |||
props: ["print_format", "meta", "layout"], | |||
components: { | |||
draggable, | |||
PrintFormatSection, | |||
}, | |||
computed: { | |||
rootStyles() { | |||
let { | |||
margin_top = 0, | |||
margin_bottom = 0, | |||
margin_left = 0, | |||
margin_right = 0, | |||
} = this.print_format; | |||
return { | |||
padding: `${margin_top}mm ${margin_right}mm ${margin_bottom}mm ${margin_left}mm`, | |||
width: "210mm", | |||
minHeight: "297mm", | |||
}; | |||
}, | |||
}, | |||
methods: { | |||
add_section_above(section) { | |||
let sections = []; | |||
for (let _section of this.layout.sections) { | |||
if (_section === section) { | |||
sections.push({ | |||
label: "", | |||
columns: [ | |||
{ label: "", fields: [] }, | |||
{ label: "", fields: [] }, | |||
], | |||
}); | |||
} | |||
sections.push(_section); | |||
} | |||
this.$set(this.layout, "sections", sections); | |||
}, | |||
}, | |||
}; | |||
</script> | |||
<style scoped> | |||
.print-format-main { | |||
margin-left: auto; | |||
background-color: white; | |||
box-shadow: var(--shadow-lg); | |||
border-radius: var(--border-radius); | |||
} | |||
</style> |
@@ -0,0 +1,110 @@ | |||
<template> | |||
<div class="layout-main-section row" v-if="print_format && meta && layout"> | |||
<div class="col-3"> | |||
<PrintFormatControls | |||
:print_format="print_format" | |||
:meta="meta" | |||
@update="update($event)" | |||
/> | |||
</div> | |||
<div class="print-format-container col-9"> | |||
<PrintFormat :print_format="print_format" :meta="meta" :layout="layout" /> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import PrintFormat from "./PrintFormat.vue"; | |||
import PrintFormatControls from "./PrintFormatControls.vue"; | |||
import { create_default_layout } from "./utils"; | |||
export default { | |||
name: "PrintFormatBuilder", | |||
props: ["print_format_name"], | |||
components: { | |||
PrintFormat, | |||
PrintFormatControls, | |||
}, | |||
data() { | |||
return { | |||
print_format: null, | |||
doctype: null, | |||
meta: null, | |||
layout: null, | |||
}; | |||
}, | |||
mounted() { | |||
this.fetch(); | |||
}, | |||
methods: { | |||
fetch() { | |||
frappe.dom.freeze(__("Loading...")); | |||
frappe.model.clear_doc("Print Format", this.print_format_name); | |||
frappe.model.with_doc("Print Format", this.print_format_name, () => { | |||
this.print_format = frappe.get_doc( | |||
"Print Format", | |||
this.print_format_name | |||
); | |||
frappe.model.with_doctype(this.print_format.doc_type, () => { | |||
this.meta = frappe.get_meta(this.print_format.doc_type); | |||
this.layout = this.get_layout(); | |||
frappe.dom.unfreeze(); | |||
}); | |||
}); | |||
}, | |||
update({ fieldname, value }) { | |||
this.$set(this.print_format, fieldname, value); | |||
}, | |||
save_changes() { | |||
frappe.dom.freeze(); | |||
this.layout.sections = this.layout.sections | |||
.map((section) => { | |||
section.columns = section.columns.map((column) => { | |||
column.fields = column.fields.filter((df) => !df.remove); | |||
return column; | |||
}); | |||
return section.remove ? null : section; | |||
}) | |||
.filter(Boolean); | |||
this.print_format.format_data = JSON.stringify(this.layout); | |||
frappe | |||
.call("frappe.client.save", { | |||
doc: this.print_format, | |||
}) | |||
.then(() => { | |||
this.fetch(); | |||
}) | |||
.always(() => { | |||
frappe.dom.unfreeze(); | |||
}); | |||
}, | |||
reset_changes() { | |||
this.fetch(); | |||
}, | |||
get_layout() { | |||
if (this.print_format) { | |||
if (!this.print_format.format_data) { | |||
return create_default_layout(this.meta); | |||
} | |||
if (typeof this.print_format.format_data == "string") { | |||
return JSON.parse(this.print_format.format_data); | |||
} | |||
return this.print_format.format_data; | |||
} | |||
return null; | |||
}, | |||
}, | |||
}; | |||
</script> | |||
<style scoped> | |||
.print-format-container { | |||
height: calc(100vh - 140px); | |||
overflow-y: auto; | |||
padding-top: 0.5rem; | |||
padding-bottom: 4rem; | |||
} | |||
</style> |
@@ -0,0 +1,163 @@ | |||
<template> | |||
<div class="layout-side-section"> | |||
<div class="form-sidebar"> | |||
<div class="sidebar-menu"> | |||
<div class="sidebar-label">{{ __("Page Margins") }}</div> | |||
<div class="margin-controls"> | |||
<div class="form-group" v-for="df in margins" :key="df.fieldname"> | |||
<div class="clearfix"> | |||
<label class="control-label"> {{ df.label }} </label> | |||
</div> | |||
<div class="control-input-wrapper"> | |||
<div class="control-input"> | |||
<input | |||
type="number" | |||
class="form-control form-control-sm" | |||
:value="print_format[df.fieldname]" | |||
@change="(e) => update_margin(df.fieldname, e.target.value)" | |||
/> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="sidebar-menu"> | |||
<div class="sidebar-label">{{ __("Fields") }}</div> | |||
<input | |||
class="form-control form-control-sm mb-2" | |||
type="text" | |||
:placeholder="__('Search fields')" | |||
v-model="search_text" | |||
/> | |||
<draggable | |||
class="fields-container" | |||
:list="fields" | |||
:group="{ name: 'fields', pull: 'clone', put: false }" | |||
:sort="false" | |||
> | |||
<div class="field" v-for="df in fields" :key="df.fieldname"> | |||
{{ df.label }} | |||
</div> | |||
</draggable> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import draggable from "vuedraggable"; | |||
import { get_table_columns } from "./utils"; | |||
export default { | |||
name: "PrintFormatControls", | |||
props: ["print_format", "meta"], | |||
data() { | |||
return { | |||
search_text: "", | |||
}; | |||
}, | |||
components: { | |||
draggable, | |||
}, | |||
methods: { | |||
update_margin(fieldname, value) { | |||
value = parseFloat(value); | |||
if (value < 0) { | |||
value = 0; | |||
} | |||
this.$emit("update", { fieldname, value }); | |||
}, | |||
}, | |||
computed: { | |||
margins() { | |||
return [ | |||
{ label: __("Top"), fieldname: "margin_top" }, | |||
{ label: __("Bottom"), fieldname: "margin_bottom" }, | |||
{ label: __("Left"), fieldname: "margin_left" }, | |||
{ label: __("Right"), fieldname: "margin_right" }, | |||
]; | |||
}, | |||
fields() { | |||
let fields = this.meta.fields | |||
.filter((df) => { | |||
if (["Section Break", "Column Break"].includes(df.fieldtype)) { | |||
return false; | |||
} | |||
if (this.search_text) { | |||
if (df.fieldname.includes(this.search_text)) { | |||
return true; | |||
} | |||
if (df.label && df.label.includes(this.search_text)) { | |||
return true; | |||
} | |||
return false; | |||
} else { | |||
return true; | |||
} | |||
}) | |||
.map((df) => { | |||
let out = { | |||
label: df.label, | |||
fieldname: df.fieldname, | |||
options: df.options, | |||
reqd: df.reqd, | |||
}; | |||
if (df.fieldtype == "Table") { | |||
out.table_columns = get_table_columns(df); | |||
} | |||
return out; | |||
}); | |||
return [ | |||
{ | |||
label: "Custom HTML", | |||
fieldname: "custom_html", | |||
fieldtype: "HTML", | |||
html: "", | |||
}, | |||
...fields, | |||
]; | |||
}, | |||
}, | |||
}; | |||
</script> | |||
<style scoped> | |||
.margin-controls { | |||
display: flex; | |||
} | |||
.margin-controls .form-control { | |||
background: white; | |||
} | |||
.margin-controls > .form-group + .form-group { | |||
margin-left: 0.5rem; | |||
} | |||
.fields-container { | |||
max-height: calc(100vh - 22rem); | |||
overflow-y: auto; | |||
} | |||
.field { | |||
display: flex; | |||
justify-content: space-between; | |||
align-items: center; | |||
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); | |||
cursor: pointer; | |||
} | |||
.field:not(:first-child) { | |||
margin-top: 0.5rem; | |||
} | |||
.sidebar-menu:last-child { | |||
margin-bottom: 0; | |||
} | |||
</style> |
@@ -0,0 +1,203 @@ | |||
<template> | |||
<div class="print-format-section" v-if="!section.remove"> | |||
<div class="section-header"> | |||
<input | |||
class="input-section-label w-50" | |||
type="text" | |||
:placeholder="__('Section Title')" | |||
v-model="section.label" | |||
/> | |||
<div class="dropdown"> | |||
<button | |||
class="btn btn-xs btn-section dropdown-button" | |||
data-toggle="dropdown" | |||
> | |||
<svg class="icon icon-sm"> | |||
<use xlink:href="#icon-dot-horizontal"></use> | |||
</svg> | |||
</button> | |||
<div class="dropdown-menu dropdown-menu-right" role="menu"> | |||
<button | |||
class="dropdown-item" | |||
@click="add_column" | |||
v-if="section.columns.length < 4" | |||
> | |||
{{ __("Add column") }} | |||
</button> | |||
<button | |||
class="dropdown-item" | |||
@click="remove_column" | |||
v-if="section.columns.length > 1" | |||
> | |||
{{ __("Remove column") }} | |||
</button> | |||
<button class="dropdown-item" @click="$emit('add_section_above')"> | |||
{{ __("Add section above") }} | |||
</button> | |||
<button class="dropdown-item" @click="$set(section, 'remove', true)"> | |||
{{ __("Remove section") }} | |||
</button> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="row section-columns"> | |||
<div class="column col" v-for="(column, i) in section.columns" :key="i"> | |||
<draggable | |||
class="drag-container" | |||
v-model="column.fields" | |||
group="fields" | |||
:animation="150" | |||
> | |||
<button | |||
class="field" | |||
v-for="df in get_fields(column)" | |||
:key="df.fieldname" | |||
> | |||
<div> | |||
{{ df.label }} | |||
</div> | |||
<button | |||
class="btn btn-xs btn-remove-field" | |||
@click="$set(df, 'remove', true)" | |||
> | |||
<svg class="icon icon-sm"> | |||
<use xlink:href="#icon-close"></use> | |||
</svg> | |||
</button> | |||
</button> | |||
</draggable> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import draggable from "vuedraggable"; | |||
export default { | |||
name: "PrintFormatSection", | |||
props: ["section"], | |||
components: { | |||
draggable, | |||
}, | |||
methods: { | |||
add_column() { | |||
if (this.section.columns.length < 4) { | |||
this.section.columns.push({ | |||
label: "", | |||
fields: [], | |||
}); | |||
} | |||
}, | |||
remove_column() { | |||
if (this.section.columns.length <= 1) return; | |||
let columns = this.section.columns.slice(); | |||
let last_column_fields = columns.slice(-1)[0].fields.slice(); | |||
let index = columns.length - 1; | |||
columns = columns.slice(0, index); | |||
let last_column = columns[index - 1]; | |||
last_column.fields = [...last_column.fields, ...last_column_fields]; | |||
this.$set(this.section, "columns", columns); | |||
}, | |||
get_fields(column) { | |||
return column.fields.filter((df) => !df.remove); | |||
}, | |||
}, | |||
}; | |||
</script> | |||
<style scoped> | |||
.print-format-section { | |||
background-color: white; | |||
border: 1px solid var(--dark-border-color); | |||
border-radius: var(--border-radius); | |||
padding: 1rem; | |||
cursor: pointer; | |||
} | |||
.section-header { | |||
display: flex; | |||
justify-content: space-between; | |||
align-items: center; | |||
padding-bottom: 0.75rem; | |||
} | |||
.input-section-label { | |||
border: 1px solid transparent; | |||
border-radius: var(--border-radius); | |||
font-size: var(--text-md); | |||
font-weight: 600; | |||
} | |||
.input-section-label:focus { | |||
border-color: var(--border-color); | |||
outline: none; | |||
background-color: var(--control-bg); | |||
} | |||
.input-section-label::placeholder { | |||
font-style: italic; | |||
font-weight: normal; | |||
} | |||
.btn-section { | |||
padding: var(--padding-xs); | |||
box-shadow: none; | |||
} | |||
.btn-section:hover { | |||
background-color: var(--bg-light-gray); | |||
} | |||
.print-format-section:not(:first-child) { | |||
margin-top: 1rem; | |||
} | |||
.section-columns { | |||
margin-left: -8px; | |||
margin-right: -8px; | |||
} | |||
.column { | |||
padding-left: 8px; | |||
padding-right: 8px; | |||
} | |||
.field { | |||
display: flex; | |||
justify-content: space-between; | |||
align-items: center; | |||
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:not(:first-child) { | |||
margin-top: 0.5rem; | |||
} | |||
.btn-remove-field { | |||
opacity: 0; | |||
padding: 2px; | |||
box-shadow: none; | |||
} | |||
.btn-remove-field:hover { | |||
background-color: white; | |||
} | |||
.field:hover .btn-remove-field { | |||
opacity: 1; | |||
} | |||
.drag-container { | |||
height: 100%; | |||
min-height: 2rem; | |||
} | |||
</style> |
@@ -0,0 +1,31 @@ | |||
import PrintFormatBuilderComponent from "./PrintFormatBuilder.vue"; | |||
class PrintFormatBuilder { | |||
constructor({ wrapper, page, print_format }) { | |||
this.$wrapper = $(wrapper); | |||
this.page = page; | |||
this.print_format = print_format; | |||
this.page.set_title(__("Editing {0}", [this.print_format])); | |||
this.page.set_primary_action(__("Save changes"), () => { | |||
this.$component.save_changes(); | |||
}); | |||
this.page.set_secondary_action(__("Reset changes"), () => { | |||
this.$component.reset_changes(); | |||
}); | |||
let $vm = new Vue({ | |||
el: this.$wrapper.get(0), | |||
render: h => | |||
h(PrintFormatBuilderComponent, { | |||
props: { | |||
print_format_name: print_format | |||
} | |||
}) | |||
}); | |||
this.$component = $vm.$children[0]; | |||
} | |||
} | |||
frappe.provide("frappe.ui"); | |||
frappe.ui.PrintFormatBuilder = PrintFormatBuilder; | |||
export default PrintFormatBuilder; |
@@ -0,0 +1,100 @@ | |||
export function create_default_layout(meta) { | |||
let layout = { | |||
sections: [] | |||
}; | |||
let section = null, | |||
column = null; | |||
function set_column(df) { | |||
if (!section) { | |||
set_section(); | |||
} | |||
column = get_new_column(df); | |||
section.columns.push(column); | |||
} | |||
function set_section(df) { | |||
section = get_new_section(df); | |||
column = null; | |||
layout.sections.push(section); | |||
} | |||
function get_new_section(df) { | |||
if (!df) { | |||
df = { label: "" }; | |||
} | |||
return { | |||
label: df.label || "", | |||
columns: [] | |||
}; | |||
} | |||
function get_new_column(df) { | |||
if (!df) { | |||
df = { label: "" }; | |||
} | |||
return { | |||
label: df.label || "", | |||
fields: [] | |||
}; | |||
} | |||
for (let df of meta.fields) { | |||
if (df.fieldname) { | |||
// make a copy to avoid mutation bugs | |||
df = JSON.parse(JSON.stringify(df)); | |||
} else { | |||
continue; | |||
} | |||
if (df.fieldtype === "Section Break") { | |||
set_section(df); | |||
} else if (df.fieldtype === "Column Break") { | |||
set_column(df); | |||
} else if (df.label) { | |||
if (!column) set_column(); | |||
if (!df.print_hide) { | |||
let field = { | |||
label: df.label, | |||
fieldname: df.fieldname, | |||
options: df.options | |||
}; | |||
if (df.fieldtype === "Table") { | |||
field.table_columns = get_table_columns(df); | |||
} | |||
column.fields.push(field); | |||
section.has_fields = true; | |||
} | |||
} | |||
} | |||
// remove empty sections | |||
layout.sections = layout.sections.filter(section => section.has_fields); | |||
return layout; | |||
} | |||
export function get_table_columns(df) { | |||
let table_columns = []; | |||
let table_fields = frappe.get_meta(df.options).fields; | |||
for (let tf of table_fields) { | |||
if ( | |||
!in_list(["Section Break", "Column Break"], tf.fieldtype) && | |||
!tf.print_hide && | |||
df.label | |||
) { | |||
table_columns.push({ | |||
label: tf.label, | |||
fieldname: tf.fieldname, | |||
options: tf.options, | |||
width: tf.width || 0 | |||
}); | |||
} | |||
} | |||
return table_columns; | |||
} |
@@ -61,7 +61,8 @@ | |||
"superagent": "^3.8.2", | |||
"touch": "^3.1.0", | |||
"vue": "2.6.12", | |||
"vue-router": "^2.0.0" | |||
"vue-router": "^2.0.0", | |||
"vuedraggable": "^2.24.3" | |||
}, | |||
"devDependencies": { | |||
"chalk": "^2.3.2", | |||
@@ -6911,6 +6911,11 @@ socket.io@^2.4.0: | |||
socket.io-client "2.4.0" | |||
socket.io-parser "~3.4.0" | |||
sortablejs@1.10.2: | |||
version "1.10.2" | |||
resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.10.2.tgz#6e40364d913f98b85a14f6678f92b5c1221f5290" | |||
integrity sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A== | |||
sortablejs@^1.7.0: | |||
version "1.8.3" | |||
resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.8.3.tgz#5ae908ef96300966e95440a143340f5dd565a0df" | |||
@@ -7790,6 +7795,13 @@ vue@2.6.12: | |||
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.12.tgz#f5ebd4fa6bd2869403e29a896aed4904456c9123" | |||
integrity sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg== | |||
vuedraggable@^2.24.3: | |||
version "2.24.3" | |||
resolved "https://registry.yarnpkg.com/vuedraggable/-/vuedraggable-2.24.3.tgz#43c93849b746a24ce503e123d5b259c701ba0d19" | |||
integrity sha512-6/HDXi92GzB+Hcs9fC6PAAozK1RLt1ewPTLjK0anTYguXLAeySDmcnqE8IC0xa7shvSzRjQXq3/+dsZ7ETGF3g== | |||
dependencies: | |||
sortablejs "1.10.2" | |||
wcwidth@^1.0.1: | |||
version "1.0.1" | |||
resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" | |||