diff --git a/tailbone/forms/core.py b/tailbone/forms/core.py
index 84169a06..4f94163c 100644
--- a/tailbone/forms/core.py
+++ b/tailbone/forms/core.py
@@ -760,6 +760,29 @@ class Form(object):
context['render_field_readonly'] = self.render_field_readonly
return render(template, context)
+ def get_vuejs_model_value(self, field):
+ """
+ This method must return "raw" JS which will be assigned as the initial
+ model value for the given field. This JS will be written as part of
+ the overall response, to be interpreted on the client side.
+ """
+ if isinstance(field.schema.typ, colander.Date):
+ # TODO: don't recall why "always null" here?
+ return 'null'
+
+ if isinstance(field.schema.typ, deform.FileData):
+ # TODO: don't recall why "always null" here?
+ return 'null'
+
+ if isinstance(field.schema.typ, colander.Set):
+ if field.cstruct is colander.null:
+ return '[]'
+
+ if field.cstruct is colander.null:
+ return 'null'
+
+ return json.dumps(field.cstruct)
+
def messages_json(self, messages):
dump = json.dumps(messages)
dump = dump.replace("'", ''')
diff --git a/tailbone/static/js/tailbone.buefy.autocomplete.js b/tailbone/static/js/tailbone.buefy.autocomplete.js
new file mode 100644
index 00000000..669b3c1f
--- /dev/null
+++ b/tailbone/static/js/tailbone.buefy.autocomplete.js
@@ -0,0 +1,91 @@
+
+const TailboneAutocomplete = {
+
+ template: '#tailbone-autocomplete-template',
+
+ props: {
+ name: String,
+ serviceUrl: String,
+ value: String,
+ initialLabel: String,
+ },
+
+ data() {
+ let selected = null
+ if (this.value) {
+ selected = {
+ value: this.value,
+ label: this.initialLabel,
+ }
+ }
+ return {
+ data: [],
+ selected: selected,
+ isFetching: false,
+ autocompleteValue: this.value,
+ }
+ },
+
+ methods: {
+
+ clearSelection() {
+ this.selected = null
+ this.autocompleteValue = null
+ this.$nextTick(function() {
+ this.$refs.autocomplete.focus()
+ })
+
+ // TODO: should emit event for caller logic (can they cancel?)
+ // $('#' + oid + '-textbox').trigger('autocompletevaluecleared');
+ },
+
+ // TODO: should we allow custom callback? or is event enough?
+ // function (oid) {
+ // $('#' + oid + '-textbox').on('autocompletevaluecleared', function() {
+ // ${cleared_callback}();
+ // });
+ // }
+
+ selectionMade(option) {
+ this.selected = option
+
+ // TODO: should emit event for caller logic (can they cancel?)
+ // $('#' + oid + '-textbox').trigger('autocompletevalueselected',
+ // [ui.item.value, ui.item.label]);
+ },
+
+ // TODO: should we allow custom callback? or is event enough?
+ // function (oid) {
+ // $('#' + oid + '-textbox').on('autocompletevalueselected', function(event, uuid, label) {
+ // ${selected_callback}(uuid, label);
+ // });
+ // }
+
+ itemSelected(value) {
+ this.$emit('input', value)
+ },
+
+ // TODO: buefy example uses `debounce()` here and perhaps we should too?
+ // https://buefy.org/documentation/autocomplete
+ getAsyncData: function (entry) {
+ if (entry.length < 3) {
+ this.data = []
+ return
+ }
+ this.isFetching = true
+ this.$http.get(this.serviceUrl + '?term=' + encodeURIComponent(entry))
+ .then(({ data }) => {
+ this.data = data
+ })
+ .catch((error) => {
+ this.data = []
+ throw error
+ })
+ .finally(() => {
+ this.isFetching = false
+ })
+ },
+ },
+}
+
+Vue.component('tailbone-autocomplete', TailboneAutocomplete)
diff --git a/tailbone/static/js/tailbone.buefy.oncebutton.js b/tailbone/static/js/tailbone.buefy.oncebutton.js
index 3af53276..c8e7e1e1 100644
--- a/tailbone/static/js/tailbone.buefy.oncebutton.js
+++ b/tailbone/static/js/tailbone.buefy.oncebutton.js
@@ -10,6 +10,8 @@ const OnceButton = {
':title="title"',
':disabled="buttonDisabled"',
'@click="clicked"',
+ 'icon-pack="fas"',
+ ':icon-left="iconLeft"',
'>',
'{{ buttonText }}',
''
@@ -22,6 +24,7 @@ const OnceButton = {
href: String,
text: String,
title: String,
+ iconLeft: String,
working: String,
workingText: String,
disabled: Boolean
diff --git a/tailbone/static/themes/falafel/css/forms.css b/tailbone/static/themes/falafel/css/forms.css
index d23205d2..d816b664 100644
--- a/tailbone/static/themes/falafel/css/forms.css
+++ b/tailbone/static/themes/falafel/css/forms.css
@@ -13,3 +13,12 @@
white-space: nowrap;
width: 18em;
}
+
+.field.is-horizontal .field-body {
+ min-width: 30em;
+}
+
+.field.is-horizontal .field-body .select,
+.field.is-horizontal .field-body .select select {
+ width: 100%;
+}
diff --git a/tailbone/templates/autocomplete.mako b/tailbone/templates/autocomplete.mako
index 249f8f2e..7ec61f4c 100644
--- a/tailbone/templates/autocomplete.mako
+++ b/tailbone/templates/autocomplete.mako
@@ -1,4 +1,5 @@
-## -*- coding: utf-8 -*-
+## -*- coding: utf-8; -*-
+
## TODO: This function signature is getting out of hand...
<%def name="autocomplete(field_name, service_url, field_value=None, field_display=None, width='300px', select=None, selected=None, cleared=None, change_clicked=None, options={})">
@@ -56,3 +57,31 @@
});
%def>
+
+<%def name="tailbone_autocomplete_template()">
+
+%def>
diff --git a/tailbone/templates/deform/autocomplete_jquery.pt b/tailbone/templates/deform/autocomplete_jquery.pt
index 7231fae3..1533cc2b 100644
--- a/tailbone/templates/deform/autocomplete_jquery.pt
+++ b/tailbone/templates/deform/autocomplete_jquery.pt
@@ -2,10 +2,14 @@
css_class css_class|field.widget.css_class;
oid oid|field.oid;
field_display field_display;
- style style|field.widget.style"
- id="${oid}-container"
- class="autocomplete-container">
+ style style|field.widget.style;
+ url url|field.widget.service_url;
+ use_buefy use_buefy|0;"
+ tal:omit-tag="">
+
+
+
+
+
+
+
diff --git a/tailbone/templates/deform/checkbox.pt b/tailbone/templates/deform/checkbox.pt
index d149f7d1..b00ced03 100644
--- a/tailbone/templates/deform/checkbox.pt
+++ b/tailbone/templates/deform/checkbox.pt
@@ -1,13 +1,26 @@
-
diff --git a/tailbone/templates/deform/checked_password.pt b/tailbone/templates/deform/checked_password.pt
new file mode 100644
index 00000000..43657045
--- /dev/null
+++ b/tailbone/templates/deform/checked_password.pt
@@ -0,0 +1,60 @@
+
+
+
+
+
+ ${field.start_mapping()}
+
+
+
+
+ ${field.end_mapping()}
+
+
+
diff --git a/tailbone/templates/deform/select.pt b/tailbone/templates/deform/select.pt
index 3a40226a..8f8ae171 100644
--- a/tailbone/templates/deform/select.pt
+++ b/tailbone/templates/deform/select.pt
@@ -58,12 +58,14 @@
+ >
+
diff --git a/tailbone/templates/deform/textarea.pt b/tailbone/templates/deform/textarea.pt
new file mode 100644
index 00000000..25583b4e
--- /dev/null
+++ b/tailbone/templates/deform/textarea.pt
@@ -0,0 +1,27 @@
+
diff --git a/tailbone/templates/deform/textinput.pt b/tailbone/templates/deform/textinput.pt
new file mode 100644
index 00000000..48d4c360
--- /dev/null
+++ b/tailbone/templates/deform/textinput.pt
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tailbone/templates/form.mako b/tailbone/templates/form.mako
index 2fb050ac..7a323a62 100644
--- a/tailbone/templates/form.mako
+++ b/tailbone/templates/form.mako
@@ -11,7 +11,38 @@
${form.render(buttons=capture(self.render_form_buttons))|n}
%def>
+<%def name="render_buefy_form()">
+
+
+
+%def>
+
<%def name="render_form_complete()">
+ % if use_buefy:
+ ${self.render_form()}
+
+
+
+
+ % else:
+ % endif
%def>
<%def name="modify_tailbone_form()">
@@ -43,8 +75,14 @@
Vue.component('tailbone-form', TailboneForm)
+ const FormPage = {
+ template: '#form-page-template'
+ }
+
+ Vue.component('form-page', FormPage)
+
new Vue({
- el: '#tailbone-form-app'
+ el: '#form-page-app'
})
@@ -53,6 +91,6 @@
${self.render_form_complete()}
-% if form.use_buefy:
+% if use_buefy:
${self.make_tailbone_form_app()}
% endif
diff --git a/tailbone/templates/forms/deform_buefy.mako b/tailbone/templates/forms/deform_buefy.mako
index 5ca8b1ac..f49b5dc9 100644
--- a/tailbone/templates/forms/deform_buefy.mako
+++ b/tailbone/templates/forms/deform_buefy.mako
@@ -91,21 +91,10 @@
% for field in form.fields:
% if field in dform:
<% field = dform[field] %>
- % if isinstance(field.schema.typ, colander.Date):
- field_model_${field.name}: null,
- % elif isinstance(field.schema.typ, deform.FileData):
- field_model_${field.name}: null,
- % else:
- field_model_${field.name}: ${'null' if field.cstruct is colander.null else json.dumps(field.cstruct)|n},
- % endif
+ field_model_${field.name}: ${form.get_vuejs_model_value(field)|n},
% endif
% endfor
% endif
}
-
-
-
-
-
diff --git a/tailbone/templates/master/delete.mako b/tailbone/templates/master/delete.mako
index e6e4fcc2..697eeb47 100644
--- a/tailbone/templates/master/delete.mako
+++ b/tailbone/templates/master/delete.mako
@@ -20,16 +20,28 @@
% endif
%def>
-<%def name="render_form()">
+<%def name="render_buefy_form()">
+ % if use_buefy:
+
+ You are about to delete the following ${model_title} and all associated data:
+
+ % else:
You are about to delete the following ${model_title} and all associated data:
+ % endif
- ${parent.render_form()}
+ ${parent.render_buefy_form()}
%def>
<%def name="render_form_buttons()">
+ % if use_buefy:
+
+ Are you sure about this?
+
+ % else:
Are you sure about this?
+ % endif
${h.form(request.current_route_url(), class_=None if form.use_buefy else 'autodisable')}
@@ -39,15 +51,13 @@
- % else:
- Whoops, nevermind...
- % endif
- % if form.use_buefy:
-
% else:
- ${h.submit('submit', "Yes, please DELETE this data forever!", class_='button is-primary')}
+ Whoops, nevermind...
+ ${h.submit('submit', "Yes, please DELETE this data forever!", class_='button is-primary')}
% endif
${h.end_form()}
diff --git a/tailbone/templates/master/index.mako b/tailbone/templates/master/index.mako
index 4fd3b50a..e161fe3c 100644
--- a/tailbone/templates/master/index.mako
+++ b/tailbone/templates/master/index.mako
@@ -141,13 +141,12 @@
-
- Merge 2 ${model_title_plural}
-
+
+
% else:
${h.hidden('uuids')}
Merge 2 ${model_title_plural}
diff --git a/tailbone/templates/themes/falafel/base.mako b/tailbone/templates/themes/falafel/base.mako
index 0cac1e11..8058a9bc 100644
--- a/tailbone/templates/themes/falafel/base.mako
+++ b/tailbone/templates/themes/falafel/base.mako
@@ -1,6 +1,7 @@
## -*- coding: utf-8; -*-
<%namespace file="/grids/nav.mako" import="grid_index_nav" />
<%namespace file="/feedback_dialog_buefy.mako" import="feedback_dialog" />
+<%namespace file="/autocomplete.mako" import="tailbone_autocomplete_template" />
<%namespace name="base_meta" file="/base_meta.mako" />
@@ -30,6 +31,11 @@
+
+ ## TODO: should move template to JS, then can postpone the JS
+ ${tailbone_autocomplete_template()}
+ ${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.autocomplete.js') + '?ver={}'.format(tailbone.__version__))}
+