@bildvitta/quasar-ui-asteroid 2.23.0-beta.5 → 2.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/form-view/QasFormView.stories.js +8 -0
- package/src/components/form-view/QasFormView.vue +37 -17
- package/src/components/list-view/QasListView.stories.js +8 -0
- package/src/components/list-view/QasListView.vue +16 -9
- package/src/components/search-box/QasSearchBox.vue +1 -1
- package/src/components/select/QasSelect.vue +20 -19
- package/src/components/single-view/QasSingleView.stories.js +4 -0
- package/src/components/single-view/QasSingleView.vue +6 -2
- package/src/mixins/lazy-loading-filter.js +20 -31
- package/src/mixins/view.js +22 -1
package/package.json
CHANGED
|
@@ -33,6 +33,10 @@ export default {
|
|
|
33
33
|
|
|
34
34
|
argTypes: {
|
|
35
35
|
// Props
|
|
36
|
+
beforeFetch: {
|
|
37
|
+
description: 'Function to be called before fetching data.'
|
|
38
|
+
},
|
|
39
|
+
|
|
36
40
|
cancelButton: {
|
|
37
41
|
description: 'Cancel button label.'
|
|
38
42
|
},
|
|
@@ -80,6 +84,10 @@ export default {
|
|
|
80
84
|
description: 'Submit button label.'
|
|
81
85
|
},
|
|
82
86
|
|
|
87
|
+
beforeSubmit: {
|
|
88
|
+
description: 'Function to be called before submit.'
|
|
89
|
+
},
|
|
90
|
+
|
|
83
91
|
url: {
|
|
84
92
|
control: null,
|
|
85
93
|
description: 'Ignore entity and specify another endpoint.'
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<slot :errors="errors" :fields="fields" :metadata="metadata" name="header" />
|
|
5
5
|
</header>
|
|
6
6
|
|
|
7
|
-
<q-form ref="form" @submit="
|
|
7
|
+
<q-form ref="form" @submit="submitHandler">
|
|
8
8
|
<slot :errors="errors" :fields="fields" :metadata="metadata" />
|
|
9
9
|
|
|
10
10
|
<slot v-if="!readOnly" :errors="errors" :fields="fields" :metadata="metadata" name="actions">
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<qas-btn v-close-popup="dialog" class="full-width" :data-cy="`btnCancel-${entity}`" :disable="isCancelButtonDisabled" :label="cancelButton" outline type="button" @click="cancel" />
|
|
14
14
|
</div>
|
|
15
15
|
<div class="col-12 col-sm-2" :class="saveButtonClass">
|
|
16
|
-
<qas-btn class="full-width" :data-cy="`btnSave-${entity}`" :disable="disable" :label="submitButton" :loading="
|
|
16
|
+
<qas-btn class="full-width" :data-cy="`btnSave-${entity}`" :disable="disable" :label="submitButton" :loading="isSubmitting" type="submit" />
|
|
17
17
|
</div>
|
|
18
18
|
</div>
|
|
19
19
|
</slot>
|
|
@@ -95,6 +95,11 @@ export default {
|
|
|
95
95
|
type: String
|
|
96
96
|
},
|
|
97
97
|
|
|
98
|
+
beforeSubmit: {
|
|
99
|
+
default: null,
|
|
100
|
+
type: Function
|
|
101
|
+
},
|
|
102
|
+
|
|
98
103
|
value: {
|
|
99
104
|
default: () => ({}),
|
|
100
105
|
type: Object
|
|
@@ -105,7 +110,7 @@ export default {
|
|
|
105
110
|
return {
|
|
106
111
|
cachedResult: {},
|
|
107
112
|
hasResult: false,
|
|
108
|
-
|
|
113
|
+
isSubmitting: false,
|
|
109
114
|
showDialog: false,
|
|
110
115
|
|
|
111
116
|
dialogConfig: {
|
|
@@ -181,7 +186,7 @@ export default {
|
|
|
181
186
|
},
|
|
182
187
|
|
|
183
188
|
created () {
|
|
184
|
-
this.
|
|
189
|
+
this.fetchHandler({ form: true, id: this.id, url: this.fetchURL }, this.fetchSingle)
|
|
185
190
|
},
|
|
186
191
|
|
|
187
192
|
methods: {
|
|
@@ -203,14 +208,18 @@ export default {
|
|
|
203
208
|
}
|
|
204
209
|
},
|
|
205
210
|
|
|
206
|
-
async
|
|
211
|
+
async fetchSingle (externalPayload = {}) {
|
|
207
212
|
this.isFetching = true
|
|
208
213
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
214
|
+
const payload = {
|
|
215
|
+
form: true,
|
|
216
|
+
id: this.id,
|
|
217
|
+
url: this.fetchURL,
|
|
218
|
+
...externalPayload
|
|
219
|
+
}
|
|
213
220
|
|
|
221
|
+
try {
|
|
222
|
+
const response = await this.$store.dispatch(`${this.entity}/fetchSingle`, payload)
|
|
214
223
|
const { errors, fields, metadata, result } = response.data
|
|
215
224
|
|
|
216
225
|
this.setErrors(errors)
|
|
@@ -264,22 +273,33 @@ export default {
|
|
|
264
273
|
this.showDialog = true
|
|
265
274
|
},
|
|
266
275
|
|
|
267
|
-
|
|
276
|
+
submitHandler (event) {
|
|
268
277
|
if (event) {
|
|
269
278
|
event.preventDefault()
|
|
270
279
|
}
|
|
271
280
|
|
|
272
|
-
|
|
281
|
+
const hasBeforeSubmit = typeof this.beforeSubmit === 'function'
|
|
282
|
+
|
|
283
|
+
if (hasBeforeSubmit) {
|
|
284
|
+
return this.beforeSubmit({
|
|
285
|
+
params: { id: this.id, payload: this.value, url: this.url },
|
|
286
|
+
resolve: payload => this.submit(payload)
|
|
287
|
+
})
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
this.submit()
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
async submit (externalPayload = {}) {
|
|
294
|
+
if (this.disable) {
|
|
273
295
|
return null
|
|
274
296
|
}
|
|
275
297
|
|
|
276
|
-
this.
|
|
298
|
+
this.isSubmitting = true
|
|
277
299
|
|
|
278
300
|
try {
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
{ id: this.id, payload: this.value, url: this.url }
|
|
282
|
-
)
|
|
301
|
+
const payload = { id: this.id, payload: this.value, url: this.url, ...externalPayload }
|
|
302
|
+
const response = await this.$store.dispatch(`${this.entity}/${this.mode}`, payload)
|
|
283
303
|
|
|
284
304
|
if (this.showDialogOnUnsavedChanges) {
|
|
285
305
|
this.cachedResult = cloneDeep(this.value)
|
|
@@ -301,7 +321,7 @@ export default {
|
|
|
301
321
|
|
|
302
322
|
this.$emit('submit-error', error)
|
|
303
323
|
} finally {
|
|
304
|
-
this.
|
|
324
|
+
this.isSubmitting = false
|
|
305
325
|
}
|
|
306
326
|
}
|
|
307
327
|
}
|
|
@@ -41,6 +41,10 @@ export default {
|
|
|
41
41
|
|
|
42
42
|
argTypes: {
|
|
43
43
|
// Props
|
|
44
|
+
beforeFetch: {
|
|
45
|
+
description: 'Function to be called before fetching data.'
|
|
46
|
+
},
|
|
47
|
+
|
|
44
48
|
dialog: {
|
|
45
49
|
description: 'Use when the component is inside a dialog.'
|
|
46
50
|
},
|
|
@@ -67,6 +71,10 @@ export default {
|
|
|
67
71
|
description: 'Ignore entity and specify another endpoint.'
|
|
68
72
|
},
|
|
69
73
|
|
|
74
|
+
useResultsAreaOnly: {
|
|
75
|
+
description: 'Controls whether results will always be displayed regardless of there are no results to be displayed.'
|
|
76
|
+
},
|
|
77
|
+
|
|
70
78
|
// Events
|
|
71
79
|
'fetch-error': {
|
|
72
80
|
description: 'Fires when occur an error fetching value.',
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
</slot>
|
|
11
11
|
|
|
12
12
|
<main class="relative-position">
|
|
13
|
-
<div v-if="
|
|
13
|
+
<div v-if="showResults">
|
|
14
14
|
<slot :fields="fields" :metadata="metadata" :results="results" />
|
|
15
15
|
</div>
|
|
16
16
|
|
|
@@ -67,6 +67,10 @@ export default {
|
|
|
67
67
|
filtersProps: {
|
|
68
68
|
default: () => ({}),
|
|
69
69
|
type: Object
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
useResultsAreaOnly: {
|
|
73
|
+
type: Boolean
|
|
70
74
|
}
|
|
71
75
|
},
|
|
72
76
|
|
|
@@ -77,11 +81,6 @@ export default {
|
|
|
77
81
|
},
|
|
78
82
|
|
|
79
83
|
computed: {
|
|
80
|
-
context () {
|
|
81
|
-
const { limit, ordering, page, search, ...filters } = this.$route.query
|
|
82
|
-
return { filters, limit, ordering, page: page ? parseInt(page) : 1, search }
|
|
83
|
-
},
|
|
84
|
-
|
|
85
84
|
hasHeaderSlot () {
|
|
86
85
|
return !!(this.$slots.header || this.$scopedSlots.header)
|
|
87
86
|
},
|
|
@@ -98,6 +97,10 @@ export default {
|
|
|
98
97
|
return this.$store.getters[`${this.entity}/list`]
|
|
99
98
|
},
|
|
100
99
|
|
|
100
|
+
showResults () {
|
|
101
|
+
return this.hasResults || (this.useResultsAreaOnly && !this.isFetching)
|
|
102
|
+
},
|
|
103
|
+
|
|
101
104
|
totalPages () {
|
|
102
105
|
return this.$store.getters[`${this.entity}/totalPages`]
|
|
103
106
|
}
|
|
@@ -105,13 +108,13 @@ export default {
|
|
|
105
108
|
|
|
106
109
|
watch: {
|
|
107
110
|
$route () {
|
|
108
|
-
this.
|
|
111
|
+
this.onFetchHandler()
|
|
109
112
|
this.setCurrentPage()
|
|
110
113
|
}
|
|
111
114
|
},
|
|
112
115
|
|
|
113
116
|
created () {
|
|
114
|
-
this.
|
|
117
|
+
this.onFetchHandler()
|
|
115
118
|
this.setCurrentPage()
|
|
116
119
|
},
|
|
117
120
|
|
|
@@ -148,13 +151,17 @@ export default {
|
|
|
148
151
|
},
|
|
149
152
|
|
|
150
153
|
async refresh (done) {
|
|
151
|
-
await this.
|
|
154
|
+
await this.onFetchHandler()
|
|
152
155
|
|
|
153
156
|
if (typeof done === 'function') {
|
|
154
157
|
done()
|
|
155
158
|
}
|
|
156
159
|
},
|
|
157
160
|
|
|
161
|
+
async onFetchHandler () {
|
|
162
|
+
await this.fetchHandler({ ...this.context, url: this.url }, this.fetchList)
|
|
163
|
+
},
|
|
164
|
+
|
|
158
165
|
setCurrentPage () {
|
|
159
166
|
this.page = parseInt(this.$route.query.page || 1)
|
|
160
167
|
}
|
|
@@ -13,15 +13,10 @@
|
|
|
13
13
|
</template>
|
|
14
14
|
|
|
15
15
|
<template #no-option>
|
|
16
|
-
<slot name="no-option">
|
|
16
|
+
<slot v-if="!isLoading" name="no-option">
|
|
17
17
|
<q-item>
|
|
18
18
|
<q-item-section class="text-grey">
|
|
19
|
-
|
|
20
|
-
Buscando opções de {{ label }}...
|
|
21
|
-
</template>
|
|
22
|
-
<template v-else>
|
|
23
|
-
{{ noOptionLabel }}
|
|
24
|
-
</template>
|
|
19
|
+
{{ noOptionLabel }}
|
|
25
20
|
</q-item-section>
|
|
26
21
|
</q-item>
|
|
27
22
|
</slot>
|
|
@@ -98,10 +93,6 @@ export default {
|
|
|
98
93
|
}
|
|
99
94
|
},
|
|
100
95
|
|
|
101
|
-
label () {
|
|
102
|
-
return this.$attrs.label || ''
|
|
103
|
-
},
|
|
104
|
-
|
|
105
96
|
listeners () {
|
|
106
97
|
const { input, ...events } = this.$listeners
|
|
107
98
|
|
|
@@ -148,19 +139,23 @@ export default {
|
|
|
148
139
|
return this.hasFetchError || this.$attrs.error
|
|
149
140
|
},
|
|
150
141
|
|
|
142
|
+
hasLoading () {
|
|
143
|
+
return this.isLoading || this.$attrs.loading
|
|
144
|
+
},
|
|
145
|
+
|
|
151
146
|
attributes () {
|
|
152
147
|
return {
|
|
148
|
+
bottomSlots: true,
|
|
153
149
|
emitValue: true,
|
|
154
150
|
mapOptions: true,
|
|
155
151
|
outlined: true,
|
|
156
152
|
clearable: this.isSearchable,
|
|
157
|
-
loading: this.isLoading,
|
|
158
153
|
inputDebounce: this.useLazyLoading ? 500 : 0,
|
|
159
|
-
popupContentClass: this.$_virtualScrollClassName,
|
|
160
154
|
...this.$attrs,
|
|
161
155
|
options: this.filteredOptions,
|
|
162
156
|
useInput: this.isSearchable,
|
|
163
|
-
error: this.hasError
|
|
157
|
+
error: this.hasError,
|
|
158
|
+
loading: this.hasLoading
|
|
164
159
|
}
|
|
165
160
|
}
|
|
166
161
|
},
|
|
@@ -189,15 +184,21 @@ export default {
|
|
|
189
184
|
const Fuse = (await import('fuse.js')).default
|
|
190
185
|
this.fuse = new Fuse(this.defaultOptions, this.defaultFuseOptions)
|
|
191
186
|
}
|
|
187
|
+
|
|
188
|
+
this.useLazyLoading && this.$_setFetchOptions('')
|
|
192
189
|
},
|
|
193
190
|
|
|
194
191
|
methods: {
|
|
195
|
-
onFilter (value, update) {
|
|
196
|
-
|
|
197
|
-
|
|
192
|
+
async onFilter (value, update) {
|
|
193
|
+
if (this.useLazyLoading && value !== this.search) {
|
|
194
|
+
await this.$_filterOptionsByStore(value)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (!this.useLazyLoading && this.searchable) {
|
|
198
|
+
this.filterOptionsByFuse(value)
|
|
199
|
+
}
|
|
198
200
|
|
|
199
|
-
|
|
200
|
-
})
|
|
201
|
+
update()
|
|
201
202
|
},
|
|
202
203
|
|
|
203
204
|
filterOptionsByFuse (value) {
|
|
@@ -33,6 +33,10 @@ export default {
|
|
|
33
33
|
|
|
34
34
|
argTypes: {
|
|
35
35
|
// Props
|
|
36
|
+
beforeFetch: {
|
|
37
|
+
description: 'Function to be called before fetching data.'
|
|
38
|
+
},
|
|
39
|
+
|
|
36
40
|
customId: {
|
|
37
41
|
control: null,
|
|
38
42
|
description: 'Sets a custom id to `entity`. When not set, will use the `:id` route param.'
|
|
@@ -57,7 +57,7 @@ export default {
|
|
|
57
57
|
|
|
58
58
|
watch: {
|
|
59
59
|
$route () {
|
|
60
|
-
this.
|
|
60
|
+
this.onFetchHandler()
|
|
61
61
|
},
|
|
62
62
|
|
|
63
63
|
result (value) {
|
|
@@ -66,7 +66,7 @@ export default {
|
|
|
66
66
|
},
|
|
67
67
|
|
|
68
68
|
created () {
|
|
69
|
-
this.
|
|
69
|
+
this.onFetchHandler()
|
|
70
70
|
},
|
|
71
71
|
|
|
72
72
|
methods: {
|
|
@@ -94,6 +94,10 @@ export default {
|
|
|
94
94
|
} finally {
|
|
95
95
|
this.isFetching = false
|
|
96
96
|
}
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
async onFetchHandler () {
|
|
100
|
+
await this.fetchHandler({ id: this.id, url: this.url }, this.fetchSingle)
|
|
97
101
|
}
|
|
98
102
|
}
|
|
99
103
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { decamelize } from 'humps'
|
|
2
2
|
import { isEqual } from 'lodash'
|
|
3
|
-
import { uid } from 'quasar'
|
|
4
3
|
|
|
5
4
|
export default {
|
|
6
5
|
props: {
|
|
@@ -32,9 +31,11 @@ export default {
|
|
|
32
31
|
isScrolling: false,
|
|
33
32
|
pagination: {
|
|
34
33
|
page: 1,
|
|
35
|
-
lastPage: null
|
|
34
|
+
lastPage: null,
|
|
35
|
+
hasCount: true,
|
|
36
|
+
hasNextPage: false
|
|
36
37
|
},
|
|
37
|
-
search:
|
|
38
|
+
search: ''
|
|
38
39
|
}
|
|
39
40
|
},
|
|
40
41
|
|
|
@@ -62,11 +63,6 @@ export default {
|
|
|
62
63
|
|
|
63
64
|
$_hasFilteredOptions () {
|
|
64
65
|
return !!this.filteredOptions.length
|
|
65
|
-
},
|
|
66
|
-
|
|
67
|
-
$_virtualScrollClassName () {
|
|
68
|
-
const id = uid()
|
|
69
|
-
return `virtual-scroll-${id}`
|
|
70
66
|
}
|
|
71
67
|
},
|
|
72
68
|
|
|
@@ -75,7 +71,7 @@ export default {
|
|
|
75
71
|
handler (value, oldValue) {
|
|
76
72
|
if (isEqual(value, oldValue)) return
|
|
77
73
|
|
|
78
|
-
this.$
|
|
74
|
+
this.$_filterOptionsByStore('')
|
|
79
75
|
this.$emit('input', '')
|
|
80
76
|
}
|
|
81
77
|
}
|
|
@@ -92,21 +88,22 @@ export default {
|
|
|
92
88
|
this.search = search
|
|
93
89
|
this.pagination = {
|
|
94
90
|
page: 1,
|
|
95
|
-
lastPage: null
|
|
91
|
+
lastPage: null,
|
|
92
|
+
hasCount: true,
|
|
93
|
+
hasNextPage: false
|
|
96
94
|
}
|
|
97
95
|
},
|
|
98
96
|
|
|
99
|
-
async $_onVirtualScroll ({ index }) {
|
|
97
|
+
async $_onVirtualScroll ({ index, ref }) {
|
|
100
98
|
const lastIndex = this.filteredOptions.length - 1
|
|
101
99
|
|
|
102
100
|
if (index === lastIndex && this.$_canFetchOptions()) {
|
|
103
|
-
const { scrollContainer, top } = this.$_getScrollContainerTop()
|
|
104
|
-
|
|
105
101
|
await this.$_loadMoreOptions()
|
|
106
102
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
103
|
+
this.$nextTick(() => {
|
|
104
|
+
ref.reset()
|
|
105
|
+
ref.refresh(lastIndex)
|
|
106
|
+
})
|
|
110
107
|
}
|
|
111
108
|
},
|
|
112
109
|
|
|
@@ -144,11 +141,14 @@ export default {
|
|
|
144
141
|
}
|
|
145
142
|
})
|
|
146
143
|
|
|
147
|
-
const { results, count } = data
|
|
144
|
+
const { results, count, hasNextPage } = data
|
|
145
|
+
const hasCount = count !== undefined
|
|
148
146
|
|
|
149
147
|
this.pagination = {
|
|
150
148
|
page: this.pagination.page + 1,
|
|
151
|
-
lastPage: Math.ceil(count / params.limit)
|
|
149
|
+
lastPage: hasCount ? Math.ceil(count / params.limit) : null,
|
|
150
|
+
hasCount,
|
|
151
|
+
hasNextPage
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
this.$emit('fetch-options-success', data)
|
|
@@ -173,23 +173,12 @@ export default {
|
|
|
173
173
|
},
|
|
174
174
|
|
|
175
175
|
$_canFetchOptions () {
|
|
176
|
-
const { lastPage, page } = this.pagination
|
|
177
|
-
const hasMorePages = lastPage && page <= lastPage
|
|
176
|
+
const { lastPage, page, hasCount, hasNextPage } = this.pagination
|
|
177
|
+
const hasMorePages = hasCount ? lastPage && page <= lastPage : hasNextPage
|
|
178
178
|
|
|
179
179
|
return hasMorePages && !this.isLoading && !this.isScrolling && this.useLazyLoading
|
|
180
180
|
},
|
|
181
181
|
|
|
182
|
-
$_getScrollContainerTop () {
|
|
183
|
-
const scrollContainer = document.querySelector(`.${this.$_virtualScrollClassName}`)
|
|
184
|
-
const scrollContainerHeight = scrollContainer.offsetHeight
|
|
185
|
-
const scrollContainerTop = scrollContainer.scrollTop
|
|
186
|
-
|
|
187
|
-
return {
|
|
188
|
-
scrollContainer,
|
|
189
|
-
top: scrollContainerTop + (scrollContainerHeight / 2)
|
|
190
|
-
}
|
|
191
|
-
},
|
|
192
|
-
|
|
193
182
|
$_handleOptions (options) {
|
|
194
183
|
if (this.labelKey && this.valueKey && this.renameKey) {
|
|
195
184
|
return options.map(item => this.renameKey(item))
|
package/src/mixins/view.js
CHANGED
|
@@ -5,6 +5,11 @@ import { NotifyError } from '../plugins'
|
|
|
5
5
|
|
|
6
6
|
export default {
|
|
7
7
|
props: {
|
|
8
|
+
beforeFetch: {
|
|
9
|
+
default: null,
|
|
10
|
+
type: Function
|
|
11
|
+
},
|
|
12
|
+
|
|
8
13
|
dialog: {
|
|
9
14
|
type: Boolean
|
|
10
15
|
},
|
|
@@ -22,10 +27,10 @@ export default {
|
|
|
22
27
|
|
|
23
28
|
data () {
|
|
24
29
|
return {
|
|
30
|
+
cancelBeforeFetch: false,
|
|
25
31
|
errors: {},
|
|
26
32
|
fields: {},
|
|
27
33
|
metadata: {},
|
|
28
|
-
|
|
29
34
|
isFetching: false
|
|
30
35
|
}
|
|
31
36
|
},
|
|
@@ -59,6 +64,22 @@ export default {
|
|
|
59
64
|
}
|
|
60
65
|
},
|
|
61
66
|
|
|
67
|
+
fetchHandler (payload, resolve) {
|
|
68
|
+
const hasBeforeFetch = typeof this.beforeFetch === 'function'
|
|
69
|
+
|
|
70
|
+
if (hasBeforeFetch && !this.cancelBeforeFetch) {
|
|
71
|
+
return this.beforeFetch({
|
|
72
|
+
payload,
|
|
73
|
+
resolve: payload => resolve(payload),
|
|
74
|
+
done: () => {
|
|
75
|
+
this.cancelBeforeFetch = true
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
resolve()
|
|
81
|
+
},
|
|
82
|
+
|
|
62
83
|
setErrors (errors = {}) {
|
|
63
84
|
this.errors = errors
|
|
64
85
|
},
|