refactored genericAPI as ApiMixin

This commit is contained in:
smilerz
2021-09-01 12:28:33 -05:00
parent 09e886958f
commit ac08ede464
23 changed files with 128 additions and 191 deletions

View File

@@ -1,13 +1,14 @@
<template>
<div id="app" style="margin-bottom: 4vh">
<generic-modal-form
<!-- v-if prevents component from loading before this_model has been assigned -->
<generic-modal-form v-if="this_model"
:model="this_model"
:action="this_action"
:item1="this_item"
:item2="this_target"
:show="show_modal"
@finish-action="finishAction"/>
<generic-split-lists
<generic-split-lists v-if="this_model"
:list_name="this_model.name"
@reset="resetList"
@get-list="getItems"
@@ -60,8 +61,7 @@ import {BootstrapVue} from 'bootstrap-vue'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import {CardMixin, ToastMixin, genericAPI} from "@/utils/utils";
import {Models, Actions} from "@/utils/models";
import {CardMixin, ToastMixin, ApiMixin} from "@/utils/utils";
import {StandardToasts} from "@/utils/utils";
import GenericSplitLists from "@/components/GenericSplitLists";
@@ -72,23 +72,27 @@ Vue.use(BootstrapVue)
export default {
name: 'FoodListView', // TODO: make generic name
mixins: [CardMixin, ToastMixin],
mixins: [CardMixin, ToastMixin, ApiMixin],
components: {GenericHorizontalCard, GenericSplitLists, GenericModalForm},
data() {
return {
// this.Models and this.Actions inherited from ApiMixin
items_left: [],
items_right: [],
load_more_left: true,
load_more_right: true,
this_model: Models.FOOD, //TODO: mounted method to calcuate
this_model: undefined,
this_action: undefined,
this_item: {},
this_target: {},
models: Models,
show_modal:false
}
},
mounted() {
this.this_model = this.Models.FOOD //TODO: mounted method to calcuate
},
methods: {
// this.genericAPI inherited from ApiMixin
resetList: function(e) {
if (e.column === 'left') {
this.items_left = []
@@ -104,22 +108,22 @@ export default {
switch (e.action) {
case 'delete':
this.this_action = Actions.DELETE
this.this_action = this.Actions.DELETE
this.show_modal = true
break;
case 'new':
this.this_action = Actions.CREATE
this.this_action = this.Actions.CREATE
this.show_modal = true
break;
case 'edit':
this.this_item = e.source
this.this_action = Actions.UPDATE
this.this_action = this.Actions.UPDATE
this.show_modal = true
break;
case 'move':
if (target == null) {
this.this_item = e.source
this.this_action = Actions.MOVE
this.this_action = this.Actions.MOVE
this.show_modal = true
} else {
this.moveThis(source.id, target.id)
@@ -128,7 +132,7 @@ export default {
case 'merge':
if (target == null) {
this.this_item = e.source
this.this_action = Actions.MERGE
this.this_action = this.Actions.MERGE
this.show_modal = true
} else {
this.mergeThis(e.source.id, e.target.id)
@@ -154,21 +158,21 @@ export default {
let update = undefined
if (e !== 'cancel') {
switch(this.this_action) {
case Actions.DELETE:
case this.Actions.DELETE:
this.deleteThis(this.this_item.id)
break;
case Actions.CREATE:
case this.Actions.CREATE:
this.saveThis(e.form_data)
break;
case Actions.UPDATE:
case this.Actions.UPDATE:
update = e.form_data
update.id = this.this_item.id
this.saveThis(update)
break;
case Actions.MERGE:
case this.Actions.MERGE:
this.mergeThis(this.this_item.id, e.form_data.target)
break;
case Actions.MOVE:
case this.Actions.MOVE:
this.moveThis(this.this_item.id, e.form_data.target)
break;
}
@@ -178,7 +182,7 @@ export default {
getItems: function(params, callback) {
let column = params?.column ?? 'left'
// TODO: does this need to be a callback?
genericAPI(this.this_model, Actions.LIST, params).then((result) => {
this.genericAPI(this.this_model, this.Actions.LIST, params).then((result) => {
if (result.data.results.length){
if (column ==='left') {
// if paginated results are in result.data.results otherwise just result.data
@@ -187,6 +191,7 @@ export default {
this.items_right = this.items_right.concat(result.data?.results ?? result.data)
}
// are the total elements less than the length of the array? if so, stop loading
// TODO: generalize this to handle results in result.data
callback(result.data.count > (column==="left" ? this.items_left.length : this.items_right.length))
} else {
callback(false) // stop loading
@@ -202,11 +207,11 @@ export default {
})
},
getThis: function(id, callback){
return genericAPI(this.this_model, Actions.FETCH, {'id': id})
return this.genericAPI(this.this_model, this.Actions.FETCH, {'id': id})
},
saveThis: function (thisItem) {
if (!thisItem?.id) { // if there is no item id assume it's a new item
genericAPI(this.this_model, Actions.CREATE, thisItem).then((result) => {
this.genericAPI(this.this_model, this.Actions.CREATE, thisItem).then((result) => {
// place all new items at the top of the list - could sort instead
this.items_left = [result.data].concat(this.items_left)
// this creates a deep copy to make sure that columns stay independent
@@ -217,7 +222,7 @@ export default {
StandardToasts.makeStandardToast(StandardToasts.FAIL_CREATE)
})
} else {
genericAPI(this.this_model, Actions.UPDATE, thisItem).then((result) => {
this.genericAPI(this.this_model, this.Actions.UPDATE, thisItem).then((result) => {
this.refreshThis(thisItem.id)
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_UPDATE)
}).catch((err) => {
@@ -232,12 +237,12 @@ export default {
this.clearState()
return
}
if (!source_id || !target_id) {
if (source_id === undefined || target_id === undefined) {
this.makeToast(this.$t('Warning'), this.$t('Nothing to do'), 'warning')
this.clearState()
return
}
genericAPI(this.this_model, Actions.MOVE, {'source': source_id, 'target': target_id}).then((result) => {
this.genericAPI(this.this_model, this.Actions.MOVE, {'source': source_id, 'target': target_id}).then((result) => {
if (target_id === 0) {
let item = this.findCard(source_id, this.items_left) || this.findCard(source_id, this.items_right)
this.items_left = [item].concat(this.destroyCard(source_id, this.items_left)) // order matters, destroy old card before adding it back in at root
@@ -268,7 +273,7 @@ export default {
this.clearState()
return
}
genericAPI(this.this_model, Actions.MERGE, {'source': source_id, 'target': target_id}).then((result) => {
this.genericAPI(this.this_model, this.Actions.MERGE, {'source': source_id, 'target': target_id}).then((result) => {
this.items_left = this.destroyCard(source_id, this.items_left)
this.items_right = this.destroyCard(source_id, this.items_right)
this.refreshThis(target_id)
@@ -287,7 +292,7 @@ export default {
'root': item.id,
'pageSize': 200
}
genericAPI(this.this_model, Actions.LIST, options).then((result) => {
this.genericAPI(this.this_model, this.Actions.LIST, options).then((result) => {
parent = this.findCard(item.id, col === 'left' ? this.items_left : this.items_right)
if (parent) {
Vue.set(parent, 'children', result.data.results)
@@ -306,7 +311,7 @@ export default {
'foods': food.id,
'pageSize': 200
}
genericAPI(Models.RECIPE, Actions.LIST, options).then((result) => {
this.genericAPI(this.Models.RECIPE, this.Actions.LIST, options).then((result) => {
parent = this.findCard(food.id, col === 'left' ? this.items_left : this.items_right)
if (parent) {
Vue.set(parent, 'recipes', result.data.results)
@@ -326,7 +331,7 @@ export default {
})
},
deleteThis: function(id) {
genericAPI(this.this_model, Actions.DELETE, {'id': id}).then((result) => {
this.genericAPI(this.this_model, this.Actions.DELETE, {'id': id}).then((result) => {
this.items_left = this.destroyCard(id, this.items_left)
this.items_right = this.destroyCard(id, this.items_right)
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_DELETE)

View File

@@ -69,8 +69,8 @@
<div class="row" :class="{'overflow-hidden' : show_split}" style="margin-top: 2vh">
<div class="col col-md" :class="{'mh-100 overflow-auto' : show_split}">
<generic-horizontal-card v-for="kw in keywords" v-bind:key="kw.id"
:model=kw
model_name="Keyword"
:item=kw
item_type="Keyword"
:draggable="true"
:merge="true"
:move="true"
@@ -86,12 +86,12 @@
<!-- right side keyword cards -->
<div class="col col-md mh-100 overflow-auto " v-if="show_split">
<generic-horizontal-card v-for="kw in keywords2" v-bind:key="kw.id"
:model=kw
model_name="Keyword"
:item=kw
item_type="Keyword"
:draggable="true"
:merge="true"
:move="true"
@item-action="startAction($event, 'left')"
@item-action="startAction($event, 'right')"
/>
<infinite-loading
:identifier='right'
@@ -147,26 +147,25 @@
{{this.$t("delete_confimation", {'kw': this_item.name})}} {{this_item.name}}
</b-modal>
<!-- move modal -->
<b-modal class="modal"
<b-modal class="modal" v-if="models"
:id="'id_modal_keyword_move'"
:title="this.$t('Move_Keyword')"
:ok-title="this.$t('Move')"
:cancel-title="this.$t('Cancel')"
@ok="moveKeyword(this_item.id, this_item.target.id)">
{{ this.$t("move_selection", {'child': this_item.name}) }}
<generic-multiselect
<generic-multiselect
@change="this_item.target=$event.val"
label="name"
:models="models.KEYWORD"
:model="models.KEYWORD"
:multiple="false"
:sticky_options="[{'id': 0,'name': $t('Root')}]"
:tree_api="true"
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
:placeholder="this.$t('Search')">
</generic-multiselect>
</b-modal>
<!-- merge modal -->
<b-modal class="modal"
<b-modal class="modal" v-if="models"
:id="'id_modal_keyword_merge'"
:title="this.$t('Merge_Keyword')"
:ok-title="this.$t('Merge')"
@@ -177,7 +176,6 @@
@change="this_item.target=$event.val"
:model="models.KEYWORD"
:multiple="false"
:tree_api="true"
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
:placeholder="this.$t('Search')">
</generic-multiselect>
@@ -444,7 +442,6 @@ export default {
let parent = {}
let pageSize = 200
let keyword = String(kw.id)
console.log(apiClient.listRecipes)
apiClient.listRecipes(
undefined, keyword, undefined, undefined, undefined, undefined,
@@ -500,25 +497,25 @@ export default {
})
},
findKeyword: function(kw_list, id){
if (kw_list.length == 0) {
return false
}
let keyword = kw_list.filter(kw => kw.id == id)
if (keyword.length == 1) {
return keyword[0]
} else if (keyword.length == 0) {
for (const k of kw_list.filter(kw => kw.show_children == true)) {
keyword = this.findKeyword(k.children, id)
if (keyword) {
return keyword
findKeyword: function(card_list, id){
let card_length = card_list?.length ?? 0
if (card_length == 0) {
return false
}
}
} else {
console.log('something terrible happened')
}
},
let cards = card_list.filter(obj => obj.id == id)
if (cards.length == 1) {
return cards[0]
} else if (cards.length == 0) {
for (const c of card_list.filter(x => x.show_children == true)) {
cards = this.findKeyword(c.children, id)
if (cards) {
return cards
}
}
} else {
console.log('something terrible happened')
}
},
// this would move with modals with mixin?
prepareEmoji: function() {
this.$refs._edit.addText(this.this_item.icon || '');

View File

@@ -192,7 +192,7 @@
<div class="row">
<div class="col-12">
<b-input-group class="mt-2">
<b-input-group class="mt-2" v-if="models">
<generic-multiselect @change="genericSelectChanged" parent_variable="search_books"
:initial_selection="settings.search_books"
:model="models.RECIPE_BOOK"
@@ -355,7 +355,7 @@ export default {
this.loadMealPlan()
// this.loadRecentlyViewed()
// this.refreshData(false) // this gets triggered whenthe cookies get loaded
// this.refreshData(false) // this gets triggered when the cookies get loaded
})
this.$i18n.locale = window.CUSTOM_LOCALE
@@ -414,7 +414,6 @@ export default {
this.pagination_count = result.data.count
this.recipes = this.removeDuplicates(result.data.results, recipe => recipe.id)
this.facets = result.data.facets
console.log(this.recipes)
})
},
openRandom: function () {
@@ -427,7 +426,6 @@ export default {
},
loadMealPlan: function () {
let apiClient = new ApiApiFactory()
// TODO setting to change days to look for meal plans
if (this.settings.show_meal_plan) {
apiClient.listMealPlans({
query: {

View File

@@ -1,5 +1,5 @@
<template>
<b-dropdown variant="link" toggle-class="text-decoration-none" no-caret>
<b-dropdown variant="link" toggle-class="text-decoration-none" no-caret style="boundary:window">
<template #button-content>
<i class="fas fa-ellipsis-v" ></i>
</template>

View File

@@ -19,14 +19,15 @@
<script>
import Multiselect from 'vue-multiselect'
import {genericAPI} from "@/utils/utils";
import {Actions} from "@/utils/models";
import {ApiMixin} from "@/utils/utils";
export default {
name: "GenericMultiselect",
components: {Multiselect},
mixins: [ApiMixin],
data() {
return {
// this.Models and this.Actions inherited from ApiMixin
loading: false,
objects: [],
selected_objects: [],
@@ -65,13 +66,14 @@ export default {
},
},
methods: {
// this.genericAPI inherited from ApiMixin
search: function (query) {
let options = {
'page': 1,
'pageSize': 10,
'query': query
}
genericAPI(this.model, Actions.LIST, options).then((result) => {
this.genericAPI(this.model, this.Actions.LIST, options).then((result) => {
this.objects = this.sticky_options.concat(result.data?.results ?? result.data)
})
},

View File

@@ -146,20 +146,8 @@ export default {
},
methods: {
resetSearch: function () {
if (this.search_right !== '') {
this.search_right = ''
} else {
this.left_page = 1
this.$emit('reset', {'column':'left'})
this.left += 1
}
if (this.search_left !== '') {
this.search_left = ''
} else {
this.right_page = 1
this.$emit('reset', {'column':'right'})
this.right += 1
}
this.search_right = ''
this.search_left = ''
},
infiniteHandler: function($state, col) {
let params = {
@@ -169,7 +157,7 @@ export default {
}
// TODO: change this to be an emit and watch a prop to determine if loaded or complete
new Promise((callback) => this.$emit('get-list', params, callback)).then((result) => {
this[col+'_page']+=1
this[col+'_page'] += 1
$state.loaded();
if (!result) { // callback needs to return true if handler should continue loading more data
$state.complete();
@@ -178,7 +166,6 @@ export default {
$state.complete();
})
},
}
}

View File

@@ -46,12 +46,11 @@ export default {
name: 'GenericModalForm',
components: {CheckboxInput, LookupInput, TextInput},
props: {
model: {type: Object, default: function() {}},
action: {type: Object, default: function() {}},
model: {required: true, type: Object, default: function() {}},
action: {required: true, type: Object, default: function() {}},
item1: {type: Object, default: function() {}},
item2: {type: Object, default: function() {}},
// action: {type: String, default: ''},
show: {type: Boolean, default: false},
show: {required: true, type: Boolean, default: false},
},
data() {
return {

View File

@@ -16,7 +16,6 @@
</template>
<script>
import Vue from "vue";
import GenericMultiselect from "@/components/GenericMultiselect";
export default {

View File

@@ -161,35 +161,47 @@ import {ApiApiFactory} from "@/utils/openapi/api.ts"; // TODO: is it possible t
import axios from "axios";
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN"
export function genericAPI(model, action, options) {
let setup = getConfig(model, action)
let func = setup.function
let config = setup?.config ?? {}
let params = setup?.params ?? []
let parameters = []
let this_value = undefined
params.forEach(function (item, index) {
if (Array.isArray(item)) {
this_value = {}
// if the value is an array, convert it to a dictionary of key:value
// filtered based on OPTIONS passed
// maybe map/reduce is better?
for (const [k, v] of Object.entries(options)) {
if (item.includes(k)) {
this_value[k] = formatParam(config?.[k], v)
import { Actions, Models } from './models';
export const ApiMixin = {
data() {
return {
Models: Models,
Actions: Actions
}
},
methods: {
genericAPI: function(model, action, options) {
let setup = getConfig(model, action)
let func = setup.function
let config = setup?.config ?? {}
let params = setup?.params ?? []
let parameters = []
let this_value = undefined
params.forEach(function (item, index) {
if (Array.isArray(item)) {
this_value = {}
// if the value is an array, convert it to a dictionary of key:value
// filtered based on OPTIONS passed
// maybe map/reduce is better?
for (const [k, v] of Object.entries(options)) {
if (item.includes(k)) {
this_value[k] = formatParam(config?.[k], v)
}
}
} else {
this_value = formatParam(config?.[item], options?.[item] ?? undefined)
}
}
} else {
this_value = formatParam(config?.[item], options?.[item] ?? undefined)
// if no value is found so far, get the default if it exists
if (this_value === undefined) {
this_value = getDefault(config?.[item], options)
}
parameters.push(this_value)
});
let apiClient = new ApiApiFactory()
return apiClient[func](...parameters)
}
// if no value is found so far, get the default if it exists
if (!this_value) {
this_value = getDefault(config?.[item], options)
}
parameters.push(this_value)
});
let apiClient = new ApiApiFactory()
return apiClient[func](...parameters)
}
}
// /*
@@ -202,7 +214,9 @@ function formatParam(config, value) {
case 'type':
switch(v) {
case 'string':
value = String(value)
if (value !== undefined){
value = String(value)
}
break;
case 'integer':
value = parseInt(value)
@@ -328,7 +342,6 @@ function formTranslate(translate, model, item1, item2) {
// * Utility functions to use manipulate nested components
// * */
import Vue from 'vue'
import { Actions } from './models';
export const CardMixin = {
methods: {
findCard: function(id, card_list){