From 4b25373c7c78b4327101deb5c5502f4723f4cc8e Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Thu, 7 Feb 2019 13:53:44 +0200 Subject: [PATCH] Abstracted text fields into smart input components --- app/imports/ui/StoryBook.vue | 2 + .../ui/components/AttributeEdit.Story.vue | 21 +++-- app/imports/ui/components/AttributeEdit.vue | 18 ++-- .../ui/components/global/SmartInput.Story.vue | 92 ++++++++++++++++++ .../ui/components/global/SmartInput.js | 93 +++++++++++++++++++ .../ui/components/global/SmartSelect.vue | 20 ++++ app/imports/ui/components/global/TextArea.vue | 19 ++++ .../ui/components/global/TextField.vue | 68 ++------------ .../ui/components/global/globalIndex.js | 9 ++ app/imports/ui/vueSetup.js | 8 +- 10 files changed, 264 insertions(+), 86 deletions(-) create mode 100644 app/imports/ui/components/global/SmartInput.Story.vue create mode 100644 app/imports/ui/components/global/SmartInput.js create mode 100644 app/imports/ui/components/global/SmartSelect.vue create mode 100644 app/imports/ui/components/global/TextArea.vue create mode 100644 app/imports/ui/components/global/globalIndex.js diff --git a/app/imports/ui/StoryBook.vue b/app/imports/ui/StoryBook.vue index 1c5a01df..3a09b332 100644 --- a/app/imports/ui/StoryBook.vue +++ b/app/imports/ui/StoryBook.vue @@ -49,6 +49,7 @@ import HitDiceListTile from '/imports/ui/components/HitDiceListTile.Story.vue'; import IconSearch from '/imports/ui/components/IconSearch.Story.vue'; import SkillListTile from '/imports/ui/components/SkillListTile.Story.vue'; + import SmartInput from '/imports/ui/components/global/SmartInput.Story.vue'; import ToolbarLayout from '/imports/ui/layouts/ToolbarLayout.vue'; export default { @@ -64,6 +65,7 @@ HitDiceListTile, IconSearch, SkillListTile, + SmartInput, ToolbarLayout, }, data(){ return { diff --git a/app/imports/ui/components/AttributeEdit.Story.vue b/app/imports/ui/components/AttributeEdit.Story.vue index d12839c7..0e1a7d9b 100644 --- a/app/imports/ui/components/AttributeEdit.Story.vue +++ b/app/imports/ui/components/AttributeEdit.Story.vue @@ -4,7 +4,7 @@ v-for="(attribute, index) in attributes" :key="attribute._id" :attribute="attribute" - @change="e => change(index, e)" + @change="(e, ack) => change(index, e, ack)" />
{{attribute}} @@ -39,14 +39,17 @@ ], }}, methods: { - change(index, e){ - for (let i in e){ - this.attributes[index][i] = e[i].trim(); - } - }); - }, - methods: { - log: console.log, + change(index, e, ack){ + // Take a while to write the attribute + setTimeout(() => { + for (let i in e){ + let val = e[i]; + if (typeof val === 'string') val = val.trim(); + this.attributes[index][i] = val; + } + if (ack) ack(); + }, 600) + }, }, }; diff --git a/app/imports/ui/components/AttributeEdit.vue b/app/imports/ui/components/AttributeEdit.vue index 900b0237..0d6099d0 100644 --- a/app/imports/ui/components/AttributeEdit.vue +++ b/app/imports/ui/components/AttributeEdit.vue @@ -3,41 +3,41 @@ - -
diff --git a/app/imports/ui/components/global/SmartInput.Story.vue b/app/imports/ui/components/global/SmartInput.Story.vue new file mode 100644 index 00000000..f8bc6ee1 --- /dev/null +++ b/app/imports/ui/components/global/SmartInput.Story.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/app/imports/ui/components/global/SmartInput.js b/app/imports/ui/components/global/SmartInput.js new file mode 100644 index 00000000..3b1ed212 --- /dev/null +++ b/app/imports/ui/components/global/SmartInput.js @@ -0,0 +1,93 @@ +/* + * Mixin to handle inputs that update the database. + * Won't bash the field's value while it's focused, even if the database trims + * or otherwise sanitizes the data captured. + * + * Emits a change event that requires acknowledgement with an optional error + * message if something went wrong + */ +import { _ } from 'underscore'; + +export default { + inheritAttrs: false, + data(){ return { + error: false, + errorMessages: [], + focused: false, + loading: false, + dirty: false, + safeValue: this.value, + inputValue: this.value, + }}, + props: { + value: [String, Number], + debounceTime: { + type: Number, + default: 750, + }, + }, + watch: { + focused(newFocus){ + // If the value updated while we were focused, show it now on defocus + // but not if we are waiting for our own writes to get persisted + // and not if there is an error in our input + if (!newFocus && !this.dirty && !this.error){ + this.forceSafeValueUpdate(); + } + // Start the loading bar on defocus if the input is dirty + // It might be a lie, we aren't doing the work yet, but it feels laggy + // to defocus an element and then it starts working after a delay + if (!newFocus, this.dirty){ + this.loading = true; + } + }, + dirty(newDirty){ + // Our changes were acknowledged, weren't in error, and we aren't focused, + // make sure the internal value matches the database value + if (!newDirty && !this.focused && !this.error){ + this.forceSafeValueUpdate(); + } + }, + value(newValue){ + if (!this.focused){ + this.safeValue = newValue; + } + }, + safeValue(newSafeValue){ + // The safe value only gets updated from the parent, so it must be valid + this.error = false; + this.errorMessages = []; + }, + }, + methods: { + input(val){ + this.$emit('input', val); + this.inputValue = val; + this.dirty = true; + this.debouncedChange(val); + }, + acknowledgeChange(error){ + this.loading = false; + this.dirty = false; + this.error = !!error; + this.errorMessages = error || []; + }, + change(val){ + this.dirty = true; + if (val === this.value) return; + if (this.hasChangeListener) this.loading = true; + this.$emit('change', val, this.acknowledgeChange); + }, + hasChangeListener(){ + return this.$listeners && this.$listeners.change + }, + forceSafeValueUpdate(){ + // hack to force the value to update on the child component + this.safeValue = null + this.$nextTick(() => this.safeValue = this.value); + }, + }, + created(){ + this.debouncedChange = _.debounce(this.change, this.debounceTime); + }, +}; diff --git a/app/imports/ui/components/global/SmartSelect.vue b/app/imports/ui/components/global/SmartSelect.vue new file mode 100644 index 00000000..21aa896c --- /dev/null +++ b/app/imports/ui/components/global/SmartSelect.vue @@ -0,0 +1,20 @@ + + + diff --git a/app/imports/ui/components/global/TextArea.vue b/app/imports/ui/components/global/TextArea.vue new file mode 100644 index 00000000..7e5c242e --- /dev/null +++ b/app/imports/ui/components/global/TextArea.vue @@ -0,0 +1,19 @@ + + + diff --git a/app/imports/ui/components/global/TextField.vue b/app/imports/ui/components/global/TextField.vue index 3e845f44..a00142a2 100644 --- a/app/imports/ui/components/global/TextField.vue +++ b/app/imports/ui/components/global/TextField.vue @@ -1,6 +1,8 @@