saasufy-components 1.13.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 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
@@ -8,6 +8,7 @@ export class AppRouter extends SocketConsumer {
8
8
  super();
9
9
  this.attachShadow({ mode: 'open' });
10
10
 
11
+ this.isReady = false;
11
12
  this.pages = {};
12
13
  this.pageRouteInfos = [];
13
14
  this.debounce = debouncer();
@@ -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() {
@@ -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
- if (!this.collection || !this.collection.isLoaded) return;
153
- let viewportNode = this.shadowRoot.querySelector('slot[name="viewport"]').assignedNodes()[0];
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);
@@ -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
- if (!this.collection || !this.collection.isLoaded) return;
130
- let viewportNode = this.shadowRoot.querySelector('slot[name="viewport"]').assignedNodes()[0];
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
@@ -4,6 +4,7 @@ class ConfirmModal extends HTMLElement {
4
4
  constructor() {
5
5
  super();
6
6
  this.hidden = true;
7
+ this.isReady = false;
7
8
  }
8
9
 
9
10
  connectedCallback() {
package/log-in-form.js CHANGED
@@ -6,6 +6,7 @@ const DEFAULT_AUTH_TIMEOUT = 10000;
6
6
  class LogInForm extends SocketConsumer {
7
7
  constructor() {
8
8
  super();
9
+ this.isReady = false;
9
10
  }
10
11
 
11
12
  connectedCallback() {
package/log-out.js ADDED
@@ -0,0 +1,9 @@
1
+ import { SocketConsumer } from './socket.js';
2
+
3
+ class LogOut extends SocketConsumer {
4
+ logOut() {
5
+ this.getSocket().deauthenticate();
6
+ }
7
+ }
8
+
9
+ window.customElements.define('log-out', LogOut);
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
- .split(',')
143
- .map((option) => {
144
- return `<option value="${option}">${option}</option>`;
145
- })
146
- .join('');
147
- this.inputElement.innerHTML = optionElements;
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
- if (!this.model || !this.model.isLoaded) return;
49
- let viewportNode = this.shadowRoot.querySelector('slot[name="viewport"]').assignedNodes()[0];
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.13.0",
3
+ "version": "1.14.0",
4
4
  "description": "Components for Saasufy.",
5
5
  "main": "index.js",
6
6
  "scripts": {
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${hasPending ? ' class="hidden"': ''}></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 slot = this.shadowRoot.querySelector('slot');
42
- slot.classList.remove('hidden');
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);