Refactor form/page component structure for Buefy/Vue.js

this also moves Execute Batch from the form buttons area, to object helper
This commit is contained in:
Lance Edgar 2019-06-17 15:07:19 -05:00
parent 4cadeb8e5d
commit cc79fe76fd
12 changed files with 270 additions and 275 deletions

View file

@ -341,7 +341,7 @@ class Form(object):
def __init__(self, fields=None, schema=None, request=None, mobile=False, readonly=False, readonly_fields=[], def __init__(self, fields=None, schema=None, request=None, mobile=False, readonly=False, readonly_fields=[],
model_instance=None, model_class=None, appstruct=UNSPECIFIED, nodes={}, enums={}, labels={}, renderers=None, model_instance=None, model_class=None, appstruct=UNSPECIFIED, nodes={}, enums={}, labels={}, renderers=None,
hidden={}, widgets={}, defaults={}, validators={}, required={}, helptext={}, focus_spec=None, hidden={}, widgets={}, defaults={}, validators={}, required={}, helptext={}, focus_spec=None,
action_url=None, cancel_url=None, use_buefy=None): action_url=None, cancel_url=None, use_buefy=None, component='tailbone-form'):
self.fields = None self.fields = None
if fields is not None: if fields is not None:
@ -377,6 +377,12 @@ class Form(object):
self.action_url = action_url self.action_url = action_url
self.cancel_url = cancel_url self.cancel_url = cancel_url
self.use_buefy = use_buefy self.use_buefy = use_buefy
self.component = component
@property
def component_studly(self):
words = self.component.split('-')
return ''.join([word.capitalize() for word in words])
def __contains__(self, item): def __contains__(self, item):
return item in self.fields return item in self.fields

View file

@ -1,10 +0,0 @@
let TailboneForm = {
template: '#tailbone-form-template',
methods: {}
}
let FormPage = {
template: '#form-page-template',
methods: {}
}

View file

@ -32,6 +32,7 @@
<%def name="extra_styles()"> <%def name="extra_styles()">
${parent.extra_styles()} ${parent.extra_styles()}
% if not use_buefy:
<style type="text/css"> <style type="text/css">
.grid-wrapper { .grid-wrapper {
@ -43,13 +44,13 @@
} }
</style> </style>
% endif
</%def> </%def>
<%def name="buttons()"> <%def name="buttons()">
<div class="buttons"> <div class="buttons">
${self.leading_buttons()} ${self.leading_buttons()}
${refresh_button()} ${refresh_button()}
${execute_button()}
</div> </div>
</%def> </%def>
@ -86,38 +87,9 @@
</once-button> </once-button>
</%def> </%def>
<%def name="execute_button()">
% if not batch.executed and request.has_perm('{}.execute'.format(permission_prefix)):
% if use_buefy:
% if master.has_execution_options(batch):
## TODO: this doesn't work yet
${execute_submit_button()}
% else:
${execute_form.render_deform(buttons=capture(execute_submit_button))|n}
% endif
% else:
## no buefy, do legacy thing
<button type="button"
class="button is-primary"
id="execute-batch"
% if not execute_enabled:
disabled="disabled"
% endif
% if why_not_execute:
title="${why_not_execute}"
% endif
>
${execute_title}
</button>
% endif
% endif
</%def>
<%def name="object_helpers()"> <%def name="object_helpers()">
${self.render_status_breakdown()} ${self.render_status_breakdown()}
${self.render_execute_helper()}
</%def> </%def>
<%def name="render_status_breakdown()"> <%def name="render_status_breakdown()">
@ -144,31 +116,101 @@
% endif % endif
</%def> </%def>
<%def name="render_execute_helper()">
<div class="object-helper">
<h3>Batch Execution</h3>
<div class="object-helper-content">
% if batch.executed:
<p>
Batch was executed
${h.pretty_datetime(request.rattail_config, batch.executed)}
by ${batch.executed_by}
</p>
% elif master.handler.executable(batch):
% if request.has_perm('{}.execute'.format(permission_prefix)):
<p>Batch has not yet been executed.</p>
% if use_buefy:
% if master.has_execution_options(batch):
## TODO: this doesn't work yet
${self.execute_submit_button()}
% else:
<execute-form></execute-form>
% endif
% else:
## no buefy, do legacy thing
<button type="button"
% if not execute_enabled:
disabled="disabled"
% endif
% if why_not_execute:
title="${why_not_execute}"
% endif
class="button is-primary"
id="execute-batch">
${execute_title}
</button>
% endif
% else:
<p>TODO: batch *may* be executed, but not by *you*</p>
% endif
% else:
<p>TODO: batch cannot be executed..?</p>
% endif
</div>
</div>
</%def>
<%def name="render_form()"> <%def name="render_form()">
## TODO: should use self.render_form_buttons() ## TODO: should use self.render_form_buttons()
## ${form.render(form_id='batch-form', buttons=capture(self.render_form_buttons))|n} ## ${form.render(form_id='batch-form', buttons=capture(self.render_form_buttons))|n}
${form.render(form_id='batch-form', buttons=capture(buttons))|n} ${form.render(form_id='batch-form', buttons=capture(buttons))|n}
</%def> </%def>
<%def name="render_this_page()">
${parent.render_this_page()}
% if not use_buefy:
% if master.handler.executable(batch) and request.has_perm('{}.execute'.format(permission_prefix)):
<div id="execution-options-dialog" style="display: none;">
${execute_form.render_deform(form_kwargs={'name': 'batch-execution'}, buttons=False)|n}
</div>
% endif
% endif
</%def>
${self.render_form_complete()} <%def name="render_this_page_buefy()">
% if use_buefy and master.handler.executable(batch) and request.has_perm('{}.execute'.format(permission_prefix)):
## TODO: stop using |n filter
${execute_form.render_deform(buttons=capture(execute_submit_button))|n}
% endif
${parent.render_this_page_buefy()}
</%def>
% if use_buefy: <%def name="declare_page_vars()">
<br /><br /> ${parent.declare_page_vars()}
## TODO: stop using |n filter % if not batch.executed and request.has_perm('{}.execute'.format(permission_prefix)):
${rows_grid.render_buefy(allow_save_defaults=False, tools=rows_grid_tools)|n} <script type="text/javascript">
<div id="tailbone-grid-app">
<tailbone-grid></tailbone-grid>
</div>
${self.make_tailbone_form_app()}
${self.make_tailbone_grid_app()}
% else:
## no buefy, so do the traditional thing
${rows_grid|n}
% endif
% if not use_buefy and master.handler.executable(batch) and not batch.executed: let ${execute_form.component_studly} = {
<div id="execution-options-dialog" style="display: none;"> template: '#${execute_form.component}-template',
${execute_form.render_deform(form_kwargs={'name': 'batch-execution'}, buttons=False)|n} methods: {}
</div> }
% endif
</script>
% endif
</%def>
<%def name="finalize_page_components()">
${parent.finalize_page_components()}
% if not batch.executed and request.has_perm('{}.execute'.format(permission_prefix)):
<script type="text/javascript">
${execute_form.component_studly}.data = function() { return ${execute_form.component_studly}Data }
Vue.component('${execute_form.component}', ${execute_form.component_studly})
</script>
% endif
</%def>
${parent.body()}

View file

@ -1,7 +1,5 @@
## -*- coding: utf-8; -*- ## -*- coding: utf-8; -*-
<%inherit file="/base.mako" /> <%inherit file="/page.mako" />
<%def name="context_menu_items()"></%def>
<%def name="object_helpers()"></%def> <%def name="object_helpers()"></%def>
@ -17,36 +15,15 @@
</div> </div>
</%def> </%def>
<%def name="render_form_complete()"> <%def name="render_this_page()">
% if use_buefy:
${self.render_form()}
<script type="text/x-template" id="form-page-template">
<div style="display: flex; justify-content: space-between;">
<div class="form-wrapper">
${self.render_buefy_form()}
</div>
<div style="display: flex; align-items: flex-start;">
<div class="object-helpers">
${self.object_helpers()}
</div>
<ul id="context-menu">
${self.context_menu_items()}
</ul>
</div>
</div>
</script>
<div id="form-page-app">
<form-page></form-page>
</div>
% else:
<div style="display: flex; justify-content: space-between;"> <div style="display: flex; justify-content: space-between;">
<div class="form-wrapper"> <div class="form-wrapper">
${self.render_form()} % if use_buefy:
${self.render_buefy_form()}
% else:
${self.render_form()}
% endif
</div> </div>
<div style="display: flex; align-items: flex-start;"> <div style="display: flex; align-items: flex-start;">
@ -60,33 +37,41 @@
</div> </div>
</div> </div>
</%def>
<%def name="render_this_page_buefy()">
% if form is not Underined:
${self.render_form()}
% endif
${parent.render_this_page_buefy()}
</%def>
<%def name="declare_page_vars()">
${parent.declare_page_vars()}
% if form is not Undefined:
<script type="text/javascript">
let ${form.component_studly} = {
template: '#${form.component}-template',
methods: {}
}
</script>
% endif % endif
</%def> </%def>
<%def name="modify_tailbone_form()"> <%def name="finalize_page_components()">
## NOTE: if you override this, must use <script> tags ${parent.finalize_page_components()}
</%def> % if form is not Undefined:
<script type="text/javascript">
<%def name="make_tailbone_form_app()"> ${form.component_studly}.data = function() { return ${form.component_studly}Data }
${self.modify_tailbone_form()}
<script type="text/javascript">
TailboneForm.data = function() { return TailboneFormData } Vue.component('${form.component}', ${form.component_studly})
Vue.component('tailbone-form', TailboneForm) </script>
% endif
Vue.component('form-page', FormPage)
new Vue({
el: '#form-page-app'
})
</script>
</%def> </%def>
${self.render_form_complete()} ${parent.body()}
% if use_buefy:
${self.make_tailbone_form_app()}
% endif

View file

@ -1,6 +1,6 @@
## -*- coding: utf-8; -*- ## -*- coding: utf-8; -*-
<script type="text/x-template" id="tailbone-form-template"> <script type="text/x-template" id="${form.component}-template">
<div> <div>
% if not form.readonly: % if not form.readonly:
${h.form(form.action_url, id=dform.formid, method='post', enctype='multipart/form-data', **form_kwargs)} ${h.form(form.action_url, id=dform.formid, method='post', enctype='multipart/form-data', **form_kwargs)}
@ -83,7 +83,7 @@
<script type="text/javascript"> <script type="text/javascript">
let TailboneFormData = { let ${form.component_studly}Data = {
## TODO: ugh, this seems pretty hacky. need to declare some data models ## TODO: ugh, this seems pretty hacky. need to declare some data models
## for various field components to bind to... ## for various field components to bind to...

View file

@ -1,5 +1,8 @@
## -*- coding: utf-8; -*- ## -*- coding: utf-8; -*-
% if form.use_buefy:
<div class="form"> ${form.render_deform(buttons=buttons)|n}
${form.render_deform(buttons=buttons)|n} % else:
</div> <div class="form">
${form.render_deform(buttons=buttons)|n}
</div>
% endif

View file

@ -26,12 +26,12 @@
% endif % endif
</%def> </%def>
<%def name="modify_tailbone_form()"> <%def name="modify_this_page()">
${parent.modify_tailbone_form()} ${parent.modify_this_page()}
% if master.deletable and instance_deletable and request.has_perm('{}.delete'.format(permission_prefix)) and master.delete_confirm == 'simple': % if master.deletable and instance_deletable and request.has_perm('{}.delete'.format(permission_prefix)) and master.delete_confirm == 'simple':
<script type="text/javascript"> <script type="text/javascript">
FormPage.methods.deleteObject = function() { ThisPage.methods.deleteObject = function() {
if (confirm("Are you sure you wish to delete this ${model_title}?")) { if (confirm("Are you sure you wish to delete this ${model_title}?")) {
this.$refs.deleteObjectForm.submit() this.$refs.deleteObjectForm.submit()
} }

View file

@ -5,8 +5,8 @@
<%def name="extra_javascript()"> <%def name="extra_javascript()">
${parent.extra_javascript()} ${parent.extra_javascript()}
% if master.deletable and instance_deletable and request.has_perm('{}.delete'.format(permission_prefix)) and master.delete_confirm == 'simple': % if not use_buefy:
% if not use_buefy: % if master.deletable and instance_deletable and request.has_perm('{}.delete'.format(permission_prefix)) and master.delete_confirm == 'simple':
<script type="text/javascript"> <script type="text/javascript">
$(function () { $(function () {
@ -22,16 +22,12 @@
</script> </script>
% endif % endif
% endif % if master.has_rows:
% if master.has_rows: <script type="text/javascript">
% if use_buefy: $(function() {
${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.grid.js') + '?ver={}'.format(tailbone.__version__))} $('.grid-wrapper').gridwrapper();
% else: });
<script type="text/javascript"> </script>
$(function() {
$('.grid-wrapper').gridwrapper();
});
</script>
% endif % endif
% endif % endif
</%def> </%def>
@ -85,45 +81,36 @@
${rows_grid_tools} ${rows_grid_tools}
</%def> </%def>
<%def name="modify_tailbone_grid()"> <%def name="render_this_page()">
## NOTE: if you override this, must use <script> tags ${parent.render_this_page()}
% if master.has_rows:
% if use_buefy:
<br />
<tailbone-grid></tailbone-grid>
% else:
${rows_grid|n}
% endif
% endif
</%def> </%def>
<%def name="make_tailbone_grid_app()"> <%def name="render_this_page_buefy()">
${self.modify_tailbone_grid()} % if master.has_rows:
## TODO: stop using |n filter
${rows_grid.render_buefy(allow_save_defaults=False, tools=capture(self.render_row_grid_tools))|n}
% endif
${parent.render_this_page_buefy()}
</%def>
<%def name="make_this_page_app()">
<script type="text/javascript"> <script type="text/javascript">
TailboneGrid.data = function() { return TailboneGridData } TailboneGrid.data = function() { return TailboneGridData }
Vue.component('tailbone-grid', TailboneGrid) Vue.component('tailbone-grid', TailboneGrid)
new Vue({
el: '#tailbone-grid-app'
});
</script> </script>
${parent.make_this_page_app()}
</%def> </%def>
${self.render_form_complete()} ${parent.body()}
% if master.has_rows:
% if use_buefy:
<br /><br />
## TODO: stop using |n filter
${rows_grid.render_buefy(allow_save_defaults=False, tools=capture(self.render_row_grid_tools))|n}
<div id="tailbone-grid-app">
<tailbone-grid></tailbone-grid>
</div>
${self.make_tailbone_grid_app()}
% else:
## no buefy, so do the traditional thing
${rows_grid|n}
% endif
% endif
% if use_buefy:
${self.make_tailbone_form_app()}
% endif

View file

@ -6,49 +6,36 @@
<%def name="page_content()"></%def> <%def name="page_content()"></%def>
<%def name="render_this_page()"> <%def name="render_this_page()">
% if use_buefy: <div style="display: flex; justify-content: space-between;">
<script type="text/x-template" id="this-page-template"> <div class="this-page-content">
<div style="display: flex; justify-content: space-between;"> ${self.page_content()}
</div>
<div class="this-page-content"> <ul id="context-menu">
${self.page_content()} ${self.context_menu_items()}
</div> </ul>
<ul id="context-menu"> </div>
${self.context_menu_items()} </%def>
</ul>
</div> <%def name="render_this_page_buefy()">
</script> <script type="text/x-template" id="this-page-template">
<div>
${self.render_this_page()}
</div>
</script>
<div id="this-page-app"> <div id="this-page-app">
<this-page></this-page> <this-page></this-page>
</div> </div>
% else:
## legacy / not buefy
<div style="display: flex; justify-content: space-between;">
<div class="this-page-content">
${self.page_content()}
</div>
<ul id="context-menu">
${self.context_menu_items()}
</ul>
</div>
% endif
</%def> </%def>
<%def name="modify_this_page()"> <%def name="modify_this_page()">
## NOTE: if you override this, must use <script> tags ## NOTE: if you override this, must use <script> tags
</%def> </%def>
<%def name="make_this_page_app()"> <%def name="declare_page_vars()">
<script type="text/javascript"> <script type="text/javascript">
let ThisPage = { let ThisPage = {
@ -57,9 +44,16 @@
} }
</script> </script>
</%def>
<%def name="finalize_page_components()">
## NOTE: if you override this, must use <script> tags
</%def>
<%def name="make_this_page_app()">
${self.declare_page_vars()}
${self.modify_this_page()} ${self.modify_this_page()}
${self.finalize_page_components()}
<script type="text/javascript"> <script type="text/javascript">
Vue.component('this-page', ThisPage) Vue.component('this-page', ThisPage)
@ -72,8 +66,9 @@
</%def> </%def>
${self.render_this_page()}
% if use_buefy: % if use_buefy:
${self.render_this_page_buefy()}
${self.make_this_page_app()} ${self.make_this_page_app()}
% else:
${self.render_this_page()}
% endif % endif

View file

@ -73,102 +73,88 @@
% endif % endif
</%def> </%def>
<%def name="render_form_complete()"> <%def name="render_this_page()">
% if use_buefy: <div style="display: flex; justify-content: space-between;">
<script type="text/x-template" id="form-page-template"> <div class="form-wrapper">
<div> <div class="form">
<div style="display: flex; justify-content: space-between;">
<div class="form-wrapper">
<div class="form">
<b-field horizontal
label="Appliance">
<div>
% if probe.appliance:
<a href="${url('tempmon.appliances.view', uuid=probe.appliance.uuid)}">${probe.appliance}</a>
% endif
</div>
</b-field>
<b-field horizontal
label="Probe Location">
<div>
${probe.location or ""}
</div>
</b-field>
<b-field horizontal
label="Showing">
${time_range}
</b-field>
% if use_buefy:
<b-field horizontal label="Appliance">
<div>
% if probe.appliance:
<a href="${url('tempmon.appliances.view', uuid=probe.appliance.uuid)}">${probe.appliance}</a>
% endif
</div>
</b-field>
% else:
<div class="field-wrapper">
<label>Appliance</label>
<div class="field">
% if probe.appliance:
<a href="${url('tempmon.appliances.view', uuid=probe.appliance.uuid)}">${probe.appliance}</a>
% endif
</div> </div>
</div> </div>
% endif
<div style="display: flex; align-items: flex-start;"> % if use_buefy:
<div class="object-helpers"> <b-field horizontal label="Probe Location">
${self.object_helpers()} <div>
${probe.location or ""}
</div> </div>
</b-field>
<ul id="context-menu"> % else:
${self.context_menu_items()} <div class="field-wrapper">
</ul> <label>Probe Location</label>
<div class="field">${probe.location or ""}</div>
</div> </div>
% endif
</div> % if use_buefy:
<b-field horizontal label="Showing">
${time_range}
</b-field>
% else:
<div class="field-wrapper">
<label>Showing</label>
<div class="field">
${time_range}
</div>
</div>
% endif
<canvas ref="tempchart" width="400" height="150"></canvas> </div>
</div> </div>
</script>
<div id="form-page-app"> <div style="display: flex; align-items: flex-start;">
<form-page></form-page> <div class="object-helpers">
${self.object_helpers()}
</div> </div>
<ul id="context-menu">
${self.context_menu_items()}
</ul>
</div>
</div>
% if use_buefy:
<canvas ref="tempchart" width="400" height="150"></canvas>
% else: % else:
## legacy / not buefy <canvas id="tempchart" width="400" height="150"></canvas>
<div class="form-wrapper">
<div class="field-wrapper">
<label>Appliance</label>
<div class="field">
% if probe.appliance:
<a href="${url('tempmon.appliances.view', uuid=probe.appliance.uuid)}">${probe.appliance}</a>
% endif
</div>
</div>
<div class="field-wrapper">
<label>Probe Location</label>
<div class="field">${probe.location or ""}</div>
</div>
<div class="field-wrapper">
<label>Showing</label>
<div class="field">
${time_range}
</div>
</div>
</div>
<canvas id="tempchart" width="400" height="150"></canvas>
% endif % endif
</%def> </%def>
<%def name="modify_tailbone_form()"> <%def name="modify_this_page()">
<script type="text/javascript"> <script type="text/javascript">
FormPage.data = function() { return { ThisPage.data = function() { return {
currentTimeRange: ${json.dumps(current_time_range)|n}, currentTimeRange: ${json.dumps(current_time_range)|n},
chart: null, chart: null,
}} }}
FormPage.methods.fetchReadings = function(timeRange) { ThisPage.methods.fetchReadings = function(timeRange) {
if (timeRange === undefined) { if (timeRange === undefined) {
timeRange = this.currentTimeRange timeRange = this.currentTimeRange
@ -211,11 +197,11 @@
}) })
} }
FormPage.methods.timeRangeChanged = function(value) { ThisPage.methods.timeRangeChanged = function(value) {
this.fetchReadings(value) this.fetchReadings(value)
} }
FormPage.mounted = function() { ThisPage.mounted = function() {
this.fetchReadings() this.fetchReadings()
} }

View file

@ -297,7 +297,7 @@
${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.datepicker.js') + '?ver={}'.format(tailbone.__version__))} ${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.datepicker.js') + '?ver={}'.format(tailbone.__version__))}
${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.oncebutton.js') + '?ver={}'.format(tailbone.__version__))} ${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.oncebutton.js') + '?ver={}'.format(tailbone.__version__))}
${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.timepicker.js') + '?ver={}'.format(tailbone.__version__))} ${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.timepicker.js') + '?ver={}'.format(tailbone.__version__))}
${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.form.js') + '?ver={}'.format(tailbone.__version__))} ${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.grid.js') + '?ver={}'.format(tailbone.__version__))}
<script type="text/javascript"> <script type="text/javascript">
var session_timeout = ${request.get_session_timeout() or 'null'}; var session_timeout = ${request.get_session_timeout() or 'null'};

View file

@ -802,6 +802,7 @@ class BatchMasterView(MasterView):
schema = colander.Schema() schema = colander.Schema()
kwargs['use_buefy'] = self.get_use_buefy() kwargs['use_buefy'] = self.get_use_buefy()
kwargs['component'] = 'execute-form'
return forms.Form(schema=schema, request=self.request, defaults=defaults, **kwargs) return forms.Form(schema=schema, request=self.request, defaults=defaults, **kwargs)
def get_execute_title(self, batch): def get_execute_title(self, batch):