add new from ListInput
This commit is contained in:
parent
04c047bd31
commit
83a747739a
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -259,7 +259,7 @@ else:
|
|||||||
'USER': os.getenv('POSTGRES_USER'),
|
'USER': os.getenv('POSTGRES_USER'),
|
||||||
'PASSWORD': os.getenv('POSTGRES_PASSWORD'),
|
'PASSWORD': os.getenv('POSTGRES_PASSWORD'),
|
||||||
'NAME': os.getenv('POSTGRES_DB') if os.getenv('POSTGRES_DB') else 'db.sqlite3',
|
'NAME': os.getenv('POSTGRES_DB') if os.getenv('POSTGRES_DB') else 'db.sqlite3',
|
||||||
'CONN_MAX_AGE': 600,
|
'CONN_MAX_AGE': 60,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ import {BootstrapVue} from 'bootstrap-vue'
|
|||||||
|
|
||||||
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||||
|
|
||||||
import {CardMixin, ToastMixin, ApiMixin} from "@/utils/utils";
|
import {CardMixin, ApiMixin} from "@/utils/utils";
|
||||||
import {StandardToasts} from "@/utils/utils";
|
import {StandardToasts} from "@/utils/utils";
|
||||||
|
|
||||||
import GenericSplitLists from "@/components/GenericSplitLists";
|
import GenericSplitLists from "@/components/GenericSplitLists";
|
||||||
@ -77,7 +77,7 @@ export default {
|
|||||||
// TODO ApiGenerator doesn't capture and share error information - would be nice to share error details when available
|
// TODO ApiGenerator doesn't capture and share error information - would be nice to share error details when available
|
||||||
// or i'm capturing it incorrectly
|
// or i'm capturing it incorrectly
|
||||||
name: 'ModelListView',
|
name: 'ModelListView',
|
||||||
mixins: [CardMixin, ToastMixin, ApiMixin],
|
mixins: [CardMixin, ApiMixin],
|
||||||
components: {GenericHorizontalCard, GenericSplitLists, GenericModalForm},
|
components: {GenericHorizontalCard, GenericSplitLists, GenericModalForm},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -10,11 +10,12 @@
|
|||||||
:label="label"
|
:label="label"
|
||||||
track-by="id"
|
track-by="id"
|
||||||
:multiple="multiple"
|
:multiple="multiple"
|
||||||
:taggable="create_new"
|
:taggable="allow_create"
|
||||||
:tag-placeholder="createText"
|
:tag-placeholder="create_placeholder"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@search-change="search"
|
@search-change="search"
|
||||||
@input="selectionChanged">
|
@input="selectionChanged"
|
||||||
|
@tag="addNew">
|
||||||
</multiselect>
|
</multiselect>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -44,33 +45,22 @@ export default {
|
|||||||
sticky_options: {type:Array, default(){return []}},
|
sticky_options: {type:Array, default(){return []}},
|
||||||
initial_selection: {type:Array, default(){return []}},
|
initial_selection: {type:Array, default(){return []}},
|
||||||
multiple: {type: Boolean, default: true},
|
multiple: {type: Boolean, default: true},
|
||||||
create_new: {type: Boolean, default: false}, // TODO: this will create option to add new drop-downs
|
allow_create: {type: Boolean, default: false}, // TODO: this will create option to add new drop-downs
|
||||||
create_text: {type: String, default: 'You Forgot to Add a Tag Placeholder'},
|
create_placeholder: {type: String, default: 'You Forgot to Add a Tag Placeholder'},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
initial_selection: function (newVal, oldVal) { // watch it
|
initial_selection: function (newVal, oldVal) { // watch it
|
||||||
if (this.multiple) {
|
|
||||||
this.selected_objects = newVal
|
this.selected_objects = newVal
|
||||||
} else if (this.selected_objects != newVal?.[0]) {
|
|
||||||
// when not using multiple selections need to convert array to value
|
|
||||||
this.selected_objects = newVal?.[0] ?? null
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.search('')
|
this.search('')
|
||||||
// when not using multiple selections need to convert array to value
|
this.selected_objects = this.initial_selection
|
||||||
if (!this.multiple & this.selected_objects != this.initial_selection?.[0]) {
|
|
||||||
this.selected_objects = this.initial_selection?.[0] ?? null
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
lookupPlaceholder() {
|
lookupPlaceholder() {
|
||||||
return this.placeholder || this.model.name || this.$t('Search')
|
return this.placeholder || this.model.name || this.$t('Search')
|
||||||
},
|
},
|
||||||
createText() {
|
|
||||||
return this.create_text
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// this.genericAPI inherited from ApiMixin
|
// this.genericAPI inherited from ApiMixin
|
||||||
@ -85,7 +75,19 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
selectionChanged: function () {
|
selectionChanged: function () {
|
||||||
|
if (this.multiple) {
|
||||||
this.$emit('change', {var: this.parent_variable, val: this.selected_objects})
|
this.$emit('change', {var: this.parent_variable, val: this.selected_objects})
|
||||||
|
} else {
|
||||||
|
// if not multiple listener is expecting a single object, not an array
|
||||||
|
this.$emit('change', {var: this.parent_variable, val: this.selected_objects?.[0] ?? null})
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
addNew(e) {
|
||||||
|
this.$emit('new', e)
|
||||||
|
// could refactor as Promise - seems unecessary
|
||||||
|
setTimeout(() => { this.search(''); }, 750);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<b-modal class="modal" id="modal" @hidden="cancelAction">
|
<b-modal :id="'modal_'+id" @hidden="cancelAction">
|
||||||
<template v-slot:modal-title><h4>{{form.title}}</h4></template>
|
<template v-slot:modal-title><h4>{{form.title}}</h4></template>
|
||||||
<div v-for="(f, i) in form.fields" v-bind:key=i>
|
<div v-for="(f, i) in form.fields" v-bind:key=i>
|
||||||
<p v-if="f.type=='instruction'">{{f.label}}</p>
|
<p v-if="f.type=='instruction'">{{f.label}}</p>
|
||||||
@ -11,6 +11,7 @@
|
|||||||
:field="f.field"
|
:field="f.field"
|
||||||
:model="listModel(f.list)"
|
:model="listModel(f.list)"
|
||||||
:sticky_options="f.sticky_options || undefined"
|
:sticky_options="f.sticky_options || undefined"
|
||||||
|
:create_new="f.allow_create"
|
||||||
@change="storeValue"/> <!-- TODO add ability to create new items associated with lookup -->
|
@change="storeValue"/> <!-- TODO add ability to create new items associated with lookup -->
|
||||||
<!-- TODO: add multi-selection input list -->
|
<!-- TODO: add multi-selection input list -->
|
||||||
<checkbox-input v-if="f.type=='checkbox'"
|
<checkbox-input v-if="f.type=='checkbox'"
|
||||||
@ -53,21 +54,24 @@ export default {
|
|||||||
name: 'GenericModalForm',
|
name: 'GenericModalForm',
|
||||||
components: {CheckboxInput, LookupInput, TextInput, EmojiInput},
|
components: {CheckboxInput, LookupInput, TextInput, EmojiInput},
|
||||||
props: {
|
props: {
|
||||||
model: {required: true, type: Object, default: function() {}},
|
model: {required: true, type: Object},
|
||||||
action: {required: true, type: Object, default: function() {}},
|
action: {required: true, type: Object},
|
||||||
item1: {type: Object, default: function() {}},
|
item1: {type: Object, default () {return undefined}},
|
||||||
item2: {type: Object, default: function() {}},
|
item2: {type: Object, default () {return undefined}},
|
||||||
show: {required: true, type: Boolean, default: false},
|
show: {required: true, type: Boolean, default: false},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
id: undefined,
|
||||||
form_data: {},
|
form_data: {},
|
||||||
form: {},
|
form: {},
|
||||||
dirty: false
|
dirty: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$root.$on('change', this.storeValue); // modal is outside Vue instance(?) so have to listen at root of component
|
this.id = Math.random()
|
||||||
|
this.$root.$on('change', this.storeValue); // boostrap modal placed at document so have to listen at root of component
|
||||||
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
buttonLabel() {
|
buttonLabel() {
|
||||||
@ -79,9 +83,9 @@ export default {
|
|||||||
if (this.show) {
|
if (this.show) {
|
||||||
this.form = getForm(this.model, this.action, this.item1, this.item2)
|
this.form = getForm(this.model, this.action, this.item1, this.item2)
|
||||||
this.dirty = true
|
this.dirty = true
|
||||||
this.$bvModal.show('modal')
|
this.$bvModal.show('modal_' + this.id)
|
||||||
} else {
|
} else {
|
||||||
this.$bvModal.hide('modal')
|
this.$bvModal.hide('modal_' + this.id)
|
||||||
this.form_data = {}
|
this.form_data = {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -5,12 +5,16 @@
|
|||||||
class="mb-3">
|
class="mb-3">
|
||||||
<generic-multiselect
|
<generic-multiselect
|
||||||
@change="new_value=$event.val"
|
@change="new_value=$event.val"
|
||||||
:initial_selection="[value]"
|
@remove="new_value=undefined"
|
||||||
|
:initial_selection="initialSelection"
|
||||||
:model="model"
|
:model="model"
|
||||||
:multiple="false"
|
:multiple="false"
|
||||||
:sticky_options="sticky_options"
|
:sticky_options="sticky_options"
|
||||||
|
:allow_create="create_new"
|
||||||
|
:create_placeholder="createPlaceholder"
|
||||||
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
||||||
:placeholder="modelName">
|
:placeholder="modelName"
|
||||||
|
@new="addNew">
|
||||||
</generic-multiselect>
|
</generic-multiselect>
|
||||||
</b-form-group>
|
</b-form-group>
|
||||||
</div>
|
</div>
|
||||||
@ -18,15 +22,18 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import GenericMultiselect from "@/components/GenericMultiselect";
|
import GenericMultiselect from "@/components/GenericMultiselect";
|
||||||
|
import {StandardToasts, ApiMixin} from "@/utils/utils";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'LookupInput',
|
name: 'LookupInput',
|
||||||
components: {GenericMultiselect},
|
components: {GenericMultiselect},
|
||||||
|
mixins: [ApiMixin],
|
||||||
props: {
|
props: {
|
||||||
field: {type: String, default: 'You Forgot To Set Field Name'},
|
field: {type: String, default: 'You Forgot To Set Field Name'},
|
||||||
label: {type: String, default: ''},
|
label: {type: String, default: ''},
|
||||||
value: {type: Object, default () {return {}}},
|
value: {type: Object, default () {return undefined}},
|
||||||
model: {type: Object, default () {return {}}},
|
model: {type: Object, default () {return undefined}},
|
||||||
|
create_new: {type: Boolean, default: false},
|
||||||
sticky_options: {type:Array, default(){return []}},
|
sticky_options: {type:Array, default(){return []}},
|
||||||
// TODO: include create_new and create_text props and associated functionality to create objects for drop down
|
// TODO: include create_new and create_text props and associated functionality to create objects for drop down
|
||||||
// see 'tagging' here: https://vue-multiselect.js.org/#sub-tagging
|
// see 'tagging' here: https://vue-multiselect.js.org/#sub-tagging
|
||||||
@ -37,9 +44,27 @@ export default {
|
|||||||
new_value: undefined,
|
new_value: undefined,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.new_value = this.value
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
modelName() {
|
modelName() {
|
||||||
return this?.model?.name ?? this.$t('Search')
|
return this?.model?.name ?? this.$t('Search')
|
||||||
|
},
|
||||||
|
initialSelection() {
|
||||||
|
// multiselect is expect to get an array of objects - make sure it gets one
|
||||||
|
if (Array.isArray(this.new_value)) {
|
||||||
|
return this.new_value
|
||||||
|
} else if (!this.new_value) {
|
||||||
|
return []
|
||||||
|
} else if (this.new_value.id) {
|
||||||
|
return [this.new_value]
|
||||||
|
} else {
|
||||||
|
return [{'id': -1, 'name': this.new_value}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createPlaceholder() {
|
||||||
|
return this.$t('Create_New_' + this?.model?.name)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@ -47,5 +72,20 @@ export default {
|
|||||||
this.$root.$emit('change', this.field, this.new_value ?? null)
|
this.$root.$emit('change', this.field, this.new_value ?? null)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
|
addNew: function(e) {
|
||||||
|
// if create a new item requires more than 1 parameter or the field 'name' is insufficient this will need reworked
|
||||||
|
// in a perfect world this would trigger a new modal and allow editing all fields
|
||||||
|
this.genericAPI(this.model, this.Actions.CREATE, {'name': e}).then((result) => {
|
||||||
|
this.new_value = result.data
|
||||||
|
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log(err)
|
||||||
|
StandardToasts.makeStandardToast(StandardToasts.FAIL_CREATE)
|
||||||
|
})
|
||||||
|
this.show_modal = false
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
@ -125,5 +125,9 @@
|
|||||||
"Icon": "Icon",
|
"Icon": "Icon",
|
||||||
"Unit": "Unit",
|
"Unit": "Unit",
|
||||||
"No_Results": "No Results",
|
"No_Results": "No Results",
|
||||||
"New_Unit": "New Unit"
|
"New_Unit": "New Unit",
|
||||||
|
"Create_New_Shopping Category": "Create New Shopping Category",
|
||||||
|
"Create_New_Food": "Add New Food",
|
||||||
|
"Create_New_Keyword": "Add New Keyword",
|
||||||
|
"Create_New_Unit": "Add New Unit"
|
||||||
}
|
}
|
||||||
|
@ -100,6 +100,7 @@ export class Models {
|
|||||||
'field': 'supermarket_category',
|
'field': 'supermarket_category',
|
||||||
'list': 'SHOPPING_CATEGORY',
|
'list': 'SHOPPING_CATEGORY',
|
||||||
'label': i18n.t('Shopping_Category'),
|
'label': i18n.t('Shopping_Category'),
|
||||||
|
'allow_create': true
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -168,6 +169,25 @@ export class Models {
|
|||||||
static SHOPPING_CATEGORY = {
|
static SHOPPING_CATEGORY = {
|
||||||
'name': i18n.t('Shopping_Category'),
|
'name': i18n.t('Shopping_Category'),
|
||||||
'apiName': 'SupermarketCategory',
|
'apiName': 'SupermarketCategory',
|
||||||
|
'create': {
|
||||||
|
'params': [['name', 'description']],
|
||||||
|
'form': {
|
||||||
|
'name': {
|
||||||
|
'form_field': true,
|
||||||
|
'type': 'text',
|
||||||
|
'field': 'name',
|
||||||
|
'label': i18n.t('Name'),
|
||||||
|
'placeholder': ''
|
||||||
|
},
|
||||||
|
'description': {
|
||||||
|
'form_field': true,
|
||||||
|
'type': 'text',
|
||||||
|
'field': 'description',
|
||||||
|
'label': i18n.t('Description'),
|
||||||
|
'placeholder': ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
static RECIPE = {
|
static RECIPE = {
|
||||||
|
@ -3,47 +3,47 @@
|
|||||||
"assets": {
|
"assets": {
|
||||||
"../../templates/sw.js": {
|
"../../templates/sw.js": {
|
||||||
"name": "../../templates/sw.js",
|
"name": "../../templates/sw.js",
|
||||||
"path": "..\\..\\templates\\sw.js"
|
"path": "../../templates/sw.js"
|
||||||
},
|
},
|
||||||
"css/chunk-vendors.css": {
|
"css/chunk-vendors.css": {
|
||||||
"name": "css/chunk-vendors.css",
|
"name": "css/chunk-vendors.css",
|
||||||
"path": "css\\chunk-vendors.css"
|
"path": "css/chunk-vendors.css"
|
||||||
},
|
},
|
||||||
"js/chunk-vendors.js": {
|
"js/chunk-vendors.js": {
|
||||||
"name": "js/chunk-vendors.js",
|
"name": "js/chunk-vendors.js",
|
||||||
"path": "js\\chunk-vendors.js"
|
"path": "js/chunk-vendors.js"
|
||||||
},
|
},
|
||||||
"js/import_response_view.js": {
|
"js/import_response_view.js": {
|
||||||
"name": "js/import_response_view.js",
|
"name": "js/import_response_view.js",
|
||||||
"path": "js\\import_response_view.js"
|
"path": "js/import_response_view.js"
|
||||||
},
|
},
|
||||||
"css/model_list_view.css": {
|
"css/model_list_view.css": {
|
||||||
"name": "css/model_list_view.css",
|
"name": "css/model_list_view.css",
|
||||||
"path": "css\\model_list_view.css"
|
"path": "css/model_list_view.css"
|
||||||
},
|
},
|
||||||
"js/model_list_view.js": {
|
"js/model_list_view.js": {
|
||||||
"name": "js/model_list_view.js",
|
"name": "js/model_list_view.js",
|
||||||
"path": "js\\model_list_view.js"
|
"path": "js/model_list_view.js"
|
||||||
},
|
},
|
||||||
"js/offline_view.js": {
|
"js/offline_view.js": {
|
||||||
"name": "js/offline_view.js",
|
"name": "js/offline_view.js",
|
||||||
"path": "js\\offline_view.js"
|
"path": "js/offline_view.js"
|
||||||
},
|
},
|
||||||
"js/recipe_search_view.js": {
|
"js/recipe_search_view.js": {
|
||||||
"name": "js/recipe_search_view.js",
|
"name": "js/recipe_search_view.js",
|
||||||
"path": "js\\recipe_search_view.js"
|
"path": "js/recipe_search_view.js"
|
||||||
},
|
},
|
||||||
"js/recipe_view.js": {
|
"js/recipe_view.js": {
|
||||||
"name": "js/recipe_view.js",
|
"name": "js/recipe_view.js",
|
||||||
"path": "js\\recipe_view.js"
|
"path": "js/recipe_view.js"
|
||||||
},
|
},
|
||||||
"js/supermarket_view.js": {
|
"js/supermarket_view.js": {
|
||||||
"name": "js/supermarket_view.js",
|
"name": "js/supermarket_view.js",
|
||||||
"path": "js\\supermarket_view.js"
|
"path": "js/supermarket_view.js"
|
||||||
},
|
},
|
||||||
"js/user_file_view.js": {
|
"js/user_file_view.js": {
|
||||||
"name": "js/user_file_view.js",
|
"name": "js/user_file_view.js",
|
||||||
"path": "js\\user_file_view.js"
|
"path": "js/user_file_view.js"
|
||||||
},
|
},
|
||||||
"recipe_search_view.html": {
|
"recipe_search_view.html": {
|
||||||
"name": "recipe_search_view.html",
|
"name": "recipe_search_view.html",
|
||||||
|
Loading…
Reference in New Issue
Block a user