Skip to content

Commit a102dbe

Browse files
Merge pull request #521 from fratzinger/count
Count - Getter/Action, Model methods & renderless data component v3.11.0
2 parents d71891a + c493f68 commit a102dbe

14 files changed

+563
-168
lines changed

docs/3.0-major-release.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,18 @@ This behavior exactly matches the new `useFind` utility.
8484

8585
## Deprecations
8686

87-
### The `keepCopiesInStore` Option<Badge text="deprecated" type="warning"/>
87+
### The `keepCopiesInStore` Option <Badge text="deprecated" type="warning"/>
8888

8989
The `keepCopiesInStore` option is now deprecated. This was a part of the "clone and commit" API which basically disabled the reason for creating the "clone and commit" API in the first place.
9090

9191
If you're not familiar with the Feathers-Vuex "clone and commit" API, you can learn more about the [built-in data modeling](./model-classes.md) API and the section about [Working with Forms](./feathers-vuex-forms.md#the-clone-and-commit-pattern).
9292

9393
The `keepCopiesInStore` feature is set to be removed in Feathers-Vuex 4.0.
9494

95-
### Auth Plugin State: `user`<Badge text="deprecated" type="warning"/>
95+
### Auth Plugin State: `user` <Badge text="deprecated" type="warning"/>
9696

9797
As described, earlier on this page, since the Auth Plugin's `user` state is no longer reactive and has been replaced by a `user` getter that IS reactive, the `user` state will be removed in the Feathers-Vuex 4.0.
98+
99+
### Renderless Data Components: `query`, `fetchQuery` and `temps` <Badge text="deprecated type="warning">
100+
101+
To keep consistency with mixins and the composition API it's preferred to use `params` and `fetchParams` instead of the old `query` and `fetchQuery` for renderless data components. Also the `:temps="true"` is deprecated in favour of `:params="{ query: {}, temps: true }"`. This way additional params can be passed to the server if you need some more magic like `$populateParams`.

docs/data-components.md

Lines changed: 177 additions & 112 deletions
Large diffs are not rendered by default.

docs/model-classes.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,34 @@ created () {
156156
}
157157
```
158158

159+
### count(params) <Badge text="3.11.0+" />
160+
161+
Model classes have a `count` method, which is a proxy to the `count` action. On the Feathers server, `$limit: 0` results in a fast count query. (./service-plugin.html#find-params).
162+
163+
> **Note:** it only works for services with enabled pagination!
164+
165+
```js
166+
// In your Vue component
167+
async created () {
168+
const { Todo } = this.$FeathersVuex.api
169+
const todosCount = await Todo.count({ query: { priority: 'critical' }})
170+
// or
171+
Todo.count().then((total) => { this.todoCount = total })
172+
}
173+
```
174+
175+
### countInStore(params) <Badge text="3.11.0+" />
176+
177+
Model classes have a `countInStore` method, which is a proxy to the [`count` getter](./service-plugin.html#Service-Getters).
178+
179+
```js
180+
// In your Vue component
181+
created () {
182+
const { Todo } = this.$FeathersVuex.api
183+
const todosCount = Todo.countInStore({ query: { priority: 'critical' }})
184+
}
185+
```
186+
159187
### get(id, params)
160188

161189
Model classes have a `get` method, which is a proxy to the [`get` action](./service-plugin.html#get-id-or-get-id-params). <Badge text="1.7.0+" /> Notice that the signature is more Feathers-like, and doesn't require using an array to passing both id and params.

docs/service-plugin.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,9 @@ Service modules include the following getters:
199199

200200
- `list {Array}` - an array of items. The array form of `keyedById` Read only.
201201
- `find(params) {Function}` - a helper function that allows you to use the [Feathers Adapter Common API](https://docs.feathersjs.com/api/databases/common) and [Query API](https://docs.feathersjs.com/api/databases/querying) to pull data from the store. This allows you to treat the store just like a local Feathers database adapter (but without hooks).
202-
- `params {Object}` - an object with a `query` object and an optional `paginate` boolean property. The `query` is in the FeathersJS query format. You can set `params.paginate` to `false` to disable pagination for a single request.
202+
- `params {Object}` - an object with a `query` object and optional `paginate` and `temps` boolean properties. The `query` is in the FeathersJS query format. You can set `params.paginate` to `false` to disable pagination for a single request.
203+
- `count(params) {Function}` - a helper function that counts items in the store matching the provided query in the params and returns this number <Badge text="3.11.0+" />
204+
- `params {Object}` - an object with a `query` object and an optional `temps` boolean property.
203205
- `get(id[, params]) {Function}` - a function that allows you to query the store for a single item, by id. It works the same way as `get` requests in Feathers database adapters.
204206
- `id {Number|String}` - the id of the data to be retrieved by id from the store.
205207
- `params {Object}` - an object containing a Feathers `query` object.
@@ -294,6 +296,21 @@ See the section about pagination, below, for more information that is applicable
294296

295297
The `afterFind` action is called by the `find` action after a successful response is added to the store. It is called with the current response. By default, it is a no-op (it literally does nothing), and is just a placeholder for you to use when necessary. See the sections on [customizing the default store](#Customizing-a-Service’s-Default-Store) and [Handling custom server responses](./common-patterns.html#Handling-custom-server-responses) for example usage.
296298

299+
### `count(params)` <Badge text="3.11.0+" />
300+
301+
Count items on the server matching the provided query.
302+
303+
- `params {Object}` - An object containing a `query` object. In the background `$limit: 0` will be added to the `query` to perform a (fast) counting query against the database.
304+
305+
> **Note:** it only works for services with enabled pagination!
306+
307+
```js
308+
let params = {query: {completed: false}}
309+
store.dispatch('todos/count', params)
310+
```
311+
312+
This will run a (fast) counting query against the database and return a page object with the total and an empty data array.
313+
297314
### `get(id)` or `get([id, params])`
298315

299316
Query a single record from the server & add to Vuex store

src/FeathersVuexCount.ts

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { randomString } from './utils'
2+
3+
export default {
4+
props: {
5+
service: {
6+
type: String,
7+
required: true
8+
},
9+
params: {
10+
type: Object,
11+
default: () => {
12+
return {
13+
query: {}
14+
}
15+
}
16+
},
17+
queryWhen: {
18+
type: [Boolean, Function],
19+
default: true
20+
},
21+
// If separate params are desired to fetch data, use fetchParams
22+
// The watchers will automatically be updated, so you don't have to write 'fetchParams.query.propName'
23+
fetchParams: {
24+
type: Object
25+
},
26+
watch: {
27+
type: [String, Array],
28+
default: () => []
29+
},
30+
local: {
31+
type: Boolean,
32+
default: false
33+
}
34+
},
35+
data: () => ({
36+
isCountPending: false,
37+
serverTotal: null
38+
}),
39+
computed: {
40+
total() {
41+
if (!this.local) {
42+
return this.serverTotal
43+
} else {
44+
const { params, service, $store, temps } = this
45+
return params ? $store.getters[`${service}/count`](params) : 0
46+
}
47+
},
48+
scope() {
49+
const { total, isCountPending } = this
50+
51+
return { total, isCountPending }
52+
}
53+
},
54+
methods: {
55+
findData() {
56+
const params = this.fetchParams || this.params
57+
58+
if (
59+
typeof this.queryWhen === 'function'
60+
? this.queryWhen(this.params)
61+
: this.queryWhen
62+
) {
63+
this.isCountPending = true
64+
65+
if (params) {
66+
return this.$store
67+
.dispatch(`${this.service}/count`, params)
68+
.then(response => {
69+
this.isCountPending = false
70+
this.serverTotal = response
71+
})
72+
}
73+
}
74+
},
75+
fetchData() {
76+
if (!this.local) {
77+
if (this.params) {
78+
return this.findData()
79+
} else {
80+
// TODO: access debug boolean from the store config, somehow.
81+
// eslint-disable-next-line no-console
82+
console.log(
83+
`No query and no id provided, so no data will be fetched.`
84+
)
85+
}
86+
}
87+
}
88+
},
89+
created() {
90+
if (!this.$FeathersVuex) {
91+
throw new Error(
92+
`You must first Vue.use the FeathersVuex plugin before using the 'FeathersVuexFind' component.`
93+
)
94+
}
95+
if (!this.$store.state[this.service]) {
96+
throw new Error(
97+
`The '${this.service}' plugin not registered with feathers-vuex`
98+
)
99+
}
100+
101+
const watch = Array.isArray(this.watch) ? this.watch : [this.watch]
102+
103+
if (this.fetchParams || this.params) {
104+
watch.forEach(prop => {
105+
if (typeof prop !== 'string') {
106+
throw new Error(`Values in the 'watch' array must be strings.`)
107+
}
108+
if (this.fetchParams) {
109+
if (prop.startsWith('params')) {
110+
prop = prop.replace('params', 'fetchParams')
111+
}
112+
}
113+
this.$watch(prop, this.fetchData)
114+
})
115+
116+
this.fetchData()
117+
}
118+
},
119+
render() {
120+
return this.$scopedSlots.default(this.scope)
121+
}
122+
}

src/FeathersVuexFind.ts

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,22 @@ export default {
2020
fetchQuery: {
2121
type: Object
2222
},
23+
/**
24+
* Can be used in place of the `query` prop to provide more params. Only params.query is
25+
* passed to the getter.
26+
*/
27+
params: {
28+
type: Object,
29+
default: null
30+
},
31+
/**
32+
* Can be used in place of the `fetchQuery` prop to provide more params. Only params.query is
33+
* passed to the getter.
34+
*/
35+
fetchParams: {
36+
type: Object,
37+
default: null
38+
},
2339
watch: {
2440
type: [String, Array],
2541
default() {
@@ -58,7 +74,9 @@ export default {
5874
computed: {
5975
items() {
6076
const { query, service, $store, temps } = this
61-
const params = { query, temps }
77+
let { params } = this
78+
79+
params = params || { query, temps }
6280

6381
return query ? $store.getters[`${service}/find`](params).data : []
6482
},
@@ -94,16 +112,21 @@ export default {
94112
methods: {
95113
findData() {
96114
const query = this.fetchQuery || this.query
115+
let params = this.fetchParams || this.params
97116

98117
if (
99118
typeof this.queryWhen === 'function'
100-
? this.queryWhen(this.query)
119+
? this.queryWhen(this.params || this.query)
101120
: this.queryWhen
102121
) {
103122
this.isFindPending = true
104123

105-
if (query) {
106-
const params = { query, qid: this.qid || 'default' }
124+
if (params || query) {
125+
if (params) {
126+
params = Object.assign({}, params, { qid: this.qid || 'default' })
127+
} else {
128+
params = { query, qid: this.qid || 'default' }
129+
}
107130

108131
return this.$store
109132
.dispatch(`${this.service}/find`, params)
@@ -118,7 +141,7 @@ export default {
118141
},
119142
fetchData() {
120143
if (!this.local) {
121-
if (this.query) {
144+
if (this.params || this.query) {
122145
return this.findData()
123146
} else {
124147
// TODO: access debug boolean from the store config, somehow.
@@ -144,7 +167,7 @@ export default {
144167

145168
const watch = Array.isArray(this.watch) ? this.watch : [this.watch]
146169

147-
if (this.fetchQuery || this.query) {
170+
if (this.fetchQuery || this.query || this.params) {
148171
watch.forEach(prop => {
149172
if (typeof prop !== 'string') {
150173
throw new Error(`Values in the 'watch' array must be strings.`)
@@ -154,6 +177,11 @@ export default {
154177
prop = prop.replace('query', 'fetchQuery')
155178
}
156179
}
180+
if (this.fetchParams) {
181+
if (prop.startsWith('params')) {
182+
prop = prop.replace('params', 'fetchParams')
183+
}
184+
}
157185
this.$watch(prop, this.fetchData)
158186
})
159187

src/service-module/make-base-model.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,14 @@ export default function makeBaseModel(options: FeathersVuexOptions) {
188188
return this._getters('find', params)
189189
}
190190

191+
public static count(params?: Params) {
192+
return this._dispatch('count', params)
193+
}
194+
195+
public static countInStore(params?: Params) {
196+
return this._getters('count', params)
197+
}
198+
191199
public static get(id: Id, params?: Params) {
192200
if (params) {
193201
return this._dispatch('get', [id, params])

src/service-module/service-module.actions.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export default function makeServiceActions(service: Service<any>) {
5555
commit('setPending', 'get')
5656
return service
5757
.get(id, params)
58-
.then(async function(item) {
58+
.then(async function (item) {
5959
dispatch('addOrUpdate', item)
6060
commit('unsetPending', 'get')
6161
return state.keyedById[id]
@@ -139,7 +139,7 @@ export default function makeServiceActions(service: Service<any>) {
139139

140140
return service
141141
.update(id, data, params)
142-
.then(async function(item) {
142+
.then(async function (item) {
143143
dispatch('addOrUpdate', item)
144144
commit('unsetPending', 'update')
145145
return state.keyedById[id]
@@ -169,7 +169,7 @@ export default function makeServiceActions(service: Service<any>) {
169169

170170
return service
171171
.patch(id, data, params)
172-
.then(async function(item) {
172+
.then(async function (item) {
173173
dispatch('addOrUpdate', item)
174174
commit('unsetPending', 'patch')
175175
return state.keyedById[id]
@@ -213,6 +213,22 @@ export default function makeServiceActions(service: Service<any>) {
213213
}
214214

215215
const actions = {
216+
count({ dispatch }, params) {
217+
params = params || {}
218+
params = fastCopy(params)
219+
220+
if (!params.query) {
221+
throw 'params must contain a query-object'
222+
}
223+
224+
params.query.$limit = 0 // <- limit 0 in feathers is a fast count query
225+
226+
return dispatch('find', params)
227+
.then(response => {
228+
return response.total || response.length
229+
})
230+
.catch(error => dispatch('handleFindError', { params, error }))
231+
},
216232
/**
217233
* Handle the response from the find action.
218234
*

src/service-module/service-module.getters.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,19 @@ export default function makeServiceGetters() {
7373
data: values
7474
}
7575
},
76+
count: (state, getters) => params => {
77+
if (isRef(params)) {
78+
params = params.value
79+
}
80+
if (!params.query) {
81+
throw 'params must contain a query-object'
82+
}
83+
84+
const cleanQuery = _omit(params.query, FILTERS)
85+
params.query = cleanQuery
86+
87+
return getters.find(state)(params).total
88+
},
7689
get: ({ keyedById, tempsById, idField, tempIdField }) => (
7790
id,
7891
params = {}

0 commit comments

Comments
 (0)