saasufy-components 2.12.1 → 2.14.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/README.md +178 -19
- package/collection-browser.js +16 -16
- package/collection-reducer.js +25 -18
- package/model-input.js +15 -1
- package/model-viewer.js +11 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,41 @@
|
|
|
1
1
|
# Saasufy Components
|
|
2
2
|
Components for Saasufy
|
|
3
3
|
|
|
4
|
+
### Application state
|
|
5
|
+
- [app-router](#app-router)
|
|
6
|
+
- [socket-provider](#socket-provider)
|
|
7
|
+
|
|
8
|
+
### Collections of resources
|
|
9
|
+
- [collection-browser](#collection-browser)
|
|
10
|
+
- [collection-adder](#collection-adder)
|
|
11
|
+
- [collection-deleter](#collection-deleter)
|
|
12
|
+
- [collection-reducer](#collection-reducer)
|
|
13
|
+
|
|
14
|
+
### Single resources
|
|
15
|
+
- [model-input](#model-input)
|
|
16
|
+
- [model-text](#model-text)
|
|
17
|
+
- [model-viewer](#model-viewer)
|
|
18
|
+
|
|
19
|
+
### User input
|
|
20
|
+
- [input-provider](#input-provider)
|
|
21
|
+
|
|
22
|
+
### Authentication
|
|
23
|
+
- [log-in-form](#log-in-form)
|
|
24
|
+
- [log-out](#log-out)
|
|
25
|
+
- [oauth-link](#oauth-link)
|
|
26
|
+
- [oauth-handler](#oauth-handler)
|
|
27
|
+
|
|
28
|
+
### Display groups
|
|
29
|
+
- [render-group](#render-group)
|
|
30
|
+
- [conditional-group](#conditional-group)
|
|
31
|
+
- [if-group](#if-group)
|
|
32
|
+
- [switch-group](#switch-group)
|
|
33
|
+
- [collection-adder-group](#collection-adder-group)
|
|
34
|
+
|
|
35
|
+
### Modal windows
|
|
36
|
+
- [confirm-modal](#confirm-modal)
|
|
37
|
+
- [overlay-modal](#overlay-modal)
|
|
38
|
+
|
|
4
39
|
## Overview
|
|
5
40
|
|
|
6
41
|
Many Saasufy components use a combination of `<template slot="item">` and `<div slot="viewport">`. Both of these are known as `slotted` elements.
|
|
@@ -29,14 +64,17 @@ To use components, you just need to include them into your `.html` file inside y
|
|
|
29
64
|
<script src="https://saasufy.com/node_modules/saasufy-components/model-text.js" type="module" defer></script>
|
|
30
65
|
<script src="https://saasufy.com/node_modules/saasufy-components/model-viewer.js" type="module" defer></script>
|
|
31
66
|
<script src="https://saasufy.com/node_modules/saasufy-components/input-provider.js" type="module" defer></script>
|
|
32
|
-
<script src="https://saasufy.com/node_modules/saasufy-components/confirm-modal.js" type="module" defer></script>
|
|
33
|
-
<script src="https://saasufy.com/node_modules/saasufy-components/render-group.js" type="module" defer></script>
|
|
34
|
-
<script src="https://saasufy.com/node_modules/saasufy-components/conditional-group.js" type="module" defer></script>
|
|
35
|
-
<script src="https://saasufy.com/node_modules/saasufy-components/collection-adder-group.js" type="module" defer></script>
|
|
36
67
|
<script src="https://saasufy.com/node_modules/saasufy-components/log-in-form.js" type="module" defer></script>
|
|
37
68
|
<script src="https://saasufy.com/node_modules/saasufy-components/log-out.js" type="module" defer></script>
|
|
38
69
|
<script src="https://saasufy.com/node_modules/saasufy-components/oauth-link.js" type="module" defer></script>
|
|
39
70
|
<script src="https://saasufy.com/node_modules/saasufy-components/oauth-handler.js" type="module" defer></script>
|
|
71
|
+
<script src="https://saasufy.com/node_modules/saasufy-components/render-group.js" type="module" defer></script>
|
|
72
|
+
<script src="https://saasufy.com/node_modules/saasufy-components/conditional-group.js" type="module" defer></script>
|
|
73
|
+
<script src="https://saasufy.com/node_modules/saasufy-components/if-group.js" type="module" defer></script>
|
|
74
|
+
<script src="https://saasufy.com/node_modules/saasufy-components/switch-group.js" type="module" defer></script>
|
|
75
|
+
<script src="https://saasufy.com/node_modules/saasufy-components/collection-adder-group.js" type="module" defer></script>
|
|
76
|
+
<script src="https://saasufy.com/node_modules/saasufy-components/confirm-modal.js" type="module" defer></script>
|
|
77
|
+
<script src="https://saasufy.com/node_modules/saasufy-components/overlay-modal.js" type="module" defer></script>
|
|
40
78
|
```
|
|
41
79
|
|
|
42
80
|
You should only add the script tags for the components which your page uses to avoid wasting bandwidth and unnecessarily delaying page load for your users.
|
|
@@ -144,40 +182,152 @@ Can also perform basic CRUD operations such as deleting or creating records by l
|
|
|
144
182
|
</div>
|
|
145
183
|
</template>
|
|
146
184
|
|
|
147
|
-
<
|
|
185
|
+
<template slot="loader">
|
|
186
|
+
<div class="loading-spinner-container">
|
|
187
|
+
<div class="spinning">↻</div>
|
|
188
|
+
</div>
|
|
189
|
+
</template>
|
|
148
190
|
|
|
149
|
-
<div slot="
|
|
150
|
-
<div class="spinning">↻</div>
|
|
151
|
-
</div>
|
|
191
|
+
<div slot="viewport" class="chat-viewport"></div>
|
|
152
192
|
</collection-browser>
|
|
153
193
|
```
|
|
154
194
|
|
|
155
195
|
**Attributes**
|
|
156
196
|
|
|
157
|
-
- `collection-type` (required): Specifies the type of collection to
|
|
197
|
+
- `collection-type` (required): Specifies the type of collection to display. This should match a `Model` available in your Saasufy service.
|
|
158
198
|
- `collection-fields` (required): A comma-separated list of fields from the `Model` that you want to display or use. This lets you pick specific pieces of data from your collection to work with.
|
|
159
199
|
- `collection-view` (required): Determines the view of the collection. This should match one of the `Views` defined in your Saasufy service under that specific `Model`.
|
|
160
200
|
- `collection-view-params` (required): Parameters for the view, specified as comma-separated key-value pairs (e.g., key1=value1,key2=value2). These parameters can customize the behavior of the collection view. The keys must match `paramFields` specified in your Saasufy service under the relevant `View`.
|
|
161
201
|
- `collection-page-size`: Sets how many items from the collection are displayed at once. This is useful for pagination, allowing users to navigate through large sets of data in chunks.
|
|
162
202
|
- `collection-page-offset`: Indicates the current page offset in the collection's data. It’s like telling the browser which page of data you want to display initially.
|
|
163
203
|
- `type-alias`: Allows you to provide an alternative name for your `Model` to use when injecting values inside the template. This is useful for situations where you may have multiple `collection-browser` elements and/or `model-viewer` elements of the same type nested inside each other and want to avoid `Model` name clashes in the nested template definitions. For example, if the `type-alias` in the snippet above was set to `SubChat`, then `{{Chat.message}}` would become `{{SubChat.message}}`.
|
|
164
|
-
- `hide-error-logs`: A flag
|
|
204
|
+
- `hide-error-logs`: A flag which, when present, suppresses error logs from being printed to the console.
|
|
165
205
|
- `max-show-loader`: If this attribute is present, your slotted `loader` element will be shown as often as possible; this includes situations where the collection is merely refreshing itself. It is disabled by default.
|
|
166
206
|
|
|
167
207
|
### collection-adder
|
|
168
208
|
|
|
169
209
|
A form component for inserting data into collections.
|
|
170
210
|
|
|
211
|
+
**Example usage**
|
|
212
|
+
|
|
213
|
+
```html
|
|
214
|
+
<collection-adder
|
|
215
|
+
collection-type="Product"
|
|
216
|
+
collection-fields="name,brand,qty"
|
|
217
|
+
model-values="categoryName=electronics"
|
|
218
|
+
submit-button-label="Create"
|
|
219
|
+
trim-spaces
|
|
220
|
+
></collection-adder>
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**Attributes**
|
|
224
|
+
|
|
225
|
+
- `collection-type` (required): Specifies the type of collection to add the resource to when the form is submitted. This should match a `Model` available in your Saasufy service.
|
|
226
|
+
- `collection-fields`: A comma-separated list of fields from the `Model` to display as input elements inside the form for the user to fill in. Each field name in this list can optionally be followed by an input element type after a `:` character. For example `collection-fields="qty:number, size:select(small,medium,large)` will create one input element with `type="number"` and one with `type=select` with options `small`, `medium` or `large`. To make a select field optional, you can prefix the list of options with a comma; e.g. `size:select(,small,medium,large)`.
|
|
227
|
+
- `model-values`: An optional list of key-value pairs in the format `field1=value1,field2=value2` to add to the newly created resource alongside the values collected from the user via the form.
|
|
228
|
+
- `submit-button-label`: Text to display on the submit button. If not specified, defaults to `Submit`.
|
|
229
|
+
- `hide-submit-button`: Adding this attribute will hide the submit button from the form.
|
|
230
|
+
- `success-message`: A message to show the user if the resource has been successfully added to the collection after submitting the form.
|
|
231
|
+
- `autocapitalize`: Can be set to `off` to disable auto-capitalization of the first input character on mobile devices.
|
|
232
|
+
- `autocorrect`: Can be set to `off` to disable auto-correction of input on mobile devices.
|
|
233
|
+
- `trim-spaces`: If this attribute exists on the element, then leading and trailing spaces will be trimmed from each input element's value before submitting the form.
|
|
234
|
+
|
|
171
235
|
### collection-deleter
|
|
172
236
|
|
|
173
237
|
A component which can be placed anywhere inside a `collection-browser` component to delete a specific item from a collection as a result of a user action (e.g. on click).
|
|
174
238
|
It supports either immediate deletion or deletion upon confirmation; in the latter case, the parent `collection-browser` must have a `confirm-modal` component slotted into its `modal` slot.
|
|
175
239
|
|
|
240
|
+
**Example usage**
|
|
241
|
+
|
|
242
|
+
```html
|
|
243
|
+
<!-- Must be placed somewhere inside a collection-browser component, typically inside the slotted item template. -->
|
|
244
|
+
<collection-deleter model-id="{{Product.id}}" confirm-message="Are you sure you want to delete the {{Product.name}} product?" onclick="confirmDeleteItem()">✕</collection-deleter>
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Attributes**
|
|
248
|
+
|
|
249
|
+
- `model-id` (required): Specifies the ID of the resource to delete from the parent collection when this component is activated. This can be achieved by invoking either the `deleteItem()` or `confirmDeleteItem()` function from inside an event handler. The example above shows how to achieve deletion via the `onclick` event. You can either invoke `deleteItem()` to delete the resource immediately or you can invoke `confirmDeleteItem()` to require additional confirmation prior to deletion.
|
|
250
|
+
- `confirm-message`: The confirmation message to show the user when this component's `confirmDeleteItem()` function is invoked,
|
|
251
|
+
|
|
252
|
+
If `confirmDeleteItem()` is used, then the parent `collection-browser` must have a `confirm-modal` element slotted into it as shown here:
|
|
253
|
+
|
|
254
|
+
```html
|
|
255
|
+
<collection-browser
|
|
256
|
+
collection-type="Product"
|
|
257
|
+
collection-fields="name,qty"
|
|
258
|
+
collection-view="alphabeticalView"
|
|
259
|
+
collection-view-params=""
|
|
260
|
+
collection-page-size="50"
|
|
261
|
+
>
|
|
262
|
+
<template slot="item">
|
|
263
|
+
<div>
|
|
264
|
+
<div class="chat-message">{{Product.name}}</div>
|
|
265
|
+
<collection-deleter model-id="{{Product.id}}" confirm-message="Are you sure you want to delete the {{Product.name}} product?" onclick="confirmDeleteItem()">✕</collection-deleter>
|
|
266
|
+
</div>
|
|
267
|
+
</template>
|
|
268
|
+
|
|
269
|
+
<div slot="viewport" class="chat-viewport"></div>
|
|
270
|
+
|
|
271
|
+
<!-- The confirm-modal element must be specified here with slot="modal" to prompt the user for confirmation -->
|
|
272
|
+
<confirm-modal slot="modal" title="Delete confirmation" message="" confirm-button-label="Delete"></confirm-modal>
|
|
273
|
+
</collection-browser>
|
|
274
|
+
```
|
|
275
|
+
|
|
176
276
|
### collection-reducer
|
|
177
277
|
|
|
178
278
|
Similar to the `collection-browser` component but designed to render collections in combined format. For example, to combine values from multiple records into a single item.
|
|
179
279
|
A common use case is to extract and join values to pass to other child components via their attributes.
|
|
180
280
|
|
|
281
|
+
**Example usage**
|
|
282
|
+
|
|
283
|
+
```html
|
|
284
|
+
<!--
|
|
285
|
+
Here, the collection-reducer fetches all available categories and joins their name fields
|
|
286
|
+
to use as options for a model-input select element.
|
|
287
|
+
This allows the user to choose among available category names to update the
|
|
288
|
+
current product's categoryName field.
|
|
289
|
+
You should assume that {{productId}} comes from a parent component (e.g. app-router).
|
|
290
|
+
-->
|
|
291
|
+
<collection-reducer
|
|
292
|
+
collection-type="Category"
|
|
293
|
+
collection-fields="name"
|
|
294
|
+
collection-view="alphabeticalView"
|
|
295
|
+
collection-page-size="100"
|
|
296
|
+
collection-view-params=""
|
|
297
|
+
>
|
|
298
|
+
<template slot="item">
|
|
299
|
+
<div class="form">
|
|
300
|
+
<label class="label-container">
|
|
301
|
+
<div>Product category</div>
|
|
302
|
+
<model-input
|
|
303
|
+
type="select"
|
|
304
|
+
options="{{joinFields(Category, 'name')}}"
|
|
305
|
+
model-type="Product"
|
|
306
|
+
default-value="accountId"
|
|
307
|
+
model-id="{{productId}}"
|
|
308
|
+
model-field="categoryName"
|
|
309
|
+
></model-input>
|
|
310
|
+
</label>
|
|
311
|
+
</div>
|
|
312
|
+
</template>
|
|
313
|
+
<div slot="viewport"></div>
|
|
314
|
+
</collection-reducer>
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Unlike with `collection-browser` or `model-viewer`, the template variable references not a single model instance but an array of model instances.
|
|
318
|
+
In the example above, the name of the first element can be accessed with `{{Category[0].name}}`.
|
|
319
|
+
|
|
320
|
+
**Attributes**
|
|
321
|
+
|
|
322
|
+
- `collection-type` (required): Specifies the type of collection to use. This should match a `Model` available in your Saasufy service.
|
|
323
|
+
- `collection-fields` (required): A comma-separated list of fields from the `Model` that you want to use. This lets you pick specific pieces of data from your collection to work with.
|
|
324
|
+
- `collection-view` (required): Determines the view of the collection. This should match one of the `Views` defined in your Saasufy service under that specific `Model`.
|
|
325
|
+
- `collection-view-params` (required): Parameters for the view, specified as comma-separated key-value pairs (e.g., key1=value1,key2=value2). These parameters can customize the behavior of the collection view. The keys must match `paramFields` specified in your Saasufy service under the relevant `View`.
|
|
326
|
+
- `collection-page-size`: Sets how many items from the collection are displayed at once. This is useful for pagination, allowing users to navigate through large sets of data in chunks.
|
|
327
|
+
- `collection-page-offset`: Indicates the current page offset in the collection's data. It’s like telling the browser which page of data you want to display initially.
|
|
328
|
+
- `type-alias`: Allows you to provide an alternative name for your `Model` to use when injecting values inside the template. This is useful for situations where you may have multiple `collection-browser` elements and/or `model-viewer` elements of the same type nested inside each other and want to avoid `Model` name clashes in the nested template definitions. For example, if the `type-alias` in the snippet above was set to `SubChat`, then `{{Chat.message}}` would become `{{SubChat.message}}`.
|
|
329
|
+
- `hide-error-logs`: A flag which, when present, suppresses error logs from being printed to the console.
|
|
330
|
+
|
|
181
331
|
### model-input
|
|
182
332
|
|
|
183
333
|
Used for displaying and editing a single field of a model instance in real time.
|
|
@@ -211,18 +361,24 @@ A component which can be placed inside a `socket-provider` to deauthenticate the
|
|
|
211
361
|
|
|
212
362
|
Example usage: `<log-out onclick="logOut()"><a href="javascript:void(0)">Log out</a></log-out>`
|
|
213
363
|
|
|
214
|
-
###
|
|
364
|
+
### oauth-link
|
|
215
365
|
|
|
216
|
-
A
|
|
366
|
+
A link to initiate an OAuth flow to authenticate a user as part of log in. Can support a range of different OAuth providers.
|
|
217
367
|
|
|
218
|
-
###
|
|
368
|
+
### oauth-handler
|
|
219
369
|
|
|
220
|
-
|
|
370
|
+
This component handles the final stage of OAuth and then redirects the user to the relevant page/URL if successful.
|
|
371
|
+
When using an OAuth provider, the callback URL which you register with the provider must lead the user back to a page which contains this `oauth-handler` component.
|
|
221
372
|
|
|
222
373
|
### render-group
|
|
223
374
|
|
|
224
375
|
A component which can be used to guarantee that certain children components are always rendered at the same time (once they are loaded) to provide a smooth user experience.
|
|
225
376
|
|
|
377
|
+
### conditional-group
|
|
378
|
+
|
|
379
|
+
A component which exposes a `show-content` property which can be set to true or false to show or hide its content.
|
|
380
|
+
It's intended to be placed inside a `model-viewer`, `collection-browser` or `collection-reducer` component such that the true/false value of the `show-content` attribute can be computed using a template `{{expression}}` placeholder. Unlike `if-group`, this component does not require a template, however, the trade-off is that the slotted content will be pre-processed by the browser even when the expression is false. This may not be suitable if you want to (for example) render child images conditionally because the browser will preload images based on their `src` attributes, even if they are not shown.
|
|
381
|
+
|
|
226
382
|
### if-group
|
|
227
383
|
|
|
228
384
|
A component which exposes a `show-content` property which can be set to true or false to show or hide its content.
|
|
@@ -233,11 +389,14 @@ It's intended to be placed inside a `model-viewer`, `collection-browser` or `col
|
|
|
233
389
|
A component which exposes a `show-cases` property which can be set to key-value pairs in the format `key1=true,key2=false`. It can be used to conditionally display multiple slotted elements based on multiple conditions. It helps to keep HTML clean when the conditions are complex.
|
|
234
390
|
This element is intended to be placed inside a `model-viewer`, `collection-browser` or `collection-reducer` component such that the true/false values of the `show-cases` attribute can be computed using template `{{expression}}` placeholders. This component requires one or more templates and a viewport to be slotted in. The content of the templates will not be processed unless the `show-cases` condition is met. All templates must have a `slot="content"` attribute and a name attribute in the format `name="key1"` where the value `key1` corresponds to the key specified inside the `show-cases` attribute of the `switch-group`.
|
|
235
391
|
|
|
236
|
-
### conditional-group
|
|
237
|
-
|
|
238
|
-
A component which exposes a `show-content` property which can be set to true or false to show or hide its content.
|
|
239
|
-
It's intended to be placed inside a `model-viewer`, `collection-browser` or `collection-reducer` component such that the true/false value of the `show-content` attribute can be computed using a template `{{expression}}` placeholder. Unlike `if-group`, this component does not require a template, however, the trade-off is that the slotted content will be pre-processed by the browser even when the expression is false. This may not be suitable if you want to (for example) render child images conditionally because the browser will preload images based on their `src` attributes, even if they are not shown.
|
|
240
|
-
|
|
241
392
|
### collection-adder-group
|
|
242
393
|
|
|
243
394
|
A component which can be used to group together multiple `collection-adder` components. It can be used to insert multiple records into a collection via a single button click.
|
|
395
|
+
|
|
396
|
+
### confirm-modal
|
|
397
|
+
|
|
398
|
+
A modal component to prompt the user for confirmation before performing sensitive operations.
|
|
399
|
+
|
|
400
|
+
### overlay-modal
|
|
401
|
+
|
|
402
|
+
A general purpose modal component.
|
package/collection-browser.js
CHANGED
|
@@ -9,6 +9,7 @@ class CollectionBrowser extends SocketConsumer {
|
|
|
9
9
|
super();
|
|
10
10
|
this.isReady = false;
|
|
11
11
|
this.isStale = true;
|
|
12
|
+
this.activeLoader = null;
|
|
12
13
|
this.attachShadow({ mode: 'open' });
|
|
13
14
|
|
|
14
15
|
this.handleShowModalEvent = (event) => {
|
|
@@ -46,7 +47,7 @@ class CollectionBrowser extends SocketConsumer {
|
|
|
46
47
|
this.goToPage((event.detail || {}).offset);
|
|
47
48
|
};
|
|
48
49
|
|
|
49
|
-
this.handleSlotChangeEvent = () => {
|
|
50
|
+
this.handleSlotChangeEvent = (event) => {
|
|
50
51
|
this.renderList();
|
|
51
52
|
};
|
|
52
53
|
}
|
|
@@ -54,6 +55,7 @@ class CollectionBrowser extends SocketConsumer {
|
|
|
54
55
|
connectedCallback() {
|
|
55
56
|
this.isReady = true;
|
|
56
57
|
this.socket = this.getSocket();
|
|
58
|
+
this.activeLoader = null;
|
|
57
59
|
this.shadowRoot.addEventListener('slotchange', this.handleSlotChangeEvent);
|
|
58
60
|
this.addEventListener('showModal', this.handleShowModalEvent);
|
|
59
61
|
this.addEventListener('crudCreate', this.handleCRUDCreateEvent);
|
|
@@ -162,23 +164,26 @@ class CollectionBrowser extends SocketConsumer {
|
|
|
162
164
|
|
|
163
165
|
renderList() {
|
|
164
166
|
let viewportSlot = this.shadowRoot.querySelector('slot[name="viewport"]');
|
|
167
|
+
|
|
168
|
+
let viewportNode = viewportSlot.assignedNodes()[0];
|
|
169
|
+
if (!viewportNode) return;
|
|
170
|
+
|
|
165
171
|
let loaderSlot = this.shadowRoot.querySelector('slot[name="loader"]');
|
|
166
|
-
let
|
|
172
|
+
let loaderNode = loaderSlot.assignedNodes()[0];
|
|
167
173
|
|
|
168
174
|
if (!this.collection || !this.collection.isLoaded) {
|
|
169
|
-
if (
|
|
170
|
-
|
|
171
|
-
|
|
175
|
+
if (
|
|
176
|
+
loaderNode &&
|
|
177
|
+
this.activeLoader !== loaderNode &&
|
|
178
|
+
(this.isStale || this.hasAttribute('max-show-loader'))
|
|
179
|
+
) {
|
|
180
|
+
this.activeLoader = loaderNode;
|
|
181
|
+
viewportNode.innerHTML = loaderNode.innerHTML;
|
|
172
182
|
}
|
|
173
183
|
return;
|
|
174
184
|
}
|
|
175
185
|
this.isStale = false;
|
|
176
|
-
|
|
177
|
-
loaderSlot.classList.add('hidden');
|
|
178
|
-
viewportSlot.classList.remove('hidden');
|
|
179
|
-
|
|
180
|
-
let viewportNode = viewportSlot.assignedNodes()[0];
|
|
181
|
-
if (!viewportNode) return;
|
|
186
|
+
this.activeLoader = null;
|
|
182
187
|
|
|
183
188
|
let itemTemplate = this.shadowRoot.querySelector('slot[name="item"]').assignedNodes()[0];
|
|
184
189
|
let noItemTemplate = this.shadowRoot.querySelector('slot[name="no-item"]').assignedNodes()[0];
|
|
@@ -241,11 +246,6 @@ class CollectionBrowser extends SocketConsumer {
|
|
|
241
246
|
});
|
|
242
247
|
|
|
243
248
|
this.shadowRoot.innerHTML = `
|
|
244
|
-
<style>
|
|
245
|
-
.hidden {
|
|
246
|
-
display: none;
|
|
247
|
-
}
|
|
248
|
-
</style>
|
|
249
249
|
<slot name="loader"></slot>
|
|
250
250
|
<slot name="item"></slot>
|
|
251
251
|
<slot name="no-item"></slot>
|
package/collection-reducer.js
CHANGED
|
@@ -7,6 +7,9 @@ const DEFAULT_RELOAD_DELAY = 0;
|
|
|
7
7
|
class CollectionReducer extends SocketConsumer {
|
|
8
8
|
constructor() {
|
|
9
9
|
super();
|
|
10
|
+
this.isReady = false;
|
|
11
|
+
this.isStale = true;
|
|
12
|
+
this.activeLoader = null;
|
|
10
13
|
this.attachShadow({ mode: 'open' });
|
|
11
14
|
|
|
12
15
|
this.handleGoToPreviousPageEvent = () => {
|
|
@@ -24,12 +27,12 @@ class CollectionReducer extends SocketConsumer {
|
|
|
24
27
|
this.handleSlotChangeEvent = () => {
|
|
25
28
|
this.renderReduction();
|
|
26
29
|
};
|
|
27
|
-
this.isReady = false;
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
connectedCallback() {
|
|
31
33
|
this.isReady = true;
|
|
32
34
|
this.socket = this.getSocket();
|
|
35
|
+
this.activeLoader = null;
|
|
33
36
|
this.shadowRoot.addEventListener('slotchange', this.handleSlotChangeEvent);
|
|
34
37
|
this.addEventListener('goToPreviousPage', this.handleGoToPreviousPageEvent);
|
|
35
38
|
this.addEventListener('goToNextPage', this.handleGoToNextPageEvent);
|
|
@@ -47,12 +50,14 @@ class CollectionReducer extends SocketConsumer {
|
|
|
47
50
|
|
|
48
51
|
goToPreviousPage() {
|
|
49
52
|
if (!this.collection) return;
|
|
53
|
+
this.isStale = true;
|
|
50
54
|
this.collection.fetchPreviousPage();
|
|
51
55
|
this.setAttribute('collection-page-offset', this.collection.meta.pageOffset);
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
goToNextPage() {
|
|
55
59
|
if (!this.collection) return;
|
|
60
|
+
this.isStale = true;
|
|
56
61
|
this.collection.fetchNextPage();
|
|
57
62
|
this.setAttribute('collection-page-offset', this.collection.meta.pageOffset);
|
|
58
63
|
}
|
|
@@ -69,6 +74,7 @@ class CollectionReducer extends SocketConsumer {
|
|
|
69
74
|
'collection-view-params',
|
|
70
75
|
'collection-page-size',
|
|
71
76
|
'collection-page-offset',
|
|
77
|
+
'max-show-loader',
|
|
72
78
|
'type-alias',
|
|
73
79
|
'hide-error-logs'
|
|
74
80
|
];
|
|
@@ -79,6 +85,7 @@ class CollectionReducer extends SocketConsumer {
|
|
|
79
85
|
if (this.collection && name === 'collection-page-offset') {
|
|
80
86
|
let newOffset = Number(newValue);
|
|
81
87
|
if (newOffset !== this.collection.meta.pageOffset) {
|
|
88
|
+
this.isStale = true;
|
|
82
89
|
this.collection.fetchPage(newOffset);
|
|
83
90
|
}
|
|
84
91
|
this.updatePageNumberElements();
|
|
@@ -128,22 +135,26 @@ class CollectionReducer extends SocketConsumer {
|
|
|
128
135
|
|
|
129
136
|
renderReduction() {
|
|
130
137
|
let viewportSlot = this.shadowRoot.querySelector('slot[name="viewport"]');
|
|
138
|
+
|
|
139
|
+
let viewportNode = viewportSlot.assignedNodes()[0];
|
|
140
|
+
if (!viewportNode) return;
|
|
141
|
+
|
|
131
142
|
let loaderSlot = this.shadowRoot.querySelector('slot[name="loader"]');
|
|
132
|
-
let
|
|
143
|
+
let loaderNode = loaderSlot.assignedNodes()[0];
|
|
133
144
|
|
|
134
145
|
if (!this.collection || !this.collection.isLoaded) {
|
|
135
|
-
if (
|
|
136
|
-
|
|
137
|
-
|
|
146
|
+
if (
|
|
147
|
+
loaderNode &&
|
|
148
|
+
this.activeLoader !== loaderNode &&
|
|
149
|
+
(this.isStale || this.hasAttribute('max-show-loader'))
|
|
150
|
+
) {
|
|
151
|
+
this.activeLoader = loaderNode;
|
|
152
|
+
viewportNode.innerHTML = loaderNode.innerHTML;
|
|
138
153
|
}
|
|
139
154
|
return;
|
|
140
155
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
viewportSlot.classList.remove('hidden');
|
|
144
|
-
|
|
145
|
-
let viewportNode = viewportSlot.assignedNodes()[0];
|
|
146
|
-
if (!viewportNode) return;
|
|
156
|
+
this.isStale = false;
|
|
157
|
+
this.activeLoader = null;
|
|
147
158
|
|
|
148
159
|
let itemTemplate = this.shadowRoot.querySelector('slot[name="item"]').assignedNodes()[0];
|
|
149
160
|
let noItemTemplate = this.shadowRoot.querySelector('slot[name="no-item"]').assignedNodes()[0];
|
|
@@ -174,10 +185,11 @@ class CollectionReducer extends SocketConsumer {
|
|
|
174
185
|
);
|
|
175
186
|
let viewParams = Object.fromEntries(
|
|
176
187
|
collectionViewParams.split(',')
|
|
177
|
-
|
|
178
|
-
|
|
188
|
+
.map(pair => pair.split('='))
|
|
189
|
+
.filter(([field]) => field)
|
|
179
190
|
);
|
|
180
191
|
if (this.collection) this.collection.destroy();
|
|
192
|
+
this.isStale = true;
|
|
181
193
|
|
|
182
194
|
this.collection = new AGCollection({
|
|
183
195
|
socket: this.socket,
|
|
@@ -191,11 +203,6 @@ class CollectionReducer extends SocketConsumer {
|
|
|
191
203
|
});
|
|
192
204
|
|
|
193
205
|
this.shadowRoot.innerHTML = `
|
|
194
|
-
<style>
|
|
195
|
-
.hidden {
|
|
196
|
-
display: none;
|
|
197
|
-
}
|
|
198
|
-
</style>
|
|
199
206
|
<slot name="loader"></slot>
|
|
200
207
|
<slot name="item"></slot>
|
|
201
208
|
<slot name="no-item"></slot>
|
package/model-input.js
CHANGED
|
@@ -61,6 +61,14 @@ class ModelInput extends SocketConsumer {
|
|
|
61
61
|
|
|
62
62
|
set value(newValue) {
|
|
63
63
|
if (this.inputElement) {
|
|
64
|
+
if (this.inputElement.type === 'checkbox') {
|
|
65
|
+
let oldValue = this.inputElement.checked;
|
|
66
|
+
this.inputElement.checked = newValue;
|
|
67
|
+
if (oldValue !== newValue) {
|
|
68
|
+
this.triggerChange();
|
|
69
|
+
}
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
64
72
|
let oldValue = this.inputElement.value;
|
|
65
73
|
this.inputElement.value = newValue;
|
|
66
74
|
if (oldValue !== newValue) {
|
|
@@ -70,7 +78,13 @@ class ModelInput extends SocketConsumer {
|
|
|
70
78
|
}
|
|
71
79
|
|
|
72
80
|
get value() {
|
|
73
|
-
|
|
81
|
+
if (!this.inputElement) {
|
|
82
|
+
return '';
|
|
83
|
+
}
|
|
84
|
+
if (this.inputElement.type === 'checkbox') {
|
|
85
|
+
return this.inputElement.checked;
|
|
86
|
+
}
|
|
87
|
+
return this.inputElement.value;
|
|
74
88
|
}
|
|
75
89
|
|
|
76
90
|
render() {
|
package/model-viewer.js
CHANGED
|
@@ -11,11 +11,13 @@ class ModelViewer extends SocketConsumer {
|
|
|
11
11
|
this.renderItem();
|
|
12
12
|
};
|
|
13
13
|
this.isReady = false;
|
|
14
|
+
this.activeLoader = null;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
connectedCallback() {
|
|
17
18
|
this.isReady = true;
|
|
18
19
|
this.socket = this.getSocket();
|
|
20
|
+
this.activeLoader = null;
|
|
19
21
|
this.shadowRoot.addEventListener('slotchange', this.handleSlotChangeEvent);
|
|
20
22
|
this.render();
|
|
21
23
|
}
|
|
@@ -53,22 +55,22 @@ class ModelViewer extends SocketConsumer {
|
|
|
53
55
|
|
|
54
56
|
renderItem() {
|
|
55
57
|
let viewportSlot = this.shadowRoot.querySelector('slot[name="viewport"]');
|
|
58
|
+
|
|
59
|
+
let viewportNode = viewportSlot.assignedNodes()[0];
|
|
60
|
+
if (!viewportNode) return;
|
|
61
|
+
|
|
56
62
|
let loaderSlot = this.shadowRoot.querySelector('slot[name="loader"]');
|
|
57
|
-
let
|
|
63
|
+
let loaderNode = loaderSlot.assignedNodes()[0];
|
|
58
64
|
|
|
59
65
|
if (!this.model || !this.model.isLoaded) {
|
|
60
|
-
if (
|
|
61
|
-
|
|
62
|
-
|
|
66
|
+
if (loaderNode && this.activeLoader !== loaderNode) {
|
|
67
|
+
this.activeLoader = loaderNode;
|
|
68
|
+
viewportNode.innerHTML = loaderNode.innerHTML;
|
|
63
69
|
}
|
|
64
70
|
return;
|
|
65
71
|
}
|
|
66
72
|
|
|
67
|
-
|
|
68
|
-
viewportSlot.classList.remove('hidden');
|
|
69
|
-
|
|
70
|
-
let viewportNode = viewportSlot.assignedNodes()[0];
|
|
71
|
-
if (!viewportNode) return;
|
|
73
|
+
this.activeLoader = null;
|
|
72
74
|
|
|
73
75
|
let itemTemplate = this.shadowRoot.querySelector('slot[name="item"]').assignedNodes()[0];
|
|
74
76
|
let modelValue = this.model.value;
|
|
@@ -130,11 +132,6 @@ class ModelViewer extends SocketConsumer {
|
|
|
130
132
|
}
|
|
131
133
|
|
|
132
134
|
this.shadowRoot.innerHTML = `
|
|
133
|
-
<style>
|
|
134
|
-
.hidden {
|
|
135
|
-
display: none;
|
|
136
|
-
}
|
|
137
|
-
</style>
|
|
138
135
|
<slot name="loader"></slot>
|
|
139
136
|
<slot name="item"></slot>
|
|
140
137
|
<slot name="no-item"></slot>
|