saasufy-components 1.12.0 → 1.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 +15 -4
- package/app-router.js +1 -0
- package/collection-adder.js +1 -0
- package/collection-browser.js +31 -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 +10 -7
- package/model-text.js +5 -0
- package/model-viewer.js +23 -2
- package/package.json +3 -3
- 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,41 @@ const DEFAULT_RELOAD_DELAY = 0;
|
|
|
7
7
|
class CollectionBrowser extends SocketConsumer {
|
|
8
8
|
constructor() {
|
|
9
9
|
super();
|
|
10
|
+
this.isReady = false;
|
|
10
11
|
this.attachShadow({ mode: 'open' });
|
|
11
12
|
|
|
12
13
|
this.handleShowModalEvent = (event) => {
|
|
14
|
+
event.stopPropagation();
|
|
13
15
|
let modal = this.shadowRoot.querySelector('slot[name="modal"]').assignedNodes()[0];
|
|
14
16
|
modal.show(event.detail.message, event.detail.callback);
|
|
15
17
|
};
|
|
16
18
|
|
|
17
19
|
this.handleCRUDCreateEvent = (event) => {
|
|
20
|
+
event.stopPropagation();
|
|
18
21
|
if (this.collection) {
|
|
19
22
|
this.collection.create(event.detail);
|
|
20
23
|
}
|
|
21
24
|
};
|
|
22
25
|
|
|
23
26
|
this.handleCRUDDeleteEvent = (event) => {
|
|
27
|
+
event.stopPropagation();
|
|
24
28
|
if (this.collection) {
|
|
25
29
|
this.collection.delete(event.detail);
|
|
26
30
|
}
|
|
27
31
|
};
|
|
28
32
|
|
|
29
|
-
this.handleGoToPreviousPageEvent = () => {
|
|
33
|
+
this.handleGoToPreviousPageEvent = (event) => {
|
|
34
|
+
event.stopPropagation();
|
|
30
35
|
this.goToPreviousPage();
|
|
31
36
|
};
|
|
32
37
|
|
|
33
|
-
this.handleGoToNextPageEvent = () => {
|
|
38
|
+
this.handleGoToNextPageEvent = (event) => {
|
|
39
|
+
event.stopPropagation();
|
|
34
40
|
this.goToNextPage();
|
|
35
41
|
};
|
|
36
42
|
|
|
37
43
|
this.handleGoToPageEvent = (event) => {
|
|
44
|
+
event.stopPropagation();
|
|
38
45
|
this.goToPage((event.detail || {}).offset);
|
|
39
46
|
};
|
|
40
47
|
|
|
@@ -149,8 +156,22 @@ class CollectionBrowser extends SocketConsumer {
|
|
|
149
156
|
}
|
|
150
157
|
|
|
151
158
|
renderList() {
|
|
152
|
-
|
|
153
|
-
let
|
|
159
|
+
let viewportSlot = this.shadowRoot.querySelector('slot[name="viewport"]');
|
|
160
|
+
let loaderSlot = this.shadowRoot.querySelector('slot[name="loader"]');
|
|
161
|
+
let hasLoaders = !!loaderSlot.assignedNodes().length;
|
|
162
|
+
|
|
163
|
+
if (!this.collection || !this.collection.isLoaded) {
|
|
164
|
+
if (hasLoaders) {
|
|
165
|
+
viewportSlot.classList.add('hidden');
|
|
166
|
+
loaderSlot.classList.remove('hidden');
|
|
167
|
+
}
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
loaderSlot.classList.add('hidden');
|
|
172
|
+
viewportSlot.classList.remove('hidden');
|
|
173
|
+
|
|
174
|
+
let viewportNode = viewportSlot.assignedNodes()[0];
|
|
154
175
|
if (!viewportNode) return;
|
|
155
176
|
|
|
156
177
|
let itemTemplate = this.shadowRoot.querySelector('slot[name="item"]').assignedNodes()[0];
|
|
@@ -213,6 +234,12 @@ class CollectionBrowser extends SocketConsumer {
|
|
|
213
234
|
});
|
|
214
235
|
|
|
215
236
|
this.shadowRoot.innerHTML = `
|
|
237
|
+
<style>
|
|
238
|
+
.hidden {
|
|
239
|
+
display: none;
|
|
240
|
+
}
|
|
241
|
+
</style>
|
|
242
|
+
<slot name="loader"></slot>
|
|
216
243
|
<slot name="item"></slot>
|
|
217
244
|
<slot name="no-item"></slot>
|
|
218
245
|
<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
|
@@ -31,6 +31,7 @@ class ModelInput extends SocketConsumer {
|
|
|
31
31
|
'model-id',
|
|
32
32
|
'model-field',
|
|
33
33
|
'debounce-delay',
|
|
34
|
+
'enable-rebound',
|
|
34
35
|
'options',
|
|
35
36
|
'height',
|
|
36
37
|
'hide-error-logs'
|
|
@@ -75,6 +76,7 @@ class ModelInput extends SocketConsumer {
|
|
|
75
76
|
let options = this.getAttribute('options');
|
|
76
77
|
let height = this.getAttribute('height');
|
|
77
78
|
let hideErrorLogs = this.hasAttribute('hide-error-logs');
|
|
79
|
+
let enableRebound = this.hasAttribute('enable-rebound');
|
|
78
80
|
let currentNode = this.parentNode;
|
|
79
81
|
let model;
|
|
80
82
|
let isModelLocal = false;
|
|
@@ -100,7 +102,8 @@ class ModelInput extends SocketConsumer {
|
|
|
100
102
|
socket: this.socket,
|
|
101
103
|
type: modelType,
|
|
102
104
|
id: modelId,
|
|
103
|
-
fields: [ modelField ]
|
|
105
|
+
fields: [ modelField ],
|
|
106
|
+
enableRebound
|
|
104
107
|
});
|
|
105
108
|
if (!hideErrorLogs) {
|
|
106
109
|
(async () => {
|
|
@@ -136,12 +139,12 @@ class ModelInput extends SocketConsumer {
|
|
|
136
139
|
if (type === 'select') {
|
|
137
140
|
if (options) {
|
|
138
141
|
let optionElements = options
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
142
|
+
.split(',')
|
|
143
|
+
.map((option) => {
|
|
144
|
+
return `<option value="${option}">${option}</option>`;
|
|
145
|
+
})
|
|
146
|
+
.join('');
|
|
147
|
+
this.inputElement.innerHTML = optionElements;
|
|
145
148
|
}
|
|
146
149
|
} else if (elementType === 'input') {
|
|
147
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "saasufy-components",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"description": "Components for Saasufy.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"author": "Jonathan Gros-Dubois",
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"ag-collection": "^3.
|
|
21
|
-
"ag-model": "^1.
|
|
20
|
+
"ag-collection": "^3.4.0",
|
|
21
|
+
"ag-model": "^1.5.0",
|
|
22
22
|
"capitalisk-auth-client": "^1.5.1",
|
|
23
23
|
"socketcluster-client": "^19.1.0"
|
|
24
24
|
},
|
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);
|