@afeefa/vue-app 0.0.46 → 0.0.47

Sign up to get free protection for your applications and to get access to all the features.
@@ -1 +1 @@
1
- 0.0.46
1
+ 0.0.47
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.47",
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) {
@@ -93,7 +93,7 @@ import { Component, Watch, Mixins } from 'vue-property-decorator'
93
93
  import { UsesPositionServiceMixin } from '../services/position/UsesPositionServiceMixin'
94
94
  import { PositionConfig } from '../services/PositionService'
95
95
  import { randomCssClass } from '../utils/random'
96
- import { QuerySourceType } from '@a-vue/components/list/QuerySourceType'
96
+ import { FilterSourceType } from '@a-vue/components/list/FilterSourceType'
97
97
  import SearchSelectFilters from './search-select/SearchSelectFilters'
98
98
  import SearchSelectList from './search-select/SearchSelectList'
99
99
  import { CancelOnEscMixin } from '@a-vue/services/escape/CancelOnEscMixin'
@@ -109,7 +109,7 @@ export default class ASearchSelect extends Mixins(UsesPositionServiceMixin, Canc
109
109
  selectId = randomCssClass(10)
110
110
  isOpen = false
111
111
  items_ = []
112
- filterSource = QuerySourceType.OBJECT
112
+ filterSource = FilterSourceType.OBJECT
113
113
 
114
114
  isLoading = false
115
115
  filters = []
@@ -0,0 +1,10 @@
1
+ export class FilterSourceType {
2
+ // filters are synchronized with url's query string
3
+ static QUERY_STRING = 'query_string'
4
+
5
+ // filters are initialized from given route params (no synchronisation allowed)
6
+ static ROUTE_PARAMS = 'route_params'
7
+
8
+ // filters are synchronized with plain history object
9
+ static OBJECT = 'object'
10
+ }
@@ -1,14 +1,15 @@
1
1
  import { ListAction } from '@a-vue/api-resources/ApiActions'
2
2
  import { Component, Vue, Watch } from 'vue-property-decorator'
3
3
 
4
- import { QuerySourceType } from './QuerySourceType'
4
+ import { FilterSourceType } from './FilterSourceType'
5
5
  import { RouteFilterSource } from './RouteFilterSource'
6
6
 
7
7
  @Component({
8
8
  props: [
9
9
  'models', 'meta', // if already loaded
10
- 'action', 'scopes', 'initialFilters', 'fields',
11
- 'noEvents', 'noHistory', 'filterHistoryKey', 'filterSource',
10
+ 'listViewRequest',
11
+ 'noEvents', 'noHistory',
12
+ 'filterHistoryKey', 'filterSource',
12
13
  'loadOnlyIfKeyword'
13
14
  ]
14
15
  })
@@ -35,19 +36,16 @@ export class ListViewMixin extends Vue {
35
36
  }
36
37
 
37
38
  if (this.requestFilters) {
39
+ // this can happen only on HMR-reload
38
40
  this.requestFilters.off('change', this.filtersChanged)
39
41
  }
40
42
 
41
43
  const historyKey = this.noHistory
42
44
  ? undefined
43
45
  : [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)
46
-
47
- if (this.initialFilters) {
48
- console.log('set initial Filters: ', this.initialFilters)
49
- this.requestFilters.initFromUsed(this.initialFilters)
50
- }
46
+ const filterSource = this.filterSource === FilterSourceType.OBJECT ? undefined : new RouteFilterSource(this.$router)
47
+ const action = this.listViewRequest.getAction()
48
+ this.requestFilters = action.createRequestFilters(historyKey, filterSource)
51
49
 
52
50
  this.requestFilters.on('change', this.filtersChanged)
53
51
 
@@ -64,8 +62,8 @@ export class ListViewMixin extends Vue {
64
62
 
65
63
  @Watch('$route.query')
66
64
  routeQueryChanged () {
67
- if (this.filterSource !== QuerySourceType.ROUTE) {
68
- this.requestFilters.querySourceChanged()
65
+ if (this.filterSource !== FilterSourceType.QUERY_STRING) {
66
+ this.requestFilters.filterSourceChanged()
69
67
  }
70
68
  }
71
69
 
@@ -79,7 +77,6 @@ export class ListViewMixin extends Vue {
79
77
  }
80
78
 
81
79
  filtersChanged () {
82
- console.log('filters changed')
83
80
  this.load()
84
81
  }
85
82
 
@@ -110,11 +107,12 @@ export class ListViewMixin extends Vue {
110
107
  this.isLoading = true
111
108
  this.$emit('update:isLoading', this.isLoading)
112
109
 
110
+ const request = this.listViewRequest
111
+ .filters(this.requestFilters.serialize())
112
+ .toApiRequest()
113
+
113
114
  const {models, meta} = await new ListAction()
114
- .setAction(this.action)
115
- .setScopes(this.scopes)
116
- .setFilters(this.requestFilters.serialize())
117
- .setFields(this.fields)
115
+ .setRequest(request)
118
116
  .noEvents(this.noEvents)
119
117
  .load()
120
118
 
@@ -0,0 +1,124 @@
1
+ import { RequestFilters, apiResources } from '@afeefa/api-resources-client'
2
+
3
+ export class ListViewRequest {
4
+ _api = {}
5
+ _resource = {}
6
+ _action = {}
7
+
8
+ _params = {}
9
+ _filters = {}
10
+ _fields = {}
11
+
12
+ _filterSource = null
13
+ _historyKey = null
14
+
15
+ action ({api, resource, action}) {
16
+ this._api = api
17
+ this._resource = resource
18
+ this._action = action
19
+ return this
20
+ }
21
+
22
+ getAction () {
23
+ return apiResources.getAction({
24
+ api: this._api,
25
+ resource: this._resource,
26
+ action: this._action
27
+ })
28
+ }
29
+
30
+ params (params) {
31
+ this._params = params
32
+ return this
33
+ }
34
+
35
+ getParams () {
36
+ return this._params
37
+ }
38
+
39
+ filters (filters) {
40
+ this._filters = filters
41
+ return this
42
+ }
43
+
44
+ getFilters () {
45
+ return this._filters
46
+ }
47
+
48
+ fields (fields) {
49
+ this._fields = fields
50
+ return this
51
+ }
52
+
53
+ getFields () {
54
+ return this._fields
55
+ }
56
+
57
+ initFromFilterSource (filterSource) {
58
+ this._filterSource = filterSource
59
+ return this
60
+ }
61
+
62
+ recreateFromHistory (historyKey) {
63
+ this._historyKey = historyKey
64
+ return this
65
+ }
66
+
67
+ clone () {
68
+ const request = new ListViewRequest()
69
+ request._api = this._api
70
+ request._resource = this._resource
71
+ request._action = this._action
72
+ request._params = this._params
73
+ request._filters = this._filters
74
+ request._fields = this._fields
75
+ return request
76
+ }
77
+
78
+ toApiRequest () {
79
+ const action = this.getAction({
80
+ api: this._api,
81
+ resource: this._resource,
82
+ action: this._action
83
+ })
84
+
85
+ const request = action.createRequest()
86
+ .params(this._params)
87
+ .fields(this._fields)
88
+
89
+ // filters set on the request will be skipped if filterSource or historyKey are given
90
+ // and produce results
91
+ let filtersToUse = this._filters
92
+ let useHistory = true
93
+
94
+ // create and init request filters based on the current filter source state
95
+ if (this._filterSource) {
96
+ const requestFilters = action.createRequestFilters(null, this._filterSource)
97
+ const requestFiltersToUse = requestFilters.serialize()
98
+ // do not use history if any filter given by the given filter source
99
+ if (Object.keys(requestFiltersToUse).length) {
100
+ useHistory = false
101
+ }
102
+
103
+ filtersToUse = {
104
+ ...filtersToUse,
105
+ ...requestFiltersToUse
106
+ }
107
+ }
108
+
109
+ // if any filters is set by current filter source
110
+ // skip history and give that filter precedence
111
+ // otherwise: set stored filters
112
+ if (useHistory && this._historyKey) {
113
+ // check any already stored filters from a previous request
114
+ const storedFilters = RequestFilters.fromHistory(this._historyKey)
115
+ if (storedFilters) {
116
+ filtersToUse = storedFilters.serialize()
117
+ }
118
+ }
119
+
120
+ request.filters(filtersToUse)
121
+
122
+ return request
123
+ }
124
+ }
@@ -1,6 +1,6 @@
1
1
  import { BaseFilterSource } from '@afeefa/api-resources-client'
2
2
 
3
- export class RouteParamsFilterSource extends BaseFilterSource {
3
+ export class RouteQueryFilterSource extends BaseFilterSource {
4
4
  route = null
5
5
 
6
6
  constructor (route) {
@@ -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()
@@ -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
@@ -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>
@@ -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
 
@@ -69,7 +69,11 @@ export default class CreatePage extends Mixins(EditPageMixin) {
69
69
  }
70
70
 
71
71
  get modelUpateAction () {
72
- return this.ModelClass.getAction(this.$routeDefinition, 'create')
72
+ return 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)
@@ -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
  }
@@ -113,7 +113,7 @@ export default class DetailPage extends Vue {
113
113
  if (this.removeAction) {
114
114
  return this.removeAction
115
115
  }
116
- return this.ModelClass.getAction(this.$routeDefinition, 'delete')
116
+ return this.ModelClass.getAction('delete')
117
117
  }
118
118
 
119
119
  async remove () {
@@ -99,14 +99,18 @@ export default class EditPage extends Mixins(EditPageMixin) {
99
99
  }
100
100
 
101
101
  get modelUpateAction () {
102
- return this.ModelClass.getAction(this.$routeDefinition, 'update')
102
+ return 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>
@@ -19,7 +19,7 @@ function load (route) {
19
19
  const routeDefinition = route.meta.routeDefinition
20
20
  const Component = routeDefinition.config.detail
21
21
  const detailConfig = Component.getDetailConfig(route)
22
- const action = detailConfig.action || detailConfig.ModelClass.getAction(routeDefinition, 'get')
22
+ const action = detailConfig.action || detailConfig.ModelClass.getAction('get')
23
23
 
24
24
  return new GetAction()
25
25
  .setAction(action)
@@ -20,7 +20,7 @@ function load (route) {
20
20
  const routeDefinition = route.meta.routeDefinition
21
21
  const Component = routeDefinition.config.edit
22
22
  const editConfig = Component.getEditConfig(route)
23
- const action = editConfig.getAction || editConfig.ModelClass.getAction(routeDefinition, 'get')
23
+ const action = editConfig.getAction || editConfig.ModelClass.getAction('get')
24
24
 
25
25
  return new GetAction()
26
26
  .setAction(action)
@@ -10,6 +10,7 @@
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 { RouteQueryFilterSource } from '@a-vue/components/list/RouteQueryFilterSource'
13
14
 
14
15
  Component.registerHooks([
15
16
  'beforeRouteEnter',
@@ -28,14 +29,18 @@ let lastVm = null
28
29
  function load (route) {
29
30
  const routeDefinition = route.meta.routeDefinition
30
31
  const Component = routeDefinition.config.list
31
- const listConfig = Component.getListConfig(route)
32
- const action = listConfig.action || listConfig.ModelClass.getAction(routeDefinition, 'list')
32
+
33
+ if (!Component.listViewRequest) {
34
+ console.warn('A list route component must implement a static listViewRequest property.')
35
+ }
36
+
37
+ const request = Component.listViewRequest.clone()
38
+ .initFromFilterSource(new RouteQueryFilterSource(route))
39
+ .recreateFromHistory(route.path)
40
+ .toApiRequest()
33
41
 
34
42
  return new ListAction()
35
- .setAction(action)
36
- .setFields(listConfig.fields)
37
- .setScopes(listConfig.scopes)
38
- .setFiltersForRoute(route)
43
+ .setRequest(request)
39
44
  .load()
40
45
  }
41
46
 
@@ -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 () {
@@ -1,4 +0,0 @@
1
- export class QuerySourceType {
2
- static ROUTE = 'route'
3
- static OBJECT = 'object'
4
- }