@afeefa/vue-app 0.0.46 → 0.0.50

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. package/.afeefa/package/release/version.txt +1 -1
  2. package/package.json +1 -1
  3. package/src/api-resources/ApiActions.js +4 -55
  4. package/src/components/AModal.vue +1 -2
  5. package/src/components/ASearchSelect.vue +5 -6
  6. package/src/components/ASelect.vue +10 -2
  7. package/src/components/list/{RouteFilterSource.js → CurrentRouteFilterSource.js} +2 -2
  8. package/src/components/list/FilterSourceType.js +7 -0
  9. package/src/components/list/ListFilterMixin.js +0 -3
  10. package/src/components/list/ListViewMixin.js +51 -41
  11. package/src/components/list/NextRouteFilterSource.js +15 -0
  12. package/src/components/list/filters/ListFilterSelect.vue +29 -24
  13. package/src/components/search-select/SearchSelectList.vue +4 -1
  14. package/src/plugins/api-resources/ApiResourcesPlugin.js +13 -0
  15. package/src/utils/props-helper.js +21 -0
  16. package/src-admin/bootstrap.js +2 -0
  17. package/src-admin/components/Splash.vue +8 -1
  18. package/src-admin/components/detail/DetailProperty.vue +5 -5
  19. package/src-admin/components/list/ListView.vue +6 -1
  20. package/src-admin/components/pages/CreatePage.vue +9 -12
  21. package/src-admin/components/pages/DetailPage.vue +5 -8
  22. package/src-admin/components/pages/EditPage.vue +10 -13
  23. package/src-admin/components/pages/ListPage.vue +8 -18
  24. package/src-admin/components/routes/DetailRoute.vue +7 -2
  25. package/src-admin/components/routes/EditRoute.vue +7 -2
  26. package/src-admin/components/routes/ListRoute.vue +20 -6
  27. package/src-admin/models/Model.js +10 -7
  28. package/src-admin/styles.scss +4 -2
  29. package/src/components/list/QuerySourceType.js +0 -4
  30. package/src/components/list/RouteParamsFilterSource.js +0 -18
@@ -1 +1 @@
1
- 0.0.46
1
+ 0.0.50
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afeefa/vue-app",
3
- "version": "0.0.46",
3
+ "version": "0.0.50",
4
4
  "description": "",
5
5
  "author": "Afeefa Kollektiv <kollektiv@afeefa.de>",
6
6
  "license": "MIT",
@@ -1,8 +1,6 @@
1
- import { RouteParamsFilterSource } from '@a-vue/components/list/RouteParamsFilterSource'
2
1
  import { AlertEvent, DialogEvent, LoadingEvent, SaveEvent } from '@a-vue/events'
3
2
  import { eventBus } from '@a-vue/plugins/event-bus/EventBus'
4
3
  import { sleep } from '@a-vue/utils/timeout'
5
- import { RequestFilters } from '@afeefa/api-resources-client'
6
4
 
7
5
  export class GetAction {
8
6
  action = null
@@ -35,7 +33,7 @@ export class GetAction {
35
33
  eventBus.dispatch(new LoadingEvent(LoadingEvent.START_LOADING))
36
34
  }
37
35
 
38
- const result = await this.action.request()
36
+ const result = await this.action.createRequest()
39
37
  .params({
40
38
  id: this.id
41
39
  })
@@ -111,7 +109,7 @@ export class SaveAction {
111
109
 
112
110
  const startTime = Date.now()
113
111
 
114
- const result = await this.action.request()
112
+ const result = await this.action.createRequest()
115
113
  .params({
116
114
  id: this.id || undefined
117
115
  })
@@ -195,7 +193,7 @@ export class RemoveAction {
195
193
 
196
194
  const startTime = Date.now()
197
195
 
198
- const result = await this.action.request()
196
+ const result = await this.action.createRequest()
199
197
  .params({
200
198
  id: this.id
201
199
  })
@@ -230,11 +228,6 @@ export class RemoveAction {
230
228
 
231
229
  export class ListAction {
232
230
  request = null
233
- action = null
234
- fields = null
235
- scopes = {}
236
- filters = {}
237
- route = null
238
231
  events = true
239
232
 
240
233
  setRequest (request) {
@@ -242,31 +235,6 @@ export class ListAction {
242
235
  return this
243
236
  }
244
237
 
245
- setAction (action) {
246
- this.action = action
247
- return this
248
- }
249
-
250
- setFiltersForRoute (route) {
251
- this.route = route
252
- return this
253
- }
254
-
255
- setFields (fields) {
256
- this.fields = fields
257
- return this
258
- }
259
-
260
- setScopes (scopes) {
261
- this.scopes = scopes
262
- return this
263
- }
264
-
265
- setFilters (filters) {
266
- this.filters = filters
267
- return this
268
- }
269
-
270
238
  noEvents (noEvents) {
271
239
  this.events = noEvents === undefined ? false : !noEvents
272
240
  return this
@@ -277,26 +245,7 @@ export class ListAction {
277
245
  eventBus.dispatch(new LoadingEvent(LoadingEvent.START_LOADING))
278
246
  }
279
247
 
280
- let filters = this.filters
281
-
282
- if (this.route) {
283
- const querySource = new RouteParamsFilterSource(this.route)
284
- const requestFilters = this.action.createRequestFilters(null, querySource)
285
- const storedFilters = RequestFilters.fromHistory(this.route.path)
286
- if (storedFilters) {
287
- requestFilters.initFromUsed(storedFilters.serialize(), 1)
288
- }
289
- filters = requestFilters.serialize()
290
- }
291
-
292
- const request = this.request ||
293
- this.action
294
- .request()
295
- .scopes(this.scopes)
296
- .filters(filters)
297
- .fields(this.fields)
298
-
299
- const result = await request.send()
248
+ const result = await this.request.send()
300
249
 
301
250
  if (result.error) {
302
251
  if (this.events) {
@@ -64,7 +64,7 @@ export default class ADialog extends Mixins(UsesPositionServiceMixin, ComponentW
64
64
  }
65
65
 
66
66
  mounted () {
67
- // monkey patch onFousin to allow non vuetify-popups to receive focus
67
+ // monkey patch onFocusin to allow non vuetify-popups to receive focus
68
68
  const dialog = this.$refs.dialog
69
69
  const onFocusin = dialog.onFocusin
70
70
  dialog.onFocusin = e => {
@@ -127,7 +127,6 @@ export default class ADialog extends Mixins(UsesPositionServiceMixin, ComponentW
127
127
 
128
128
  createTransientAnchor () {
129
129
  let transientAnchorEl = document.querySelector('.' + this.transientAnchorClass)
130
- console.log('anchor', this.anchorPosition)
131
130
  if (!transientAnchorEl) {
132
131
  transientAnchorEl = document.createElement('div')
133
132
  transientAnchorEl.classList.add(this.transientAnchorClass)
@@ -46,11 +46,10 @@
46
46
  v-if="isOpen"
47
47
  v-bind="listConfig"
48
48
  :q="q"
49
- :noEvents="true"
50
- :noHistory="true"
49
+ :events="false"
50
+ :history="false"
51
51
  :filterSource="filterSource"
52
52
  :loadOnlyIfKeyword="_loadOnlyIfKeyword"
53
- :filters.sync="filters"
54
53
  :count.sync="count"
55
54
  :isLoading.sync="isLoading"
56
55
  >
@@ -76,7 +75,7 @@
76
75
  <div class="lastColumn" />
77
76
  </template>
78
77
 
79
- <template #not-found>
78
+ <template #not-found="{ filters }">
80
79
  <slot
81
80
  name="not-found"
82
81
  :filters="filters"
@@ -93,7 +92,7 @@ import { Component, Watch, Mixins } from 'vue-property-decorator'
93
92
  import { UsesPositionServiceMixin } from '../services/position/UsesPositionServiceMixin'
94
93
  import { PositionConfig } from '../services/PositionService'
95
94
  import { randomCssClass } from '../utils/random'
96
- import { QuerySourceType } from '@a-vue/components/list/QuerySourceType'
95
+ import { FilterSourceType } from '@a-vue/components/list/FilterSourceType'
97
96
  import SearchSelectFilters from './search-select/SearchSelectFilters'
98
97
  import SearchSelectList from './search-select/SearchSelectList'
99
98
  import { CancelOnEscMixin } from '@a-vue/services/escape/CancelOnEscMixin'
@@ -109,7 +108,7 @@ export default class ASearchSelect extends Mixins(UsesPositionServiceMixin, Canc
109
108
  selectId = randomCssClass(10)
110
109
  isOpen = false
111
110
  items_ = []
112
- filterSource = QuerySourceType.OBJECT
111
+ filterSource = FilterSourceType.OBJECT
113
112
 
114
113
  isLoading = false
115
114
  filters = []
@@ -23,14 +23,22 @@ export default class ASelect extends Mixins(ComponentWidthMixin) {
23
23
  items_ = []
24
24
 
25
25
  mounted () {
26
- // monkey patch v-select setting 'undefined' on clearable
26
+ // monkey patch v-select to set default value on clear
27
+ const clearableCallback = this.select.clearableCallback
28
+ this.select.clearableCallback = () => {
29
+ this.select.isClear = true
30
+ clearableCallback()
31
+ this.select.isClear = false
32
+ }
33
+
27
34
  const setValue = this.select.setValue
28
35
  this.select.setValue = value => {
29
- if (!value && value !== false) { // if undefined alike
36
+ if (this.select.isClear) {
30
37
  value = this.defaultValue || null
31
38
  }
32
39
  setValue(value)
33
40
  }
41
+
34
42
  this.init()
35
43
  }
36
44
 
@@ -1,6 +1,6 @@
1
- import { BaseFilterSource } from '@afeefa/api-resources-client'
1
+ import { ListViewFilterSource } from '@afeefa/api-resources-client'
2
2
 
3
- export class RouteFilterSource extends BaseFilterSource {
3
+ export class CurrentRouteFilterSource extends ListViewFilterSource {
4
4
  router = null
5
5
 
6
6
  constructor (router) {
@@ -0,0 +1,7 @@
1
+ export class FilterSourceType {
2
+ // filters are synchronized with url's query string
3
+ static QUERY_STRING = 'query_string'
4
+
5
+ // filters are synchronized with plain history object
6
+ static OBJECT = 'object'
7
+ }
@@ -28,12 +28,9 @@ export class ListFilterMixin extends Vue {
28
28
 
29
29
  get filter () {
30
30
  const filter = this.filters[this._name]
31
-
32
-
33
31
  if (!filter) {
34
32
  console.warn(`Filter "${this._name}" not found`)
35
33
  }
36
-
37
34
  return filter
38
35
  }
39
36
 
@@ -1,23 +1,30 @@
1
1
  import { ListAction } from '@a-vue/api-resources/ApiActions'
2
+ import { propsWithDefaults } from '@a-vue/utils/props-helper'
3
+ import { ListViewModel } from '@afeefa/api-resources-client'
2
4
  import { Component, Vue, Watch } from 'vue-property-decorator'
3
5
 
4
- import { QuerySourceType } from './QuerySourceType'
5
- import { RouteFilterSource } from './RouteFilterSource'
6
+ import { CurrentRouteFilterSource } from './CurrentRouteFilterSource'
7
+ import { FilterSourceType } from './FilterSourceType'
6
8
 
7
9
  @Component({
8
- props: [
9
- 'models', 'meta', // if already loaded
10
- 'action', 'scopes', 'initialFilters', 'fields',
11
- 'noEvents', 'noHistory', 'filterHistoryKey', 'filterSource',
12
- 'loadOnlyIfKeyword'
13
- ]
10
+ ...propsWithDefaults([
11
+ 'models', 'meta', // given, if already loaded
12
+ 'listViewConfig',
13
+ 'filterHistoryKey',
14
+ 'loadOnlyIfKeyword',
15
+ {
16
+ filterSource: FilterSourceType.QUERY_STRING,
17
+ events: true,
18
+ history: true
19
+ }
20
+ ])
14
21
  })
15
22
  export class ListViewMixin extends Vue {
16
23
  LIST_VIEW = true
17
24
 
25
+ listViewModel = null
18
26
  models_ = []
19
27
  meta_ = {}
20
- requestFilters = null
21
28
  isLoading = false
22
29
 
23
30
  created () {
@@ -25,37 +32,41 @@ export class ListViewMixin extends Vue {
25
32
  }
26
33
 
27
34
  destroyed () {
28
- this.requestFilters.off('change', this.filtersChanged)
35
+ this.listViewModel.off('change', this.filtersChanged)
29
36
  }
30
37
 
31
38
  init () {
32
- if (this.models) {
33
- this.models_ = this.models
34
- this.meta_ = this.meta
35
- }
36
-
37
- if (this.requestFilters) {
38
- this.requestFilters.off('change', this.filtersChanged)
39
+ if (this.listViewModel) {
40
+ // this can happen only on HMR-reload
41
+ this.listViewModel.off('change', this.filtersChanged)
39
42
  }
40
43
 
41
- const historyKey = this.noHistory
42
- ? undefined
43
- : [this.$route.path, this.filterHistoryKey].filter(i => i).join('.')
44
- const querySource = this.filterSource === QuerySourceType.OBJECT ? undefined : new RouteFilterSource(this.$router)
45
- this.requestFilters = this.action.createRequestFilters(historyKey, querySource)
44
+ const historyKey = this.history
45
+ ? [this.$route.path, this.filterHistoryKey].filter(i => i).join('.')
46
+ : undefined
47
+ const filterSource = this.filterSource === FilterSourceType.QUERY_STRING
48
+ ? new CurrentRouteFilterSource(this.$router)
49
+ : undefined
46
50
 
47
- if (this.initialFilters) {
48
- console.log('set initial Filters: ', this.initialFilters)
49
- this.requestFilters.initFromUsed(this.initialFilters)
51
+ if (this.models) {
52
+ this.models_ = this.models
53
+ this.meta_ = this.meta
50
54
  }
51
55
 
52
- this.requestFilters.on('change', this.filtersChanged)
56
+ this.listViewModel = new ListViewModel(this.listViewConfig)
57
+ .filterSource(filterSource, !!filterSource)
58
+ .historyKey(historyKey, this.history)
59
+ .usedFilters(this.meta_.used_filters || null, this.meta_.count_search || 0)
60
+ .initFilters({
61
+ source: !!filterSource,
62
+ history: !!historyKey,
63
+ used: !!this.models
64
+ })
65
+ .on('change', this.filtersChanged) // listen to change
53
66
 
54
- this.$emit('update:filters', this.filters)
55
67
  this._filtersInitialized()
56
68
 
57
69
  if (this.models) {
58
- this.requestFilters.initFromUsed(this.meta_.used_filters, this.meta_.count_search)
59
70
  this.$emit('update:count', this.meta_.count_search)
60
71
  } else {
61
72
  this.load()
@@ -64,8 +75,8 @@ export class ListViewMixin extends Vue {
64
75
 
65
76
  @Watch('$route.query')
66
77
  routeQueryChanged () {
67
- if (this.filterSource !== QuerySourceType.ROUTE) {
68
- this.requestFilters.querySourceChanged()
78
+ if (this.filterSource === FilterSourceType.QUERY_STRING) {
79
+ this.listViewModel.filterSourceChanged()
69
80
  }
70
81
  }
71
82
 
@@ -79,16 +90,15 @@ export class ListViewMixin extends Vue {
79
90
  }
80
91
 
81
92
  filtersChanged () {
82
- console.log('filters changed')
83
93
  this.load()
84
94
  }
85
95
 
86
96
  resetFilters () {
87
- this.requestFilters.reset()
97
+ this.listViewModel.resetFilters()
88
98
  }
89
99
 
90
100
  get filters () {
91
- return this.requestFilters.getFilters()
101
+ return this.listViewModel.getFilters().getEntries()
92
102
  }
93
103
 
94
104
  get count () {
@@ -110,16 +120,16 @@ export class ListViewMixin extends Vue {
110
120
  this.isLoading = true
111
121
  this.$emit('update:isLoading', this.isLoading)
112
122
 
123
+ const request = this.listViewModel.getApiRequest()
124
+
113
125
  const {models, meta} = await new ListAction()
114
- .setAction(this.action)
115
- .setScopes(this.scopes)
116
- .setFilters(this.requestFilters.serialize())
117
- .setFields(this.fields)
118
- .noEvents(this.noEvents)
126
+ .setRequest(request)
127
+ .noEvents(!this.events)
119
128
  .load()
120
129
 
121
- if (!models) { // error, reset filters
122
- this.resetFilters()
130
+ if (!models) { // error happened
131
+ this.isLoading = false
132
+ this.$emit('update:isLoading', this.isLoading)
123
133
  return
124
134
  }
125
135
 
@@ -127,7 +137,7 @@ export class ListViewMixin extends Vue {
127
137
  this.meta_ = meta
128
138
 
129
139
  if (this.meta_.used_filters) {
130
- this.requestFilters.initFromUsed(this.meta_.used_filters, this.meta_.count_search)
140
+ this.listViewModel.initFromUsedFilters(this.meta_.used_filters, this.meta_.count_search)
131
141
  }
132
142
 
133
143
  this.isLoading = false
@@ -0,0 +1,15 @@
1
+ import { ListViewFilterSource } from '@afeefa/api-resources-client'
2
+
3
+ export class NextRouteFilterSource extends ListViewFilterSource {
4
+ route = null
5
+
6
+ constructor (route) {
7
+ super()
8
+
9
+ this.route = route
10
+ }
11
+
12
+ getQuery () {
13
+ return this.route.query
14
+ }
15
+ }
@@ -5,7 +5,8 @@
5
5
  :items="_items"
6
6
  itemText="itemTitle"
7
7
  itemValue="itemValue"
8
- :clearable="filter.value !== null"
8
+ :clearable="filter.value !== filter.defaultValue"
9
+ :defaultValue="filter.defaultValue"
9
10
  v-bind="$attrs"
10
11
  />
11
12
  </template>
@@ -16,16 +17,14 @@ import { Component, Mixins } from 'vue-property-decorator'
16
17
  import { ListFilterMixin } from '../ListFilterMixin'
17
18
  import { ListAction } from '@a-vue/api-resources/ApiActions'
18
19
 
19
- @Component({
20
- props: ['totalVisible']
21
- })
20
+ @Component
22
21
  export default class ListFilterSelect extends Mixins(ListFilterMixin) {
23
22
  items = null
24
23
 
25
24
  created () {
26
- if (this.hasOptionsRequest()) {
25
+ if (this.filter.hasOptionsRequest()) {
27
26
  this.items = this.loadRequestOptions()
28
- } else if (this.hasOptions()) {
27
+ } else if (this.filter.hasOptions()) {
29
28
  this.items = this.getOptions()
30
29
  }
31
30
  }
@@ -34,43 +33,49 @@ export default class ListFilterSelect extends Mixins(ListFilterMixin) {
34
33
  return this.$attrs.items || this.items || []
35
34
  }
36
35
 
37
- hasOptions () {
38
- return this.filter.options
36
+ get showNullOption () {
37
+ // either null is a selectable option, than it should be shown in the list
38
+ // or the default is null, so the list should offer a 'null' option for the unselected state
39
+ return this.filter.nullIsOption || this.filter.defaultValue === null
39
40
  }
40
41
 
41
- hasOptionsRequest () {
42
- return this.filter.request
42
+ createOptions () {
43
+ const options = []
44
+
45
+ if (this.showNullOption) {
46
+ options.push({
47
+ itemTitle: 'Alle',
48
+ itemValue: null
49
+ })
50
+ }
51
+
52
+ return options
43
53
  }
44
54
 
45
55
  async loadRequestOptions () {
46
56
  const {models} = await new ListAction()
47
- .setRequest(this.filter.request)
57
+ .setRequest(this.filter.createOptionsRequest())
48
58
  .noEvents()
49
59
  .load()
50
60
 
51
61
  return [
52
- {
53
- itemTitle: 'Alle',
54
- itemValue: null
55
- },
62
+ ...this.createOptions(),
56
63
  ...models.map(model => ({
57
64
  itemTitle: model.name,
58
65
  itemValue: model.id
59
66
  }))
60
-
61
67
  ]
62
68
  }
63
69
 
64
70
  getOptions () {
65
71
  return [
66
- {
67
- itemTitle: 'Alle',
68
- itemValue: null
69
- },
70
- ...this.filter.options.map(o => ({
71
- itemTitle: o ? 'Ja' : 'Nein',
72
- itemValue: o
73
- }))
72
+ ...this.createOptions(),
73
+ ...this.filter.options
74
+ .filter(o => o !== null) // null is already set in options (if any)
75
+ .map(o => ({
76
+ itemTitle: o ? 'Ja' : 'Nein',
77
+ itemValue: o
78
+ }))
74
79
  ]
75
80
  }
76
81
  }
@@ -26,7 +26,10 @@
26
26
  v-else-if="showNotFound"
27
27
  class="notFound"
28
28
  >
29
- <slot name="not-found">
29
+ <slot
30
+ name="not-found"
31
+ :filters="filters"
32
+ >
30
33
  Nichts gefunden.
31
34
  </slot>
32
35
  </div>
@@ -0,0 +1,13 @@
1
+ import { apiResources } from '@afeefa/api-resources-client'
2
+
3
+ class ApiResourcesPlugin {
4
+ install (Vue) {
5
+ Object.defineProperty(Vue.prototype, '$apiResources', {
6
+ get () {
7
+ return apiResources
8
+ }
9
+ })
10
+ }
11
+ }
12
+
13
+ export const apiResourcesPlugin = new ApiResourcesPlugin()
@@ -0,0 +1,21 @@
1
+ export function propsWithDefaults (props) {
2
+ const normalizedProps = {}
3
+
4
+ for (const prop of props) {
5
+ if (typeof prop === 'object') {
6
+ Object.keys(prop).forEach(subProp => {
7
+ if (typeof prop[subProp] === 'object') {
8
+ normalizedProps[subProp] = prop[subProp]
9
+ } else {
10
+ normalizedProps[subProp] = { default: prop[subProp] }
11
+ }
12
+ })
13
+ } else {
14
+ normalizedProps[prop] = null
15
+ }
16
+ }
17
+
18
+ return {
19
+ props: normalizedProps
20
+ }
21
+ }
@@ -1,6 +1,7 @@
1
1
  import './config/event-bus'
2
2
  import './config/components'
3
3
 
4
+ import { apiResourcesPlugin } from '@a-vue/plugins/api-resources/ApiResourcesPlugin'
4
5
  import { hasOptionsPlugin } from '@a-vue/plugins/has-options/HasOptionsPlugin'
5
6
  import { timeout } from '@a-vue/utils/timeout'
6
7
  import { apiResources } from '@afeefa/api-resources-client'
@@ -10,6 +11,7 @@ import { appConfig } from './config/AppConfig'
10
11
  import routeConfigPlugin from './config/routing'
11
12
  import vuetify from './config/vuetify'
12
13
 
14
+ Vue.use(apiResourcesPlugin)
13
15
  Vue.use(hasOptionsPlugin)
14
16
 
15
17
  Vue.config.productionTip = false
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <v-app>
2
+ <v-app class="splash">
3
3
  <v-container
4
4
  fill-height
5
5
  fluid
@@ -49,6 +49,13 @@ export default class Splash extends Vue {
49
49
 
50
50
 
51
51
  <style lang="scss" scoped>
52
+ .splash {
53
+ position: absolute;
54
+ top: 0;
55
+ left: 0;
56
+ width: 100vw;
57
+ }
58
+
52
59
  .logo {
53
60
  margin-bottom: 2rem;
54
61
  }
@@ -8,10 +8,10 @@
8
8
  >
9
9
  {{ _icon.icon }}
10
10
  </v-icon>
11
- <label :class="['label', {'label--withIcon': this._icon != null}]">{{ label }}</label>
11
+ <label :class="['label', {'label--withIcon': _icon != null}]">{{ label }}</label>
12
12
  </div>
13
13
 
14
- <div :class="['content', {'content--withIcon': this._icon != null}]">
14
+ <div :class="['content', {'content--withIcon': _icon != null}]">
15
15
  <a-row
16
16
  vertical
17
17
  gap="6"
@@ -37,8 +37,8 @@ export default class DetailProperty extends Vue {
37
37
  }
38
38
 
39
39
  if (this.iconModelType) {
40
- const model = apiResources.getModel(this.iconModelType)
41
- return model.icon
40
+ const ModelClass = apiResources.getModelClass(this.iconModelType)
41
+ return ModelClass.icon
42
42
  }
43
43
  }
44
44
  }
@@ -64,7 +64,7 @@ export default class DetailProperty extends Vue {
64
64
  @media (max-width: 900px), (orientation : portrait) {
65
65
  padding-left: 55px;
66
66
  &--withIcon {
67
- padding-left: 0;
67
+ padding-left: 0;
68
68
  }
69
69
  }
70
70
  }
@@ -31,6 +31,7 @@
31
31
  <slot
32
32
  name="model-table"
33
33
  :model="model"
34
+ :setFilter="setFilter"
34
35
  />
35
36
  </a-table-row>
36
37
  </a-table>
@@ -72,7 +73,7 @@ export default class ListView extends Mixins(ListViewMixin) {
72
73
 
73
74
  @Watch('isLoading')
74
75
  isLoadingChanged () {
75
- if (this.noEvents === undefined || !this.noEvents) {
76
+ if (this.events) {
76
77
  if (this.isLoading) {
77
78
  this.$events.dispatch(new LoadingEvent(LoadingEvent.START_LOADING))
78
79
  } else {
@@ -85,6 +86,10 @@ export default class ListView extends Mixins(ListViewMixin) {
85
86
  get _table () {
86
87
  return this.table !== false
87
88
  }
89
+
90
+ setFilter (name, value) {
91
+ this.filters[name].value = value
92
+ }
88
93
  }
89
94
  </script>
90
95
 
@@ -56,8 +56,8 @@ import { apiResources } from '@afeefa/api-resources-client'
56
56
  })
57
57
  export default class CreatePage extends Mixins(EditPageMixin) {
58
58
  created () {
59
- if (!this.$parent.constructor.getCreateConfig) {
60
- console.warn('<create-page> owner must provide a static getCreateConfig method.')
59
+ if (!this.$parent.constructor.createRouteConfig) {
60
+ console.warn('<create-page> owner must provide a static createRouteConfig method.')
61
61
  }
62
62
 
63
63
  this.reset()
@@ -65,11 +65,15 @@ export default class CreatePage extends Mixins(EditPageMixin) {
65
65
  }
66
66
 
67
67
  get editConfig () {
68
- return this.$parent.constructor.getCreateConfig(this.$route)
68
+ return this.$parent.constructor.createRouteConfig
69
69
  }
70
70
 
71
71
  get modelUpateAction () {
72
- return this.ModelClass.getAction(this.$routeDefinition, 'create')
72
+ return this.editConfig.createAction || this.ModelClass.getAction('create')
73
+ }
74
+
75
+ get _icon () {
76
+ return this.icon || this.modelToEdit.getIcon()
73
77
  }
74
78
 
75
79
  get _title () {
@@ -89,7 +93,7 @@ export default class CreatePage extends Mixins(EditPageMixin) {
89
93
  get _listLink () {
90
94
  if (this.listLink) {
91
95
  if (typeof this.listLink === 'function') {
92
- return this.listLink(this.$route.params)
96
+ return this.listLink()
93
97
  } else {
94
98
  return this.listLink
95
99
  }
@@ -97,13 +101,6 @@ export default class CreatePage extends Mixins(EditPageMixin) {
97
101
  return this.modelToEdit.getLink('list')
98
102
  }
99
103
 
100
- get _icon () {
101
- if (this.icon) {
102
- return this.icon
103
- }
104
- return this.modelToEdit.getIcon()
105
- }
106
-
107
104
  createModelToEdit () {
108
105
  if (this.createModel) {
109
106
  return this.createModel(this.fields)
@@ -55,7 +55,7 @@ import { Component, Vue, Watch } from 'vue-property-decorator'
55
55
  import { RemoveAction } from '@a-vue/api-resources/ApiActions'
56
56
 
57
57
  @Component({
58
- props: ['model', 'title', 'icon', 'removeAction', 'protectRemove', 'listLink']
58
+ props: ['model', 'title', 'icon', 'protectRemove', 'listLink']
59
59
  })
60
60
  export default class DetailPage extends Vue {
61
61
  $hasOptions = ['edit', 'remove', 'list']
@@ -64,7 +64,7 @@ export default class DetailPage extends Vue {
64
64
  removeConfirmed = null
65
65
 
66
66
  created () {
67
- if (!this.$parent.constructor.getDetailConfig) {
67
+ if (!this.$parent.constructor.detailRouteConfig) {
68
68
  console.warn('<detail-page> owner must provide a static getDetailConfig method.')
69
69
  }
70
70
  this.$emit('model', this.model)
@@ -80,7 +80,7 @@ export default class DetailPage extends Vue {
80
80
  }
81
81
 
82
82
  get detailConfig () {
83
- return this.$parent.constructor.getDetailConfig(this.$route)
83
+ return this.$parent.constructor.detailRouteConfig
84
84
  }
85
85
 
86
86
  get document () {
@@ -94,7 +94,7 @@ export default class DetailPage extends Vue {
94
94
  get _listLink () {
95
95
  if (this.listLink) {
96
96
  if (typeof this.listLink === 'function') {
97
- return this.listLink(this.$route.params)
97
+ return this.listLink()
98
98
  } else {
99
99
  return this.listLink
100
100
  }
@@ -110,10 +110,7 @@ export default class DetailPage extends Vue {
110
110
  }
111
111
 
112
112
  get _deleteAction () {
113
- if (this.removeAction) {
114
- return this.removeAction
115
- }
116
- return this.ModelClass.getAction(this.$routeDefinition, 'delete')
113
+ return this.detailConfig.removeAction || this.ModelClass.getAction('delete')
117
114
  }
118
115
 
119
116
  async remove () {
@@ -78,8 +78,8 @@ export default class EditPage extends Mixins(EditPageMixin) {
78
78
  model_ = null
79
79
 
80
80
  created () {
81
- if (!this.$parent.constructor.getEditConfig) {
82
- console.warn('<edit-page> owner must provide a static getEditConfig method.')
81
+ if (!this.$parent.constructor.editRouteConfig) {
82
+ console.warn('<edit-page> owner must provide a static editRouteConfig method.')
83
83
  }
84
84
 
85
85
  this.model_ = this.model
@@ -95,18 +95,22 @@ export default class EditPage extends Mixins(EditPageMixin) {
95
95
  }
96
96
 
97
97
  get editConfig () {
98
- return this.$parent.constructor.getEditConfig(this.$route)
98
+ return this.$parent.constructor.editRouteConfig
99
99
  }
100
100
 
101
101
  get modelUpateAction () {
102
- return this.ModelClass.getAction(this.$routeDefinition, 'update')
102
+ return this.editConfig.updateAction || this.ModelClass.getAction('update')
103
103
  }
104
104
 
105
105
  get _getAction () {
106
106
  if (this.getAction) {
107
107
  return this.getAction
108
108
  }
109
- return this.ModelClass.getAction(this.$routeDefinition, 'get')
109
+ return this.ModelClass.getAction('get')
110
+ }
111
+
112
+ get _icon () {
113
+ return this.icon || this.model.getIcon()
110
114
  }
111
115
 
112
116
  get _title () {
@@ -126,7 +130,7 @@ export default class EditPage extends Mixins(EditPageMixin) {
126
130
  get _listLink () {
127
131
  if (this.listLink) {
128
132
  if (typeof this.listLink === 'function') {
129
- return this.listLink(this.$route.params)
133
+ return this.listLink()
130
134
  } else {
131
135
  return this.listLink
132
136
  }
@@ -134,13 +138,6 @@ export default class EditPage extends Mixins(EditPageMixin) {
134
138
  return this.model.getLink('list')
135
139
  }
136
140
 
137
- get _icon () {
138
- if (this.icon) {
139
- return this.icon
140
- }
141
- return this.model.getIcon()
142
- }
143
-
144
141
  createModelToEdit () {
145
142
  return this.model_.cloneForEdit(this.fields)
146
143
  }
@@ -24,17 +24,21 @@ import { Component, Vue } from 'vue-property-decorator'
24
24
  import { apiResources } from '@afeefa/api-resources-client'
25
25
 
26
26
  @Component({
27
- props: ['title', 'newTitle', 'newLink', 'ModelClass']
27
+ props: ['icon', 'title', 'newTitle', 'newLink', 'Model']
28
28
  })
29
29
  export default class ListPage extends Vue {
30
30
  $hasOptions = ['add']
31
31
 
32
+ get _icon () {
33
+ return this.icon || this.Model.icon
34
+ }
35
+
32
36
  get _title () {
33
37
  if (this.title) {
34
38
  return this.title
35
39
  }
36
40
 
37
- const type = apiResources.getType(this.ModelClass.type)
41
+ const type = apiResources.getType(this.Model.type)
38
42
  return type.t('TITLE_PLURAL')
39
43
  }
40
44
 
@@ -43,26 +47,12 @@ export default class ListPage extends Vue {
43
47
  return this.newTitle
44
48
  }
45
49
 
46
- const type = apiResources.getType(this.ModelClass.type)
50
+ const type = apiResources.getType(this.Model.type)
47
51
  return type.t('TITLE_SINGULAR')
48
52
  }
49
53
 
50
- get _icon () {
51
- if (this.icon) {
52
- return this.icon
53
- }
54
- return this.ModelClass.icon
55
- }
56
-
57
54
  get _newLink () {
58
- if (this.newLink) {
59
- if (typeof this.newLink === 'function') {
60
- return this.newLink(this.$route.params)
61
- } else {
62
- return this.newLink
63
- }
64
- }
65
- return this.ModelClass.getLink('new')
55
+ return this.newLink || this.Model.getLink('new')
66
56
  }
67
57
  }
68
58
  </script>
@@ -18,8 +18,13 @@ Component.registerHooks([
18
18
  function load (route) {
19
19
  const routeDefinition = route.meta.routeDefinition
20
20
  const Component = routeDefinition.config.detail
21
- const detailConfig = Component.getDetailConfig(route)
22
- const action = detailConfig.action || detailConfig.ModelClass.getAction(routeDefinition, 'get')
21
+
22
+ if (!Component.detailRouteConfig) {
23
+ console.warn('A detail route component must implement a static detailRouteConfig property.')
24
+ }
25
+
26
+ const detailConfig = Component.detailRouteConfig
27
+ const action = detailConfig.action || detailConfig.ModelClass.getAction('get')
23
28
 
24
29
  return new GetAction()
25
30
  .setAction(action)
@@ -19,8 +19,13 @@ Component.registerHooks([
19
19
  function load (route) {
20
20
  const routeDefinition = route.meta.routeDefinition
21
21
  const Component = routeDefinition.config.edit
22
- const editConfig = Component.getEditConfig(route)
23
- const action = editConfig.getAction || editConfig.ModelClass.getAction(routeDefinition, 'get')
22
+
23
+ if (!Component.editRouteConfig) {
24
+ console.warn('An edit route component must implement a static editRouteConfig property.')
25
+ }
26
+
27
+ const editConfig = Component.editRouteConfig
28
+ const action = editConfig.getAction || editConfig.ModelClass.getAction('get')
24
29
 
25
30
  return new GetAction()
26
31
  .setAction(action)
@@ -10,6 +10,8 @@
10
10
  <script>
11
11
  import { Component, Vue, Watch } from 'vue-property-decorator'
12
12
  import { ListAction } from '@a-vue/api-resources/ApiActions'
13
+ import { NextRouteFilterSource } from '@a-vue/components/list/NextRouteFilterSource'
14
+ import { ListViewModel } from '@afeefa/api-resources-client'
13
15
 
14
16
  Component.registerHooks([
15
17
  'beforeRouteEnter',
@@ -28,14 +30,26 @@ let lastVm = null
28
30
  function load (route) {
29
31
  const routeDefinition = route.meta.routeDefinition
30
32
  const Component = routeDefinition.config.list
31
- const listConfig = Component.getListConfig(route)
32
- const action = listConfig.action || listConfig.ModelClass.getAction(routeDefinition, 'list')
33
+
34
+ if (!Component.listViewConfig) {
35
+ console.warn('A list route component must implement a static listViewConfig property.')
36
+ }
37
+
38
+ const request = new ListViewModel(Component.listViewConfig)
39
+ // read from next route query string, but do not push
40
+ // list component will be init with used_filters
41
+ .filterSource(new NextRouteFilterSource(route), false)
42
+ // read from history, but do not push
43
+ // list component will be init with used_filters
44
+ .historyKey(route.path, false)
45
+ .initFilters({
46
+ source: true,
47
+ history: true
48
+ })
49
+ .getApiRequest()
33
50
 
34
51
  return new ListAction()
35
- .setAction(action)
36
- .setFields(listConfig.fields)
37
- .setScopes(listConfig.scopes)
38
- .setFiltersForRoute(route)
52
+ .setRequest(request)
39
53
  .load()
40
54
  }
41
55
 
@@ -1,5 +1,6 @@
1
- import { Model as ApiResourcesModel } from '@afeefa/api-resources-client'
1
+ import { Model as ApiResourcesModel, apiResources } from '@afeefa/api-resources-client'
2
2
  import { mdiAlphaMCircle } from '@mdi/js'
3
+
3
4
  export class Model extends ApiResourcesModel {
4
5
  static resourceType = null
5
6
  static routeName = null
@@ -9,12 +10,14 @@ export class Model extends ApiResourcesModel {
9
10
  return (new this()).getLink(action)
10
11
  }
11
12
 
12
- static getAction (routeDefinition, action) {
13
+ static getAction (action) {
13
14
  if (this.resourceType) {
14
- const api = routeDefinition.config.api
15
- return api.getAction(this.resourceType, action)
15
+ return apiResources.getAction({
16
+ resource: this.resourceType,
17
+ action
18
+ })
16
19
  }
17
- console.warn('You can\'t get an action of a model without resourceType:', this.type)
20
+ console.warn('You can\'t get an action out of a model without resourceType:', this.type)
18
21
  return null
19
22
  }
20
23
 
@@ -32,8 +35,8 @@ export class Model extends ApiResourcesModel {
32
35
  }
33
36
  }
34
37
 
35
- getAction (routeDefinition, action) {
36
- return this.constructor.getAction(routeDefinition, action)
38
+ getAction (action) {
39
+ return this.constructor.getAction(action)
37
40
  }
38
41
 
39
42
  getIcon () {
@@ -21,14 +21,16 @@
21
21
  }
22
22
 
23
23
  .v-application {
24
- margin-bottom: 2rem;
25
-
26
24
  .text-strike {
27
25
  color: inherit;
28
26
  text-decoration: line-through;
29
27
  }
30
28
  }
31
29
 
30
+ .v-main {
31
+ margin-bottom: 2rem;
32
+ }
33
+
32
34
  .theme--light.v-btn.v-btn--has-bg {
33
35
  background-color: #E9E9E9;
34
36
  }
@@ -1,4 +0,0 @@
1
- export class QuerySourceType {
2
- static ROUTE = 'route'
3
- static OBJECT = 'object'
4
- }
@@ -1,18 +0,0 @@
1
- import { BaseFilterSource } from '@afeefa/api-resources-client'
2
-
3
- export class RouteParamsFilterSource extends BaseFilterSource {
4
- route = null
5
-
6
- constructor (route) {
7
- super()
8
-
9
- this.route = route
10
- }
11
-
12
- getQuery () {
13
- return this.route.query
14
- }
15
-
16
- push (_query) {
17
- }
18
- }