@@ -5,60 +5,24 @@ slug: /guides/lists
55
66## API
77
8- ### Use case and result iterator
8+ In the API, a list means retrieving data from the database according to:
99
10- It starts by creating a use case. For instance, * src/api/src/UseCase/User/GetUsers.php* .
10+ * Random filters (e.g., free search).
11+ * Predictable filters (e.g., dropdown values).
12+ * A sort direction on a field.
13+ * A limit and an offset.
1114
12- Your use case have to return a ` ResultIterator ` to paginate efficiently your results.
13- Let's take a look at the GraphQL query for the ` GetUsers ` use case:
14-
15- ``` graphql title="GraphQL query"
16- query users ($search : String , $role : Role , $sortBy : UsersSortBy , $sortOrder : SortOrder , $limit : Int ! , $offset : Int ! ) {
17- users (search : $search , role : $role , sortBy : $sortBy , sortOrder : $sortOrder ) {
18- items (limit : $limit , offset : $offset ) {
19- id
20- firstName
21- lastName
22- email
23- locale
24- role
25- }
26- count
27- }
28- }
29- ```
30-
31- The ` limit ` and ` offset ` parameters from the ` items ` part do not exist in the use case parameters;
32- it is the ` ResultIterator ` that takes these parameters.
33-
34- Also, note the ` count ` value; that's the total of items.
35-
36- We will see later how to use these values to create a pagination on the web application.
15+ There are two main PHP classes for that goal:
3716
38- ### Enumerators
39-
40- For "sort by" and "sort order" values, we use enumerators (see * src/api/src/Domain/Enum/Filter* folder).
17+ 1 . The use case (and GraphQL entrypoint).
18+ 2 . The DAO.
4119
42- GraphQLite recognizes an enumerator as a valid value for your GraphQL request.
43-
44- Each enumerator's key (i.e., ` FIRST_NAME ` ) is a GraphQL value, while each enumerator's value (i.e., ` first_name ` )
45- is a valid SQL expression.
20+ Both [ TDBM] ( https://github.com/thecodingmachine/tdbm ) and [ GraphQLite] ( https://graphqlite.thecodingmachine.io/ )
21+ will also help us a lot.
4622
47- For instance:
23+ ### Use case
4824
49- ``` graphql title="GraphQL query"
50- users (search : "foo" , role : ADMINISTATOR , sortBy : LAST_NAME , sortOrder : DESC ) {
51- items (limit : 10 , offset : 0 ) {
52- id
53- firstName
54- lastName
55- email
56- locale
57- role
58- }
59- count
60- }
61- ```
25+ Let's start by the use case. For instance, * src/api/src/UseCase/User/GetUsers.php* :
6226
6327``` php title="src/api/src/UseCase/User/GetUsers.php"
6428/**
@@ -83,6 +47,86 @@ public function users(
8347}
8448```
8549
50+ If we split this use case, we have:
51+
52+ ** The annotations**
53+
54+ * ` @return ` : it contains both an array of your type AND a ` ResultIterator ` . It will make both PHPStan and GraphLite happy!
55+ * ` @Query ` : it equivalent to a REST GET method.
56+ * ` @Logged ` and ` @Security ` : access control.
57+
58+ ** The arguments**
59+
60+ * ` $search ` : a random filter.
61+ * ` $role ` : a predictable filter.
62+ * ` $sortBy ` : the sort field.
63+ * ` $sortOrder ` : the sort direction.
64+
65+ ** The return value:** ` ResultIterator ` .
66+
67+ ** The logic:** ` UserDao ` 's ` search ` method.
68+
69+ ::: note
70+
71+ 📣  ;  ; The predictable filter, sort field and sort direction are enumerators.
72+
73+ :::
74+
75+ ::: note
76+
77+ 📣  ;  ; There are no ` $limit ` or ` $offset ` arguments.
78+
79+ :::
80+
81+ Let's take a look at the GraphQL query for the ` GetUsers ` use case:
82+
83+ ``` graphql title="GraphQL query"
84+ query users ($search : String , $role : Role , $sortBy : UsersSortBy , $sortOrder : SortOrder , $limit : Int ! , $offset : Int ! ) {
85+ users (search : $search , role : $role , sortBy : $sortBy , sortOrder : $sortOrder ) {
86+ items (limit : $limit , offset : $offset ) {
87+ id
88+ firstName
89+ lastName
90+ email
91+ locale
92+ role
93+ }
94+ count
95+ }
96+ }
97+ ```
98+
99+ Here we can see that:
100+
101+ 1 . The enumerators are valid GraphQL types.
102+ 2 . The ` items ` property with the ` limit ` and ` offset ` arguments plus the ` count ` property come from the ` ResultIterator ` .
103+
104+ ### Enumerators
105+
106+ The folder * src/api/src/Domain/Enum* contains our enumerators.
107+
108+ Each enumerator's key (i.e., ` FIRST_NAME ` ) is a GraphQL value, while each enumerator's value (i.e., ` first_name ` )
109+ is a valid SQL expression.
110+
111+ ::: note
112+
113+ 📣  ;  ; The key is for your GraphQL query; it not valid, [ GraphQLite] ( https://graphqlite.thecodingmachine.io/ )
114+ throws an exception.
115+
116+ :::
117+
118+ ::: note
119+
120+ 📣  ;  ; The value is for creating SQL statements like the sort clause.
121+
122+ :::
123+
124+ ### Result iterator
125+
126+ In the API, you will not manage the limit and offset of your data. That's the role of the ` ResultIterator ` .
127+
128+ ### DAO
129+
86130``` php title="src/api/src/Domain/Dao/UserDao.php"
87131/**
88132 * @return User[]|ResultIterator
@@ -110,9 +154,172 @@ public function search(
110154}
111155```
112156
157+ The goal of the DAO is to:
158+
159+ * Initialize the sort values.
160+ * Build the SQL query.
161+ * Retrieve the data from the database.
162+
163+ ::: note
164+
165+ 📣  ;  ; Reminder: an enumerator's value is for creating SQL statements like the sort clause.
166+
167+ :::
168+
113169## Web application
114170
115- > IF default TEXT filters === null, it will refresh the page the first time!!
171+ In the API, a list means displaying data according to user's inputs.
172+
173+ ### List mixin
174+
175+ The boilerplate provides the * src/webapp/mixins/list.js* mixin.
176+
177+ ::: note
178+
179+ 📣  ;  ; A mixin content merges with the content of your Vue component.
180+
181+ :::
182+
183+ This mixin contains useful data and methods to help you build a list.
184+
185+ ### Initialization
186+
187+ The initialization of your page occurs in the ` asyncData ` attribute of your Vue Component:
188+
189+ ``` vue title="src/webapp/pages/admin/users/index.vue"
190+ async asyncData(context) {
191+ try {
192+ const result = await context.app.$graphql.request(UsersQuery, {
193+ search: context.route.query.search || '',
194+ role: context.route.query.role || null,
195+ sortBy: context.route.query.sortBy || null,
196+ sortOrder: context.route.query.sortOrder || null,
197+ limit: defaultItemsPerPage,
198+ offset: calculateOffset(
199+ context.route.query.page || 1,
200+ defaultItemsPerPage
201+ ),
202+ })
203+
204+ return {
205+ items: result.users.items,
206+ count: result.users.count,
207+ }
208+ } catch (e) {
209+ context.error(e)
210+ }
211+ },
212+ ```
213+
214+ ::: note
215+
216+ 📣  ;  ; On page access, Nuxt.js always renders a Vue component with an ` asyncData ` attribute on the server; you don't have access to
217+ ` this ` but ` context ` instead.
218+
219+ :::
220+
221+ ::: note
222+
223+ 📣  ;  ; ` asyncData ` will merge with the ` data ` of your Vue component.
224+
225+ :::
226+
227+ First, we do a GraphQL query to retrieve our data.
228+
229+ The parameters' values may come from query parameters (i.e., ` ?foo=bar,baz=2 ` ) or use a default value.
230+
231+ ::: note
232+
233+ 📣  ;  ; Don't use ` null ` as value for your scalar parameters as it will make your page reload. Prefer,
234+ for instance, an empty string.
235+
236+ :::
237+
238+ The ` limit ` parameter uses the ` defaultItemsPerPage ` constant from the list mixin, but you may define a custom constant
239+ in your Vue component.
240+
241+ The ` offset ` parameter uses the ` calculateOffset ` method from the list mixin.
242+
243+ Once the GraphQL query finishes, there are two possible outcomes:
244+
245+ 1 . No error: your fills the ` items ` and ` count ` data from the list mixin.
246+ 2 . An error occurs (access control mostly): you catch it and provide it to the ` context `
247+ (see our [ Security] ( /docs/guides/security ) guide for more details).
248+
249+ ::: note
250+
251+ 📣  ;  ; You may display the data in your ` <template> ` block thanks to the ` items ` .
252+
253+ :::
254+
255+ ### Filters
256+
257+ ``` vue title=""
258+ data() {
259+ return {
260+ filters: {
261+ search: this.$route.query.search || '',
262+ role: this.$route.query.role || null,
263+ },
264+ fields: [
265+ { key: 'id', label: '#', sortable: false },
266+ {
267+ key: 'firstName',
268+ label: this.$t('common.first_name'),
269+ sortable: true,
270+ },
271+ { key: 'lastName', label: this.$t('common.last_name'), sortable: true },
272+ { key: 'email', label: this.$t('common.email'), sortable: true },
273+ { key: 'locale', label: this.$t('common.locale'), sortable: false },
274+ { key: 'role', label: this.$t('common.role'), sortable: false },
275+ ],
276+ sortByMap: {
277+ firstName: FIRST_NAME,
278+ lastName: LAST_NAME,
279+ email: EMAIL,
280+ },
281+ }
282+ },
283+ ```
284+
285+ ### Fields / Headers
286+
287+ If you are using a Bootstrap Vue's ` <b-table> ` , you also need to define the ` fields ` (i.e., the headers).
288+
289+ You do that in the ` data ` attribute of your Vue component:
290+
291+ ``` vue title=""
292+ data() {
293+ return {
294+ fields: [
295+ { key: 'id', label: '#', sortable: false },
296+ {
297+ key: 'firstName',
298+ label: this.$t('common.first_name'),
299+ sortable: true,
300+ },
301+ { key: 'lastName', label: this.$t('common.last_name'), sortable: true },
302+ { key: 'email', label: this.$t('common.email'), sortable: true },
303+ { key: 'locale', label: this.$t('common.locale'), sortable: false },
304+ { key: 'role', label: this.$t('common.role'), sortable: false },
305+ ],
306+ }
307+ },
308+ ```
309+
310+ ``` vue title="Bootstrap table"
311+ <b-table
312+ :items="items"
313+ :fields="fields"
314+ ></b-table>
315+ ```
316+
317+ ### Search
318+
319+ ### Sort
320+
321+ ### Paginate
322+
323+ ### Full example
116324
117- > CLIENT need to try catch and call this.$nuxt.error
118- > SERVER need to try catch and call context.error
325+ See * src/webapp/pages/admin/users/index.vue* .
0 commit comments