saasufy-components 1.13.0 → 1.14.1
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 +15 -4
- package/app-router.js +1 -0
- package/collection-adder.js +1 -0
- package/collection-browser.js +38 -4
- package/collection-deleter.js +43 -0
- package/collection-reducer.js +23 -2
- package/confirm-modal.js +1 -0
- package/log-in-form.js +1 -0
- package/log-out.js +9 -0
- package/model-input.js +6 -6
- package/model-text.js +5 -0
- package/model-viewer.js +23 -2
- package/package.json +1 -1
- package/render-group.js +10 -12
package/README.md
CHANGED
|
@@ -12,16 +12,21 @@ Coming soon.
|
|
|
12
12
|
A top level component which connects to your Saasufy service and inside which you can place other components which depends on Saasufy data.
|
|
13
13
|
A Saasufy component which integrates with data from Saasufy is known as a `socket-consumer` and must always be placed inside a `socket-provider` element (although it does not have to be a direct child).
|
|
14
14
|
|
|
15
|
-
### collection-adder
|
|
16
|
-
|
|
17
|
-
A form component for inserting data into collections.
|
|
18
|
-
|
|
19
15
|
### collection-browser
|
|
20
16
|
|
|
21
17
|
Used for rendering collections as lists, tables or other sequences based on a specific view using a template.
|
|
22
18
|
Supports pagination by allowing you to specify custom buttons or links to navigate between pages.
|
|
23
19
|
Can also perform basic CRUD operations such as deleting or creating records by listening for events from child components.
|
|
24
20
|
|
|
21
|
+
### collection-adder
|
|
22
|
+
|
|
23
|
+
A form component for inserting data into collections.
|
|
24
|
+
|
|
25
|
+
### collection-deleter
|
|
26
|
+
|
|
27
|
+
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).
|
|
28
|
+
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.
|
|
29
|
+
|
|
25
30
|
### collection-reducer
|
|
26
31
|
|
|
27
32
|
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.
|
|
@@ -54,6 +59,12 @@ Once a user is authenticated, they will be able to access restricted data (as sp
|
|
|
54
59
|
It's also possible to specify a `success-location-hash` attribute to trigger a client-side redirect upon successful authentication.
|
|
55
60
|
The change in the location hash can then be detected by an `app-router` to switch to a different page upon successful login.
|
|
56
61
|
|
|
62
|
+
### log-out
|
|
63
|
+
|
|
64
|
+
A component which can be placed inside a `socket-provider` to deauthenticate the socket (e.g. on click).
|
|
65
|
+
|
|
66
|
+
Example usage: `<log-out onclick="logOut()"><a href="javascript:void(0)">Log out</a></log-out>`
|
|
67
|
+
|
|
57
68
|
### confirm-modal
|
|
58
69
|
|
|
59
70
|
A modal component to prompt the user for confirmation before performing sensitive operations.
|
package/app-router.js
CHANGED
package/collection-adder.js
CHANGED
|
@@ -15,6 +15,7 @@ class CollectionAdder extends SocketConsumer {
|
|
|
15
15
|
this.fieldPartsRegExp = /("[^"]*"|'[^']*'|\([^)]*\)|[^,()"']+)+/g;
|
|
16
16
|
this.inputTypeWithParamsRegExp = /^([^()]*)(\(([^)]*)\))?/;
|
|
17
17
|
this.quotedContentRegExp = /^\s*["']?(.*?)["']?\s*$/;
|
|
18
|
+
this.isReady = false;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
connectedCallback() {
|
package/collection-browser.js
CHANGED
|
@@ -7,34 +7,42 @@ const DEFAULT_RELOAD_DELAY = 0;
|
|
|
7
7
|
class CollectionBrowser extends SocketConsumer {
|
|
8
8
|
constructor() {
|
|
9
9
|
super();
|
|
10
|
+
this.isReady = false;
|
|
11
|
+
this.isStale = true;
|
|
10
12
|
this.attachShadow({ mode: 'open' });
|
|
11
13
|
|
|
12
14
|
this.handleShowModalEvent = (event) => {
|
|
15
|
+
event.stopPropagation();
|
|
13
16
|
let modal = this.shadowRoot.querySelector('slot[name="modal"]').assignedNodes()[0];
|
|
14
17
|
modal.show(event.detail.message, event.detail.callback);
|
|
15
18
|
};
|
|
16
19
|
|
|
17
20
|
this.handleCRUDCreateEvent = (event) => {
|
|
21
|
+
event.stopPropagation();
|
|
18
22
|
if (this.collection) {
|
|
19
23
|
this.collection.create(event.detail);
|
|
20
24
|
}
|
|
21
25
|
};
|
|
22
26
|
|
|
23
27
|
this.handleCRUDDeleteEvent = (event) => {
|
|
28
|
+
event.stopPropagation();
|
|
24
29
|
if (this.collection) {
|
|
25
30
|
this.collection.delete(event.detail);
|
|
26
31
|
}
|
|
27
32
|
};
|
|
28
33
|
|
|
29
|
-
this.handleGoToPreviousPageEvent = () => {
|
|
34
|
+
this.handleGoToPreviousPageEvent = (event) => {
|
|
35
|
+
event.stopPropagation();
|
|
30
36
|
this.goToPreviousPage();
|
|
31
37
|
};
|
|
32
38
|
|
|
33
|
-
this.handleGoToNextPageEvent = () => {
|
|
39
|
+
this.handleGoToNextPageEvent = (event) => {
|
|
40
|
+
event.stopPropagation();
|
|
34
41
|
this.goToNextPage();
|
|
35
42
|
};
|
|
36
43
|
|
|
37
44
|
this.handleGoToPageEvent = (event) => {
|
|
45
|
+
event.stopPropagation();
|
|
38
46
|
this.goToPage((event.detail || {}).offset);
|
|
39
47
|
};
|
|
40
48
|
|
|
@@ -69,12 +77,14 @@ class CollectionBrowser extends SocketConsumer {
|
|
|
69
77
|
|
|
70
78
|
goToPreviousPage() {
|
|
71
79
|
if (!this.collection) return;
|
|
80
|
+
this.isStale = true;
|
|
72
81
|
this.collection.fetchPreviousPage();
|
|
73
82
|
this.setAttribute('collection-page-offset', this.collection.meta.pageOffset);
|
|
74
83
|
}
|
|
75
84
|
|
|
76
85
|
goToNextPage() {
|
|
77
86
|
if (!this.collection) return;
|
|
87
|
+
this.isStale = true;
|
|
78
88
|
this.collection.fetchNextPage();
|
|
79
89
|
this.setAttribute('collection-page-offset', this.collection.meta.pageOffset);
|
|
80
90
|
}
|
|
@@ -91,6 +101,7 @@ class CollectionBrowser extends SocketConsumer {
|
|
|
91
101
|
'collection-view-params',
|
|
92
102
|
'collection-page-size',
|
|
93
103
|
'collection-page-offset',
|
|
104
|
+
'max-show-loader',
|
|
94
105
|
'type-alias',
|
|
95
106
|
'hide-error-logs'
|
|
96
107
|
];
|
|
@@ -101,6 +112,7 @@ class CollectionBrowser extends SocketConsumer {
|
|
|
101
112
|
if (this.collection && name === 'collection-page-offset') {
|
|
102
113
|
let newOffset = Number(newValue);
|
|
103
114
|
if (newOffset !== this.collection.meta.pageOffset) {
|
|
115
|
+
this.isStale = true;
|
|
104
116
|
this.collection.fetchPage(newOffset);
|
|
105
117
|
}
|
|
106
118
|
this.updatePageNumberElements();
|
|
@@ -149,8 +161,23 @@ class CollectionBrowser extends SocketConsumer {
|
|
|
149
161
|
}
|
|
150
162
|
|
|
151
163
|
renderList() {
|
|
152
|
-
|
|
153
|
-
let
|
|
164
|
+
let viewportSlot = this.shadowRoot.querySelector('slot[name="viewport"]');
|
|
165
|
+
let loaderSlot = this.shadowRoot.querySelector('slot[name="loader"]');
|
|
166
|
+
let hasLoaders = !!loaderSlot.assignedNodes().length;
|
|
167
|
+
|
|
168
|
+
if (!this.collection || !this.collection.isLoaded) {
|
|
169
|
+
if (hasLoaders && (this.isStale || this.hasAttribute('max-show-loader'))) {
|
|
170
|
+
viewportSlot.classList.add('hidden');
|
|
171
|
+
loaderSlot.classList.remove('hidden');
|
|
172
|
+
}
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
this.isStale = false;
|
|
176
|
+
|
|
177
|
+
loaderSlot.classList.add('hidden');
|
|
178
|
+
viewportSlot.classList.remove('hidden');
|
|
179
|
+
|
|
180
|
+
let viewportNode = viewportSlot.assignedNodes()[0];
|
|
154
181
|
if (!viewportNode) return;
|
|
155
182
|
|
|
156
183
|
let itemTemplate = this.shadowRoot.querySelector('slot[name="item"]').assignedNodes()[0];
|
|
@@ -200,6 +227,7 @@ class CollectionBrowser extends SocketConsumer {
|
|
|
200
227
|
.filter(([field]) => field)
|
|
201
228
|
);
|
|
202
229
|
if (this.collection) this.collection.destroy();
|
|
230
|
+
this.isStale = true;
|
|
203
231
|
|
|
204
232
|
this.collection = new AGCollection({
|
|
205
233
|
socket: this.socket,
|
|
@@ -213,6 +241,12 @@ class CollectionBrowser extends SocketConsumer {
|
|
|
213
241
|
});
|
|
214
242
|
|
|
215
243
|
this.shadowRoot.innerHTML = `
|
|
244
|
+
<style>
|
|
245
|
+
.hidden {
|
|
246
|
+
display: none;
|
|
247
|
+
}
|
|
248
|
+
</style>
|
|
249
|
+
<slot name="loader"></slot>
|
|
216
250
|
<slot name="item"></slot>
|
|
217
251
|
<slot name="no-item"></slot>
|
|
218
252
|
<slot name="first-item"></slot>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
class CollectionDeleter extends HTMLElement {
|
|
2
|
+
constructor() {
|
|
3
|
+
super();
|
|
4
|
+
this.attachShadow({ mode: 'open' });
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
static get observedAttributes() {
|
|
8
|
+
return [
|
|
9
|
+
'model-id',
|
|
10
|
+
'confirm-message'
|
|
11
|
+
];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
connectedCallback() {
|
|
15
|
+
this.shadowRoot.innerHTML = `
|
|
16
|
+
<slot></slot>
|
|
17
|
+
`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
deleteItem() {
|
|
21
|
+
let modelId = this.getAttribute('model-id');
|
|
22
|
+
this.dispatchEvent(
|
|
23
|
+
new CustomEvent('crudDelete', { detail: modelId, bubbles: true })
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
confirmDeleteItem() {
|
|
28
|
+
let confirmMessage = this.getAttribute('confirm-message');
|
|
29
|
+
this.dispatchEvent(
|
|
30
|
+
new CustomEvent('showModal', {
|
|
31
|
+
detail: {
|
|
32
|
+
message: confirmMessage,
|
|
33
|
+
callback: () => {
|
|
34
|
+
this.deleteItem();
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
bubbles: true
|
|
38
|
+
})
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
window.customElements.define('collection-deleter', CollectionDeleter);
|
package/collection-reducer.js
CHANGED
|
@@ -24,6 +24,7 @@ class CollectionReducer extends SocketConsumer {
|
|
|
24
24
|
this.handleSlotChangeEvent = () => {
|
|
25
25
|
this.renderReduction();
|
|
26
26
|
};
|
|
27
|
+
this.isReady = false;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
connectedCallback() {
|
|
@@ -126,8 +127,22 @@ class CollectionReducer extends SocketConsumer {
|
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
renderReduction() {
|
|
129
|
-
|
|
130
|
-
let
|
|
130
|
+
let viewportSlot = this.shadowRoot.querySelector('slot[name="viewport"]');
|
|
131
|
+
let loaderSlot = this.shadowRoot.querySelector('slot[name="loader"]');
|
|
132
|
+
let hasLoaders = !!loaderSlot.assignedNodes().length;
|
|
133
|
+
|
|
134
|
+
if (!this.collection || !this.collection.isLoaded) {
|
|
135
|
+
if (hasLoaders) {
|
|
136
|
+
viewportSlot.classList.add('hidden');
|
|
137
|
+
loaderSlot.classList.remove('hidden');
|
|
138
|
+
}
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
loaderSlot.classList.add('hidden');
|
|
143
|
+
viewportSlot.classList.remove('hidden');
|
|
144
|
+
|
|
145
|
+
let viewportNode = viewportSlot.assignedNodes()[0];
|
|
131
146
|
if (!viewportNode) return;
|
|
132
147
|
|
|
133
148
|
let itemTemplate = this.shadowRoot.querySelector('slot[name="item"]').assignedNodes()[0];
|
|
@@ -176,6 +191,12 @@ class CollectionReducer extends SocketConsumer {
|
|
|
176
191
|
});
|
|
177
192
|
|
|
178
193
|
this.shadowRoot.innerHTML = `
|
|
194
|
+
<style>
|
|
195
|
+
.hidden {
|
|
196
|
+
display: none;
|
|
197
|
+
}
|
|
198
|
+
</style>
|
|
199
|
+
<slot name="loader"></slot>
|
|
179
200
|
<slot name="item"></slot>
|
|
180
201
|
<slot name="no-item"></slot>
|
|
181
202
|
<slot name="viewport"></slot>
|
package/confirm-modal.js
CHANGED
package/log-in-form.js
CHANGED
package/log-out.js
ADDED
package/model-input.js
CHANGED
|
@@ -139,12 +139,12 @@ class ModelInput extends SocketConsumer {
|
|
|
139
139
|
if (type === 'select') {
|
|
140
140
|
if (options) {
|
|
141
141
|
let optionElements = options
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
142
|
+
.split(',')
|
|
143
|
+
.map((option) => {
|
|
144
|
+
return `<option value="${option}">${option}</option>`;
|
|
145
|
+
})
|
|
146
|
+
.join('');
|
|
147
|
+
this.inputElement.innerHTML = optionElements;
|
|
148
148
|
}
|
|
149
149
|
} else if (elementType === 'input') {
|
|
150
150
|
if (inputList) {
|
package/model-text.js
CHANGED
|
@@ -3,6 +3,11 @@ import { toSafeHTML } from './utils.js';
|
|
|
3
3
|
import AGModel from '/node_modules/ag-model/ag-model.js';
|
|
4
4
|
|
|
5
5
|
class ModelText extends SocketConsumer {
|
|
6
|
+
constructor() {
|
|
7
|
+
super();
|
|
8
|
+
this.isReady = false;
|
|
9
|
+
}
|
|
10
|
+
|
|
6
11
|
connectedCallback() {
|
|
7
12
|
this.isReady = true;
|
|
8
13
|
this.destroy = () => {};
|
package/model-viewer.js
CHANGED
|
@@ -10,6 +10,7 @@ class ModelViewer extends SocketConsumer {
|
|
|
10
10
|
this.handleSlotChangeEvent = () => {
|
|
11
11
|
this.renderItem();
|
|
12
12
|
};
|
|
13
|
+
this.isReady = false;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
connectedCallback() {
|
|
@@ -45,8 +46,22 @@ class ModelViewer extends SocketConsumer {
|
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
renderItem() {
|
|
48
|
-
|
|
49
|
-
let
|
|
49
|
+
let viewportSlot = this.shadowRoot.querySelector('slot[name="viewport"]');
|
|
50
|
+
let loaderSlot = this.shadowRoot.querySelector('slot[name="loader"]');
|
|
51
|
+
let hasLoaders = !!loaderSlot.assignedNodes().length;
|
|
52
|
+
|
|
53
|
+
if (!this.model || !this.model.isLoaded) {
|
|
54
|
+
if (hasLoaders) {
|
|
55
|
+
viewportSlot.classList.add('hidden');
|
|
56
|
+
loaderSlot.classList.remove('hidden');
|
|
57
|
+
}
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
loaderSlot.classList.add('hidden');
|
|
62
|
+
viewportSlot.classList.remove('hidden');
|
|
63
|
+
|
|
64
|
+
let viewportNode = viewportSlot.assignedNodes()[0];
|
|
50
65
|
if (!viewportNode) return;
|
|
51
66
|
|
|
52
67
|
let itemTemplate = this.shadowRoot.querySelector('slot[name="item"]').assignedNodes()[0];
|
|
@@ -82,6 +97,12 @@ class ModelViewer extends SocketConsumer {
|
|
|
82
97
|
});
|
|
83
98
|
|
|
84
99
|
this.shadowRoot.innerHTML = `
|
|
100
|
+
<style>
|
|
101
|
+
.hidden {
|
|
102
|
+
display: none;
|
|
103
|
+
}
|
|
104
|
+
</style>
|
|
105
|
+
<slot name="loader"></slot>
|
|
85
106
|
<slot name="item"></slot>
|
|
86
107
|
<slot name="no-item"></slot>
|
|
87
108
|
<slot name="viewport"></slot>
|
package/package.json
CHANGED
package/render-group.js
CHANGED
|
@@ -16,12 +16,7 @@ class RenderGroup extends HTMLElement {
|
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
disconnectedCallback() {
|
|
20
|
-
this.removeEventListener('load', this.loadHandler);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
19
|
connectedCallback() {
|
|
24
|
-
this.isReady = true;
|
|
25
20
|
this.loadedElements = {};
|
|
26
21
|
let hasPending = !!this.getWaitFor().length;
|
|
27
22
|
this.shadowRoot.innerHTML = `
|
|
@@ -30,16 +25,23 @@ class RenderGroup extends HTMLElement {
|
|
|
30
25
|
display: none;
|
|
31
26
|
}
|
|
32
27
|
</style>
|
|
33
|
-
<slot
|
|
28
|
+
<slot name="loader"></slot>
|
|
29
|
+
<slot id="main-slot"${hasPending ? ' class="hidden"': ''}></slot>
|
|
34
30
|
`;
|
|
35
31
|
if (hasPending) {
|
|
36
32
|
this.addEventListener('load', this.loadHandler);
|
|
37
33
|
}
|
|
38
34
|
}
|
|
39
35
|
|
|
36
|
+
disconnectedCallback() {
|
|
37
|
+
this.removeEventListener('load', this.loadHandler);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
40
|
showSlottedElements() {
|
|
41
|
-
let
|
|
42
|
-
|
|
41
|
+
let loaderSlot = this.shadowRoot.querySelector('slot[name="loader"]');
|
|
42
|
+
loaderSlot.classList.add('hidden');
|
|
43
|
+
let mainSlot = this.shadowRoot.querySelector('#main-slot');
|
|
44
|
+
mainSlot.classList.remove('hidden');
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
getWaitFor() {
|
|
@@ -49,10 +51,6 @@ class RenderGroup extends HTMLElement {
|
|
|
49
51
|
}
|
|
50
52
|
return waitForString.split(',').map(id => id.trim());
|
|
51
53
|
}
|
|
52
|
-
|
|
53
|
-
attributeChangedCallback(name, oldValue, newValue) {
|
|
54
|
-
if (!this.isReady) return;
|
|
55
|
-
}
|
|
56
54
|
}
|
|
57
55
|
|
|
58
56
|
window.customElements.define('render-group', RenderGroup);
|