@awes-io/ui 2.142.0 → 2.143.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/CHANGELOG.md +58 -0
- package/assets/css/components/_index.css +7 -1
- package/assets/css/components/animation.css +38 -32
- package/assets/css/components/content-placeholder.css +103 -0
- package/assets/css/components/empty-container.css +69 -1
- package/assets/css/components/filter-chosen.css +6 -0
- package/assets/css/components/filter-date-range.css +17 -1
- package/assets/css/components/filter-month.css +23 -17
- package/assets/css/components/filter-select.css +11 -0
- package/assets/css/components/layout.css +1 -32
- package/assets/css/components/modal.css +1 -1
- package/assets/css/components/number.css +12 -0
- package/assets/css/components/page-aside.css +54 -0
- package/assets/js/css.js +1 -1
- package/assets/js/icons/mono.js +59 -91
- package/assets/js/icons/multicolor.js +1 -31
- package/components/1_atoms/AwContentPlaceholder.vue +60 -0
- package/components/1_atoms/AwFlow.vue +21 -48
- package/components/1_atoms/AwLabel.vue +1 -1
- package/components/2_molecules/AwButton.vue +1 -1
- package/components/2_molecules/AwEmptyContainer.vue +74 -72
- package/components/2_molecules/AwNumber.vue +180 -0
- package/components/2_molecules/AwSelect.vue +11 -4
- package/components/3_organisms/AwFilterChosen.vue +73 -0
- package/components/3_organisms/AwFilterDateRange.vue +177 -0
- package/components/3_organisms/AwFilterMonth.vue +37 -40
- package/components/3_organisms/AwFilterSelect.vue +368 -0
- package/components/3_organisms/AwImageUpload.vue +1 -1
- package/components/3_organisms/AwMarkdownEditor.vue +0 -0
- package/components/3_organisms/AwMultiBlockBuilder.vue +1 -1
- package/components/3_organisms/AwTable/AwTableBuilder.vue +12 -60
- package/components/4_pages/AwPageAside.vue +108 -0
- package/components/5_layouts/AwLayoutCenter.vue +3 -8
- package/components/5_layouts/_AwUserMenu.vue +1 -1
- package/dist/css/aw-icons.css +26 -0
- package/dist/fonts/aw-icons.svg +18 -0
- package/dist/fonts/aw-icons.ttf +0 -0
- package/dist/fonts/aw-icons.woff +0 -0
- package/dist/fonts/aw-icons.woff2 +0 -0
- package/docs/_template.md +80 -0
- package/docs/components/atoms/aw-accordion-fold.md +91 -0
- package/docs/components/atoms/aw-action-card-body.md +67 -0
- package/docs/components/atoms/aw-action-card.md +94 -0
- package/docs/components/atoms/aw-action-icon.md +88 -0
- package/docs/components/atoms/aw-avatar.md +106 -0
- package/docs/components/atoms/aw-card.md +112 -0
- package/docs/components/atoms/aw-checkbox.md +112 -0
- package/docs/components/atoms/aw-content-placeholder.md +116 -0
- package/docs/components/atoms/aw-description.md +83 -0
- package/docs/components/atoms/aw-dock.md +84 -0
- package/docs/components/atoms/aw-dropdown-button.md +94 -0
- package/docs/components/atoms/aw-dropdown.md +128 -0
- package/docs/components/atoms/aw-file.md +73 -0
- package/docs/components/atoms/aw-flow.md +92 -0
- package/docs/components/atoms/aw-grid.md +91 -0
- package/docs/components/atoms/aw-headline.md +71 -0
- package/docs/components/atoms/aw-icon-system-color.md +121 -0
- package/docs/components/atoms/aw-icon-system-mono.md +205 -0
- package/docs/components/atoms/aw-icon.md +235 -0
- package/docs/components/atoms/aw-info.md +85 -0
- package/docs/components/atoms/aw-input.md +120 -0
- package/docs/components/atoms/aw-label.md +83 -0
- package/docs/components/atoms/aw-link.md +99 -0
- package/docs/components/atoms/aw-list.md +88 -0
- package/docs/components/atoms/aw-progress.md +70 -0
- package/docs/components/atoms/aw-radio.md +109 -0
- package/docs/components/atoms/aw-refresh-wrapper.md +81 -0
- package/docs/components/atoms/aw-select-native.md +106 -0
- package/docs/components/atoms/aw-slider.md +82 -0
- package/docs/components/atoms/aw-sub-headline.md +73 -0
- package/docs/components/atoms/aw-switcher.md +115 -0
- package/docs/components/atoms/aw-tag.md +80 -0
- package/docs/components/atoms/aw-title.md +70 -0
- package/docs/components/atoms/aw-toggler.md +69 -0
- package/docs/components/layouts/aw-layout-center.md +168 -0
- package/docs/components/layouts/aw-layout-error.md +153 -0
- package/docs/components/layouts/aw-layout-provider.md +238 -0
- package/docs/components/layouts/aw-layout.md +88 -0
- package/docs/components/molecules/aw-action-button.md +91 -0
- package/docs/components/molecules/aw-alert.md +96 -0
- package/docs/components/molecules/aw-badge.md +108 -0
- package/docs/components/molecules/aw-banner-text.md +90 -0
- package/docs/components/molecules/aw-button-nav.md +46 -0
- package/docs/components/molecules/aw-button.md +123 -0
- package/docs/components/molecules/aw-description-input.md +67 -0
- package/docs/components/molecules/aw-empty-container.md +86 -0
- package/docs/components/molecules/aw-island.md +234 -0
- package/docs/components/molecules/aw-number.md +138 -0
- package/docs/components/molecules/aw-select-object.md +401 -0
- package/docs/components/molecules/aw-select.md +215 -0
- package/docs/components/molecules/aw-tab-nav.md +108 -0
- package/docs/components/molecules/aw-tel.md +129 -0
- package/docs/components/molecules/aw-textarea.md +83 -0
- package/docs/components/molecules/aw-userpic.md +115 -0
- package/docs/components/organisms/aw-address-block.md +64 -0
- package/docs/components/organisms/aw-address.md +132 -0
- package/docs/components/organisms/aw-birthday-picker.md +73 -0
- package/docs/components/organisms/aw-bottom-bar.md +66 -0
- package/docs/components/organisms/aw-calendar-days.md +115 -0
- package/docs/components/organisms/aw-calendar-nav.md +98 -0
- package/docs/components/organisms/aw-calendar-view.md +98 -0
- package/docs/components/organisms/aw-calendar.md +166 -0
- package/docs/components/organisms/aw-chart.md +154 -0
- package/docs/components/organisms/aw-chip-select.md +164 -0
- package/docs/components/organisms/aw-chip.md +126 -0
- package/docs/components/organisms/aw-code-snippet.md +94 -0
- package/docs/components/organisms/aw-code.md +132 -0
- package/docs/components/organisms/aw-context-menu.md +117 -0
- package/docs/components/organisms/aw-cropper.md +151 -0
- package/docs/components/organisms/aw-date.md +161 -0
- package/docs/components/organisms/aw-display-date.md +33 -0
- package/docs/components/organisms/aw-download-link.md +46 -0
- package/docs/components/organisms/aw-fetch-data.md +161 -0
- package/docs/components/organisms/aw-filter-chosen.md +226 -0
- package/docs/components/organisms/aw-filter-date-range.md +205 -0
- package/docs/components/organisms/aw-filter-month.md +43 -0
- package/docs/components/organisms/aw-filter-select.md +225 -0
- package/docs/components/organisms/aw-form.md +174 -0
- package/docs/components/organisms/aw-gmap-marker.md +86 -0
- package/docs/components/organisms/aw-gmap.md +90 -0
- package/docs/components/organisms/aw-image-upload.md +56 -0
- package/docs/components/organisms/aw-island-avatar.md +87 -0
- package/docs/components/organisms/aw-markdown-editor.md +104 -0
- package/docs/components/organisms/aw-modal-buttons.md +57 -0
- package/docs/components/organisms/aw-modal.md +246 -0
- package/docs/components/organisms/aw-model-edit.md +74 -0
- package/docs/components/organisms/aw-money.md +53 -0
- package/docs/components/organisms/aw-multi-block-builder.md +165 -0
- package/docs/components/organisms/aw-pagination.md +121 -0
- package/docs/components/organisms/aw-password.md +103 -0
- package/docs/components/organisms/aw-preview-card.md +45 -0
- package/docs/components/organisms/aw-search.md +116 -0
- package/docs/components/organisms/aw-subnav.md +122 -0
- package/docs/components/organisms/aw-table-builder.md +165 -0
- package/docs/components/organisms/aw-table-col.md +123 -0
- package/docs/components/organisms/aw-table-head.md +92 -0
- package/docs/components/organisms/aw-table-row.md +91 -0
- package/docs/components/organisms/aw-table.md +172 -0
- package/docs/components/organisms/aw-tags.md +54 -0
- package/docs/components/organisms/aw-toggle-show-aside.md +43 -0
- package/docs/components/organisms/aw-uploader-files.md +125 -0
- package/docs/components/organisms/aw-uploader.md +163 -0
- package/docs/components/organisms/aw-user-menu.md +87 -0
- package/docs/components/pages/aw-page-aside.md +296 -0
- package/docs/components/pages/aw-page-menu-buttons.md +172 -0
- package/docs/components/pages/aw-page-modal.md +198 -0
- package/docs/components/pages/aw-page-single.md +253 -0
- package/docs/components/pages/aw-page.md +194 -0
- package/docs/configuration.md +493 -0
- package/docs/cookbook/advanced-patterns.md +1388 -0
- package/docs/cookbook/common-patterns.md +965 -0
- package/docs/cookbook/index.md +786 -0
- package/docs/getting-started.md +596 -0
- package/docs/guides/best-practices.md +1106 -0
- package/docs/guides/data-fetching.md +852 -0
- package/docs/guides/error-handling.md +1172 -0
- package/docs/guides/forms-guide.md +1329 -0
- package/docs/guides/mobile-subnavigation.md +359 -0
- package/docs/guides/page-patterns/aside-pages.md +1418 -0
- package/docs/guides/page-patterns/dashboard-pages.md +990 -0
- package/docs/guides/page-patterns/detail-pages.md +1493 -0
- package/docs/guides/page-patterns/list-pages.md +1094 -0
- package/docs/index.md +263 -1
- package/docs/integrations.md +870 -0
- package/docs/reference/menu.md +462 -0
- package/docs/reference/plugins.md +970 -0
- package/docs/reference/troubleshooting.md +945 -0
- package/nuxt/awes.config.js +9 -8
- package/nuxt/icons.css +26 -0
- package/nuxt/index.js +2 -2
- package/nuxt/pages/more.vue +1 -1
- package/package.json +5 -3
- package/readme.md +171 -1
- package/docs/aw-accordion-fold.md +0 -46
- package/docs/aw-address.md +0 -44
- package/docs/aw-avatar.md +0 -51
- package/docs/aw-badge.md +0 -32
- package/docs/aw-button-nav.md +0 -44
- package/docs/aw-button.md +0 -50
- package/docs/aw-calendar-days.md +0 -46
- package/docs/aw-calendar-nav.md +0 -25
- package/docs/aw-calendar-view.md +0 -12
- package/docs/aw-calendar.md +0 -59
- package/docs/aw-card.md +0 -48
- package/docs/aw-chart.md +0 -51
- package/docs/aw-checkbox.md +0 -56
- package/docs/aw-chip-select.md +0 -46
- package/docs/aw-chip.md +0 -53
- package/docs/aw-code-snippet.md +0 -18
- package/docs/aw-code.md +0 -56
- package/docs/aw-content-wrapper.md +0 -40
- package/docs/aw-context-menu.md +0 -31
- package/docs/aw-cropper.md +0 -60
- package/docs/aw-dashboard-card.md +0 -37
- package/docs/aw-dashboard-donut.md +0 -30
- package/docs/aw-dashboard-line.md +0 -20
- package/docs/aw-dashboard-progress.md +0 -33
- package/docs/aw-dashboard-section.md +0 -32
- package/docs/aw-dashboard-speed.md +0 -30
- package/docs/aw-date.md +0 -52
- package/docs/aw-dropdown-button.md +0 -31
- package/docs/aw-dropdown.md +0 -69
- package/docs/aw-fetch-data.md +0 -45
- package/docs/aw-form.md +0 -52
- package/docs/aw-grid.md +0 -48
- package/docs/aw-icon.md +0 -50
- package/docs/aw-info.md +0 -53
- package/docs/aw-input.md +0 -55
- package/docs/aw-layout-default.md +0 -30
- package/docs/aw-layout-frame-center.md +0 -29
- package/docs/aw-layout-simple.md +0 -49
- package/docs/aw-link.md +0 -54
- package/docs/aw-markdown-editor.md +0 -51
- package/docs/aw-modal.md +0 -63
- package/docs/aw-multi-block-builder.md +0 -66
- package/docs/aw-page.md +0 -36
- package/docs/aw-pagination.md +0 -54
- package/docs/aw-password.md +0 -48
- package/docs/aw-radio.md +0 -54
- package/docs/aw-search.md +0 -49
- package/docs/aw-select.md +0 -93
- package/docs/aw-slider.md +0 -40
- package/docs/aw-svg-image.md +0 -19
- package/docs/aw-switcher.md +0 -51
- package/docs/aw-tab-nav.md +0 -55
- package/docs/aw-table-builder.md +0 -58
- package/docs/aw-table-col.md +0 -33
- package/docs/aw-table-head.md +0 -28
- package/docs/aw-table-row.md +0 -33
- package/docs/aw-table.md +0 -59
- package/docs/aw-tel.md +0 -47
- package/docs/aw-textarea.md +0 -47
- package/docs/aw-timeline-builder.md +0 -50
- package/docs/aw-toggler.md +0 -41
- package/docs/aw-uploader-files.md +0 -20
- package/docs/aw-uploader.md +0 -60
- package/docs/aw-user-menu.md +0 -34
- package/docs/aw-userpic.md +0 -34
- /package/components/{3_organisms → 2_molecules}/AwTel.vue +0 -0
|
@@ -0,0 +1,852 @@
|
|
|
1
|
+
# Data Fetching Guide
|
|
2
|
+
|
|
3
|
+
Complete guide to fetching data from your Laravel backend using vue-mc collections and models.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The AwesCode UI framework uses **vue-mc** for data management, which provides:
|
|
8
|
+
- **Collections** - For lists of items with pagination
|
|
9
|
+
- **Models** - For single resources
|
|
10
|
+
- **Automatic API communication** - Built-in axios integration
|
|
11
|
+
- **Validation** - Client and server-side validation
|
|
12
|
+
- **Lifecycle hooks** - Before/after fetch, save, delete
|
|
13
|
+
|
|
14
|
+
## Collection Auto-Fetching with AwTableBuilder
|
|
15
|
+
|
|
16
|
+
### The Simple Way
|
|
17
|
+
|
|
18
|
+
AwTableBuilder automatically fetches collection data - no manual fetching needed!
|
|
19
|
+
|
|
20
|
+
```markup
|
|
21
|
+
<template>
|
|
22
|
+
<AwPage title="Customers">
|
|
23
|
+
<AwTableBuilder :collection="customers">
|
|
24
|
+
<AwTableCol field="name" title="Name" />
|
|
25
|
+
<AwTableCol field="email" title="Email" />
|
|
26
|
+
</AwTableBuilder>
|
|
27
|
+
</AwPage>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<script>
|
|
31
|
+
import Customers from '~/collections/Customers'
|
|
32
|
+
|
|
33
|
+
export default {
|
|
34
|
+
data() {
|
|
35
|
+
return {
|
|
36
|
+
customers: new Customers([], {
|
|
37
|
+
shop_uuid: this.$route.params.shop_uuid
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// No fetch() or mounted() needed!
|
|
42
|
+
}
|
|
43
|
+
</script>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**What happens:**
|
|
47
|
+
1. ✅ AwTableBuilder mounts
|
|
48
|
+
2. ✅ Automatically calls `customers.fetch()`
|
|
49
|
+
3. ✅ Shows loading state
|
|
50
|
+
4. ✅ Displays data when loaded
|
|
51
|
+
5. ✅ Handles pagination automatically
|
|
52
|
+
|
|
53
|
+
### ⚠️ Important: Don't Use v-if for Loading
|
|
54
|
+
|
|
55
|
+
```markup
|
|
56
|
+
<!-- ❌ BAD - Prevents AwTableBuilder from mounting -->
|
|
57
|
+
<template>
|
|
58
|
+
<AwPage title="Customers">
|
|
59
|
+
<div v-if="loading">Loading...</div>
|
|
60
|
+
<AwTableBuilder v-else :collection="customers">
|
|
61
|
+
<!-- Never mounts if loading=true initially -->
|
|
62
|
+
</AwTableBuilder>
|
|
63
|
+
</AwPage>
|
|
64
|
+
</template>
|
|
65
|
+
|
|
66
|
+
<!-- ✅ GOOD - Let AwTableBuilder handle loading -->
|
|
67
|
+
<template>
|
|
68
|
+
<AwPage title="Customers">
|
|
69
|
+
<AwTableBuilder :collection="customers">
|
|
70
|
+
<!-- Mounts immediately, shows internal loading state -->
|
|
71
|
+
</AwTableBuilder>
|
|
72
|
+
</AwPage>
|
|
73
|
+
</template>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### With Search Parameters
|
|
77
|
+
|
|
78
|
+
AwTableBuilder watches for route query changes:
|
|
79
|
+
|
|
80
|
+
```markup
|
|
81
|
+
<template>
|
|
82
|
+
<AwPage title="Customers">
|
|
83
|
+
<div class="flex justify-end mb-6">
|
|
84
|
+
<AwSearch class="w-full lg:w-auto" />
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<AwTableBuilder
|
|
88
|
+
:collection="customers"
|
|
89
|
+
:watch-params="['search']"
|
|
90
|
+
>
|
|
91
|
+
<AwTableCol field="name" title="Name" />
|
|
92
|
+
</AwTableBuilder>
|
|
93
|
+
</AwPage>
|
|
94
|
+
</template>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**How it works:**
|
|
98
|
+
1. User types in AwSearch
|
|
99
|
+
2. Updates `$route.query.search`
|
|
100
|
+
3. AwTableBuilder detects change
|
|
101
|
+
4. Automatically refetches collection
|
|
102
|
+
|
|
103
|
+
### With Filter Options
|
|
104
|
+
|
|
105
|
+
Pass additional options to collection:
|
|
106
|
+
|
|
107
|
+
```markup
|
|
108
|
+
<template>
|
|
109
|
+
<AwPage title="Orders">
|
|
110
|
+
<AwSelect
|
|
111
|
+
v-model="statusFilter"
|
|
112
|
+
:options="['all', 'pending', 'completed']"
|
|
113
|
+
label="Status"
|
|
114
|
+
/>
|
|
115
|
+
|
|
116
|
+
<AwTableBuilder
|
|
117
|
+
:collection="orders"
|
|
118
|
+
:options="filterOptions"
|
|
119
|
+
>
|
|
120
|
+
<AwTableCol field="id" title="Order #" />
|
|
121
|
+
</AwTableBuilder>
|
|
122
|
+
</AwPage>
|
|
123
|
+
</template>
|
|
124
|
+
|
|
125
|
+
<script>
|
|
126
|
+
export default {
|
|
127
|
+
data() {
|
|
128
|
+
return {
|
|
129
|
+
statusFilter: 'all'
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
computed: {
|
|
134
|
+
filterOptions() {
|
|
135
|
+
return {
|
|
136
|
+
shop_uuid: this.$route.params.shop_uuid,
|
|
137
|
+
status: this.statusFilter !== 'all' ? this.statusFilter : undefined
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
watch: {
|
|
143
|
+
filterOptions: {
|
|
144
|
+
handler() {
|
|
145
|
+
this.orders.fetch()
|
|
146
|
+
},
|
|
147
|
+
deep: true
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
</script>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Manual Collection Fetching
|
|
155
|
+
|
|
156
|
+
### Basic Manual Fetch
|
|
157
|
+
|
|
158
|
+
For cases where you need manual control:
|
|
159
|
+
|
|
160
|
+
```markup
|
|
161
|
+
<template>
|
|
162
|
+
<AwPage title="Analytics">
|
|
163
|
+
<AwContentPlaceholder v-if="loading" type="text" :lines="6" />
|
|
164
|
+
|
|
165
|
+
<div v-else>
|
|
166
|
+
<div v-for="item in analytics.models" :key="item.id">
|
|
167
|
+
{{ item.name }}
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
</AwPage>
|
|
171
|
+
</template>
|
|
172
|
+
|
|
173
|
+
<script>
|
|
174
|
+
import Analytics from '~/collections/Analytics'
|
|
175
|
+
|
|
176
|
+
export default {
|
|
177
|
+
data() {
|
|
178
|
+
return {
|
|
179
|
+
analytics: new Analytics([], {
|
|
180
|
+
shop_uuid: this.$route.params.shop_uuid
|
|
181
|
+
}),
|
|
182
|
+
loading: true
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
async mounted() {
|
|
187
|
+
await this.loadAnalytics()
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
methods: {
|
|
191
|
+
async loadAnalytics() {
|
|
192
|
+
this.loading = true
|
|
193
|
+
try {
|
|
194
|
+
await this.analytics.fetch()
|
|
195
|
+
} catch (error) {
|
|
196
|
+
this.$notify({
|
|
197
|
+
message: 'Failed to load analytics',
|
|
198
|
+
type: 'error'
|
|
199
|
+
})
|
|
200
|
+
} finally {
|
|
201
|
+
this.loading = false
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
</script>
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### With Parameters
|
|
210
|
+
|
|
211
|
+
Pass query parameters to fetch:
|
|
212
|
+
|
|
213
|
+
```javascript
|
|
214
|
+
async loadData() {
|
|
215
|
+
await this.collection.fetch({
|
|
216
|
+
params: {
|
|
217
|
+
start_date: this.dateRange.start,
|
|
218
|
+
end_date: this.dateRange.end,
|
|
219
|
+
category: this.selectedCategory
|
|
220
|
+
}
|
|
221
|
+
})
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Refetching After Changes
|
|
226
|
+
|
|
227
|
+
Refetch collection after mutations:
|
|
228
|
+
|
|
229
|
+
```javascript
|
|
230
|
+
async deleteItem(item) {
|
|
231
|
+
const confirmed = await this.$confirm({
|
|
232
|
+
title: 'Delete Item',
|
|
233
|
+
message: 'Are you sure?'
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
if (!confirmed) return
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
await this.$axios.delete(`/api/items/${item.id}`)
|
|
240
|
+
this.$notify({
|
|
241
|
+
message: 'Item deleted',
|
|
242
|
+
type: 'success'
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
// Refetch collection
|
|
246
|
+
await this.items.fetch()
|
|
247
|
+
} catch (error) {
|
|
248
|
+
this.$notify({
|
|
249
|
+
message: 'Failed to delete item',
|
|
250
|
+
type: 'error'
|
|
251
|
+
})
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Model Fetching
|
|
257
|
+
|
|
258
|
+
### Fetch Existing Model
|
|
259
|
+
|
|
260
|
+
Load data for existing record:
|
|
261
|
+
|
|
262
|
+
```markup
|
|
263
|
+
<script>
|
|
264
|
+
import Customer from '~/models/Customer'
|
|
265
|
+
|
|
266
|
+
export default {
|
|
267
|
+
data() {
|
|
268
|
+
return {
|
|
269
|
+
customer: new Customer(
|
|
270
|
+
{ id: this.$route.params.id },
|
|
271
|
+
null,
|
|
272
|
+
{ shop_uuid: this.$route.params.shop_uuid }
|
|
273
|
+
),
|
|
274
|
+
loading: true
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
async mounted() {
|
|
279
|
+
if (!this.customer.isNew()) {
|
|
280
|
+
try {
|
|
281
|
+
await this.customer.fetch()
|
|
282
|
+
} catch (error) {
|
|
283
|
+
// Handle 404
|
|
284
|
+
if (error.response?.status === 404) {
|
|
285
|
+
this.$notify({
|
|
286
|
+
message: 'Customer not found',
|
|
287
|
+
type: 'error'
|
|
288
|
+
})
|
|
289
|
+
this.$router.push('/customers')
|
|
290
|
+
return
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Other errors
|
|
294
|
+
this.$notify({
|
|
295
|
+
message: 'Failed to load customer',
|
|
296
|
+
type: 'error'
|
|
297
|
+
})
|
|
298
|
+
} finally {
|
|
299
|
+
this.loading = false
|
|
300
|
+
}
|
|
301
|
+
} else {
|
|
302
|
+
this.loading = false
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
</script>
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Conditional Fetching
|
|
310
|
+
|
|
311
|
+
Only fetch if not creating new:
|
|
312
|
+
|
|
313
|
+
```javascript
|
|
314
|
+
async mounted() {
|
|
315
|
+
const uuid = this.$route.params.uuid
|
|
316
|
+
|
|
317
|
+
if (uuid !== 'new') {
|
|
318
|
+
this.loading = true
|
|
319
|
+
try {
|
|
320
|
+
await this.model.fetch()
|
|
321
|
+
} catch (error) {
|
|
322
|
+
this.$notify({
|
|
323
|
+
message: 'Record not found',
|
|
324
|
+
type: 'error'
|
|
325
|
+
})
|
|
326
|
+
this.$router.push('/list')
|
|
327
|
+
} finally {
|
|
328
|
+
this.loading = false
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
## AwSelect Async Loading
|
|
335
|
+
|
|
336
|
+
### Function Returns URL
|
|
337
|
+
|
|
338
|
+
For dropdowns with many options, use async loading:
|
|
339
|
+
|
|
340
|
+
```markup
|
|
341
|
+
<template>
|
|
342
|
+
<AwSelect
|
|
343
|
+
v-model="product.category_id"
|
|
344
|
+
:options="loadCategories"
|
|
345
|
+
option-label="name"
|
|
346
|
+
track-by="id"
|
|
347
|
+
label="Category"
|
|
348
|
+
/>
|
|
349
|
+
</template>
|
|
350
|
+
|
|
351
|
+
<script>
|
|
352
|
+
export default {
|
|
353
|
+
methods: {
|
|
354
|
+
// Return URL string for AwSelect to fetch from
|
|
355
|
+
loadCategories(search) {
|
|
356
|
+
const shopUuid = this.$route.params.shop_uuid
|
|
357
|
+
return `/api/shops/${shopUuid}/categories?search=${search}`
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
</script>
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
**How it works:**
|
|
365
|
+
1. ✅ Pass **function** to `:options` (not array)
|
|
366
|
+
2. ✅ Function receives `search` parameter
|
|
367
|
+
3. ✅ Returns URL string
|
|
368
|
+
4. ✅ AwSelect makes GET request automatically
|
|
369
|
+
5. ✅ Autocomplete behavior as user types
|
|
370
|
+
|
|
371
|
+
### With Custom Label Formatting
|
|
372
|
+
|
|
373
|
+
```markup
|
|
374
|
+
<AwSelect
|
|
375
|
+
v-model="campaign.template_key"
|
|
376
|
+
:options="loadTemplates"
|
|
377
|
+
:option-label="formatTemplateLabel"
|
|
378
|
+
track-by="template_key"
|
|
379
|
+
label="Template"
|
|
380
|
+
/>
|
|
381
|
+
|
|
382
|
+
<script>
|
|
383
|
+
export default {
|
|
384
|
+
methods: {
|
|
385
|
+
loadTemplates(search) {
|
|
386
|
+
return `/api/templates?search=${search}`
|
|
387
|
+
},
|
|
388
|
+
|
|
389
|
+
formatTemplateLabel(template) {
|
|
390
|
+
return `${template.name} (${template.channel})`
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
</script>
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
## Pagination
|
|
398
|
+
|
|
399
|
+
### Automatic with AwTableBuilder
|
|
400
|
+
|
|
401
|
+
AwTableBuilder handles pagination automatically:
|
|
402
|
+
|
|
403
|
+
```markup
|
|
404
|
+
<AwTableBuilder :collection="customers">
|
|
405
|
+
<AwTableCol field="name" title="Name" />
|
|
406
|
+
</AwTableBuilder>
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
Collection response should include pagination meta:
|
|
410
|
+
|
|
411
|
+
```json
|
|
412
|
+
{
|
|
413
|
+
"data": [...],
|
|
414
|
+
"meta": {
|
|
415
|
+
"current_page": 1,
|
|
416
|
+
"last_page": 10,
|
|
417
|
+
"per_page": 15,
|
|
418
|
+
"total": 145
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### Manual Pagination
|
|
424
|
+
|
|
425
|
+
Use AwPagination component:
|
|
426
|
+
|
|
427
|
+
```markup
|
|
428
|
+
<template>
|
|
429
|
+
<div>
|
|
430
|
+
<div v-for="item in items.models" :key="item.id">
|
|
431
|
+
{{ item.name }}
|
|
432
|
+
</div>
|
|
433
|
+
|
|
434
|
+
<AwPagination
|
|
435
|
+
v-model="page"
|
|
436
|
+
:total-pages="totalPages"
|
|
437
|
+
@input="loadPage"
|
|
438
|
+
/>
|
|
439
|
+
</div>
|
|
440
|
+
</template>
|
|
441
|
+
|
|
442
|
+
<script>
|
|
443
|
+
export default {
|
|
444
|
+
data() {
|
|
445
|
+
return {
|
|
446
|
+
page: 1,
|
|
447
|
+
totalPages: 1
|
|
448
|
+
}
|
|
449
|
+
},
|
|
450
|
+
|
|
451
|
+
methods: {
|
|
452
|
+
async loadPage(page) {
|
|
453
|
+
await this.items.fetch({
|
|
454
|
+
params: { page }
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
// Update total pages from response
|
|
458
|
+
this.totalPages = this.items.response?.meta?.last_page || 1
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
</script>
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
## Error Handling
|
|
466
|
+
|
|
467
|
+
### Collection Fetch Errors
|
|
468
|
+
|
|
469
|
+
```javascript
|
|
470
|
+
async loadData() {
|
|
471
|
+
try {
|
|
472
|
+
await this.collection.fetch()
|
|
473
|
+
} catch (error) {
|
|
474
|
+
console.error('Fetch failed:', error)
|
|
475
|
+
|
|
476
|
+
// Network error
|
|
477
|
+
if (!error.response) {
|
|
478
|
+
this.$notify({
|
|
479
|
+
message: 'Network error. Please check your connection.',
|
|
480
|
+
type: 'error'
|
|
481
|
+
})
|
|
482
|
+
return
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// 404 - Not found
|
|
486
|
+
if (error.response.status === 404) {
|
|
487
|
+
this.$notify({
|
|
488
|
+
message: 'Resource not found',
|
|
489
|
+
type: 'error'
|
|
490
|
+
})
|
|
491
|
+
this.$router.push('/dashboard')
|
|
492
|
+
return
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// 403 - Forbidden
|
|
496
|
+
if (error.response.status === 403) {
|
|
497
|
+
this.$notify({
|
|
498
|
+
message: 'You do not have permission to view this',
|
|
499
|
+
type: 'error'
|
|
500
|
+
})
|
|
501
|
+
return
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Generic error
|
|
505
|
+
this.$notify({
|
|
506
|
+
message: 'Failed to load data',
|
|
507
|
+
type: 'error'
|
|
508
|
+
})
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### Model Fetch Errors
|
|
514
|
+
|
|
515
|
+
```javascript
|
|
516
|
+
async mounted() {
|
|
517
|
+
if (!this.model.isNew()) {
|
|
518
|
+
try {
|
|
519
|
+
await this.model.fetch()
|
|
520
|
+
} catch (error) {
|
|
521
|
+
if (error.response?.status === 404) {
|
|
522
|
+
this.$notify({
|
|
523
|
+
message: 'Record not found',
|
|
524
|
+
type: 'error'
|
|
525
|
+
})
|
|
526
|
+
this.$router.push('/list')
|
|
527
|
+
} else {
|
|
528
|
+
this.$notify({
|
|
529
|
+
message: 'Failed to load record',
|
|
530
|
+
type: 'error'
|
|
531
|
+
})
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
## Caching
|
|
539
|
+
|
|
540
|
+
### Simple Cache
|
|
541
|
+
|
|
542
|
+
Cache data to avoid unnecessary requests:
|
|
543
|
+
|
|
544
|
+
```javascript
|
|
545
|
+
export default {
|
|
546
|
+
data() {
|
|
547
|
+
return {
|
|
548
|
+
cachedData: null
|
|
549
|
+
}
|
|
550
|
+
},
|
|
551
|
+
|
|
552
|
+
methods: {
|
|
553
|
+
async loadData() {
|
|
554
|
+
// Return cached data if available
|
|
555
|
+
if (this.cachedData) {
|
|
556
|
+
return this.cachedData
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Fetch and cache
|
|
560
|
+
const { data } = await this.$axios.get('/api/data')
|
|
561
|
+
this.cachedData = data
|
|
562
|
+
return data
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
### Vuex Store Cache
|
|
569
|
+
|
|
570
|
+
```javascript
|
|
571
|
+
// store/customers.js
|
|
572
|
+
export const state = () => ({
|
|
573
|
+
customers: [],
|
|
574
|
+
loaded: false
|
|
575
|
+
})
|
|
576
|
+
|
|
577
|
+
export const actions = {
|
|
578
|
+
async fetch({ commit, state }) {
|
|
579
|
+
// Return cached if already loaded
|
|
580
|
+
if (state.loaded) {
|
|
581
|
+
return state.customers
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const { data } = await this.$axios.get('/api/customers')
|
|
585
|
+
commit('SET_CUSTOMERS', data)
|
|
586
|
+
commit('SET_LOADED', true)
|
|
587
|
+
return data
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
export const mutations = {
|
|
592
|
+
SET_CUSTOMERS(state, customers) {
|
|
593
|
+
state.customers = customers
|
|
594
|
+
},
|
|
595
|
+
SET_LOADED(state, loaded) {
|
|
596
|
+
state.loaded = loaded
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
## Polling for Updates
|
|
602
|
+
|
|
603
|
+
### Interval Polling
|
|
604
|
+
|
|
605
|
+
```markup
|
|
606
|
+
<script>
|
|
607
|
+
export default {
|
|
608
|
+
data() {
|
|
609
|
+
return {
|
|
610
|
+
pollingInterval: null
|
|
611
|
+
}
|
|
612
|
+
},
|
|
613
|
+
|
|
614
|
+
mounted() {
|
|
615
|
+
// Initial fetch
|
|
616
|
+
this.loadData()
|
|
617
|
+
|
|
618
|
+
// Poll every 30 seconds
|
|
619
|
+
this.pollingInterval = setInterval(() => {
|
|
620
|
+
this.loadData()
|
|
621
|
+
}, 30000)
|
|
622
|
+
},
|
|
623
|
+
|
|
624
|
+
beforeDestroy() {
|
|
625
|
+
// Clean up interval
|
|
626
|
+
if (this.pollingInterval) {
|
|
627
|
+
clearInterval(this.pollingInterval)
|
|
628
|
+
}
|
|
629
|
+
},
|
|
630
|
+
|
|
631
|
+
methods: {
|
|
632
|
+
async loadData() {
|
|
633
|
+
await this.collection.fetch()
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
</script>
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
### Conditional Polling
|
|
641
|
+
|
|
642
|
+
Only poll when page is visible:
|
|
643
|
+
|
|
644
|
+
```javascript
|
|
645
|
+
mounted() {
|
|
646
|
+
// Start polling
|
|
647
|
+
this.startPolling()
|
|
648
|
+
|
|
649
|
+
// Stop polling when page hidden
|
|
650
|
+
document.addEventListener('visibilitychange', this.handleVisibilityChange)
|
|
651
|
+
},
|
|
652
|
+
|
|
653
|
+
beforeDestroy() {
|
|
654
|
+
this.stopPolling()
|
|
655
|
+
document.removeEventListener('visibilitychange', this.handleVisibilityChange)
|
|
656
|
+
},
|
|
657
|
+
|
|
658
|
+
methods: {
|
|
659
|
+
startPolling() {
|
|
660
|
+
this.loadData()
|
|
661
|
+
this.pollingInterval = setInterval(() => {
|
|
662
|
+
if (!document.hidden) {
|
|
663
|
+
this.loadData()
|
|
664
|
+
}
|
|
665
|
+
}, 30000)
|
|
666
|
+
},
|
|
667
|
+
|
|
668
|
+
stopPolling() {
|
|
669
|
+
if (this.pollingInterval) {
|
|
670
|
+
clearInterval(this.pollingInterval)
|
|
671
|
+
}
|
|
672
|
+
},
|
|
673
|
+
|
|
674
|
+
handleVisibilityChange() {
|
|
675
|
+
if (document.hidden) {
|
|
676
|
+
this.stopPolling()
|
|
677
|
+
} else {
|
|
678
|
+
this.startPolling()
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
## Loading States
|
|
685
|
+
|
|
686
|
+
### Collection Loading
|
|
687
|
+
|
|
688
|
+
Vue-mc collections provide `loading` state. Use `AwContentPlaceholder` for better UX:
|
|
689
|
+
|
|
690
|
+
```markup
|
|
691
|
+
<template>
|
|
692
|
+
<div>
|
|
693
|
+
<AwContentPlaceholder v-if="customers.loading" type="text" :lines="8" />
|
|
694
|
+
|
|
695
|
+
<div v-else>
|
|
696
|
+
<div v-for="customer in customers.models" :key="customer.id">
|
|
697
|
+
{{ customer.name }}
|
|
698
|
+
</div>
|
|
699
|
+
</div>
|
|
700
|
+
</div>
|
|
701
|
+
</template>
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
### Model Loading
|
|
705
|
+
|
|
706
|
+
Models provide `fetching` state. Use form placeholder for form data. When using `AwCard`, place the placeholder inside the card to replace the content:
|
|
707
|
+
|
|
708
|
+
```markup
|
|
709
|
+
<template>
|
|
710
|
+
<AwCard title="Customer Details">
|
|
711
|
+
<AwContentPlaceholder v-if="customer.fetching" type="form" :lines="6" />
|
|
712
|
+
|
|
713
|
+
<AwGrid v-else>
|
|
714
|
+
<!-- Customer data -->
|
|
715
|
+
</AwGrid>
|
|
716
|
+
</AwCard>
|
|
717
|
+
</template>
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
### Complex Content Loading
|
|
721
|
+
|
|
722
|
+
For pages with mixed content types, use multiple placeholders:
|
|
723
|
+
|
|
724
|
+
```markup
|
|
725
|
+
<template>
|
|
726
|
+
<div v-if="loading">
|
|
727
|
+
<AwGrid :col="{ md: 2 }">
|
|
728
|
+
<AwContentPlaceholder type="image" />
|
|
729
|
+
<AwContentPlaceholder type="text" :lines="6" />
|
|
730
|
+
</AwGrid>
|
|
731
|
+
<AwFlow class="mt-4">
|
|
732
|
+
<AwContentPlaceholder type="avatar" />
|
|
733
|
+
<AwContentPlaceholder type="text" :lines="3" />
|
|
734
|
+
</AwFlow>
|
|
735
|
+
</div>
|
|
736
|
+
|
|
737
|
+
<div v-else>
|
|
738
|
+
<!-- Actual content -->
|
|
739
|
+
</div>
|
|
740
|
+
</template>
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
### Custom Loading Flag
|
|
744
|
+
|
|
745
|
+
For manual control:
|
|
746
|
+
|
|
747
|
+
```markup
|
|
748
|
+
<template>
|
|
749
|
+
<AwContentPlaceholder v-if="loading" type="form" :lines="4" />
|
|
750
|
+
|
|
751
|
+
<AwForm v-else url="/api/submit">
|
|
752
|
+
<!-- Form fields -->
|
|
753
|
+
</AwForm>
|
|
754
|
+
</template>
|
|
755
|
+
|
|
756
|
+
<script>
|
|
757
|
+
export default {
|
|
758
|
+
data() {
|
|
759
|
+
return {
|
|
760
|
+
loading: false
|
|
761
|
+
}
|
|
762
|
+
},
|
|
763
|
+
|
|
764
|
+
methods: {
|
|
765
|
+
async loadData() {
|
|
766
|
+
this.loading = true
|
|
767
|
+
try {
|
|
768
|
+
await this.collection.fetch()
|
|
769
|
+
} finally {
|
|
770
|
+
this.loading = false
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
</script>
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
## Best Practices
|
|
779
|
+
|
|
780
|
+
### 1. Use AwTableBuilder Auto-Fetch
|
|
781
|
+
|
|
782
|
+
```markup
|
|
783
|
+
<!-- ✅ GOOD - Let AwTableBuilder fetch -->
|
|
784
|
+
<AwTableBuilder :collection="customers" />
|
|
785
|
+
|
|
786
|
+
<!-- ❌ BAD - Manual fetch not needed -->
|
|
787
|
+
<script>
|
|
788
|
+
async mounted() {
|
|
789
|
+
await this.customers.fetch() // Unnecessary!
|
|
790
|
+
}
|
|
791
|
+
</script>
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
### 2. Always Include shop_uuid
|
|
795
|
+
|
|
796
|
+
```javascript
|
|
797
|
+
// ✅ GOOD
|
|
798
|
+
customers: new Customers([], {
|
|
799
|
+
shop_uuid: this.$route.params.shop_uuid
|
|
800
|
+
})
|
|
801
|
+
|
|
802
|
+
// ❌ BAD
|
|
803
|
+
customers: new Customers([])
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
### 3. Handle Errors
|
|
807
|
+
|
|
808
|
+
```javascript
|
|
809
|
+
// ✅ GOOD
|
|
810
|
+
try {
|
|
811
|
+
await this.model.fetch()
|
|
812
|
+
} catch (error) {
|
|
813
|
+
this.$notify({
|
|
814
|
+
message: 'Failed to load',
|
|
815
|
+
type: 'error'
|
|
816
|
+
})
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// ❌ BAD
|
|
820
|
+
await this.model.fetch() // No error handling
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
### 4. Check isNew() Before Fetching
|
|
824
|
+
|
|
825
|
+
```javascript
|
|
826
|
+
// ✅ GOOD
|
|
827
|
+
if (!this.model.isNew()) {
|
|
828
|
+
await this.model.fetch()
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// ❌ BAD
|
|
832
|
+
await this.model.fetch() // Fails if new model
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
### 5. Clean Up Intervals
|
|
836
|
+
|
|
837
|
+
```javascript
|
|
838
|
+
// ✅ GOOD
|
|
839
|
+
beforeDestroy() {
|
|
840
|
+
clearInterval(this.pollingInterval)
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// ❌ BAD
|
|
844
|
+
// No cleanup - memory leak
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
## See Also
|
|
848
|
+
|
|
849
|
+
- [Best Practices Guide](./best-practices.md) - Data management patterns
|
|
850
|
+
- [Error Handling Guide](./error-handling.md) - Error handling strategies
|
|
851
|
+
- [List Pages Guide](./page-patterns/list-pages.md) - List page patterns
|
|
852
|
+
- [Vue-MC Documentation](../../vue-mc/docs/) - Complete vue-mc API
|