saasufy-components 1.5.2 → 1.6.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
@@ -17,6 +17,11 @@ Used for rendering collections as lists, tables or other sequences based on a sp
17
17
  Supports pagination by allowing you to specify custom buttons or links to navigate between pages.
18
18
  Can also perform basic CRUD operations such as deleting or creating records.
19
19
 
20
+ ### collection-reducer
21
+
22
+ 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.
23
+ A common use case is to extract and combine values from a specific field to pass to other child components via their attributes.
24
+
20
25
  ### model-input
21
26
 
22
27
  Used for displaying and editing a field of a model instance in real time.
@@ -0,0 +1,227 @@
1
+ import { SocketConsumer } from './socket.js';
2
+ import AGCollection from '/node_modules/ag-collection/ag-collection.js';
3
+ import { renderTemplate } from './utils.js';
4
+
5
+ const DEFAULT_RELOAD_DELAY = 0;
6
+
7
+ class CollectionReducer extends SocketConsumer {
8
+ constructor() {
9
+ super();
10
+ this.attachShadow({ mode: 'open' });
11
+
12
+ this.handleGoToPreviousPageEvent = () => {
13
+ this.goToPreviousPage();
14
+ };
15
+
16
+ this.handleGoToNextPageEvent = () => {
17
+ this.goToNextPage();
18
+ };
19
+
20
+ this.handleGoToPageEvent = (event) => {
21
+ this.goToPage((event.detail || {}).offset);
22
+ };
23
+
24
+ this.handleSlotChangeEvent = () => {
25
+ this.renderReduction();
26
+ };
27
+ }
28
+
29
+ connectedCallback() {
30
+ this.isReady = true;
31
+ this.socket = this.getSocket();
32
+ this.shadowRoot.addEventListener('slotchange', this.handleSlotChangeEvent);
33
+ this.addEventListener('goToPreviousPage', this.handleGoToPreviousPageEvent);
34
+ this.addEventListener('goToNextPage', this.handleGoToNextPageEvent);
35
+ this.addEventListener('goToPage', this.handleGoToPageEvent);
36
+ this.render();
37
+ }
38
+
39
+ disconnectedCallback() {
40
+ if (this.collection) this.collection.destroy();
41
+ this.shadowRoot.removeEventListener('slotchange', this.handleSlotChangeEvent);
42
+ this.removeEventListener('goToPreviousPage', this.handleGoToPreviousPageEvent);
43
+ this.removeEventListener('goToNextPage', this.handleGoToNextPageEvent);
44
+ this.removeEventListener('goToPage', this.handleGoToPageEvent);
45
+ }
46
+
47
+ goToPreviousPage() {
48
+ if (!this.collection) return;
49
+ this.collection.fetchPreviousPage();
50
+ this.setAttribute('collection-page-offset', this.collection.meta.pageOffset);
51
+ }
52
+
53
+ goToNextPage() {
54
+ if (!this.collection) return;
55
+ this.collection.fetchNextPage();
56
+ this.setAttribute('collection-page-offset', this.collection.meta.pageOffset);
57
+ }
58
+
59
+ goToPage(offset) {
60
+ this.setAttribute('collection-page-offset', offset);
61
+ }
62
+
63
+ static get observedAttributes() {
64
+ return [
65
+ 'collection-type',
66
+ 'collection-fields',
67
+ 'collection-view',
68
+ 'collection-view-params',
69
+ 'collection-page-size',
70
+ 'collection-page-offset',
71
+ 'type-alias',
72
+ 'hide-error-logs'
73
+ ];
74
+ }
75
+
76
+ attributeChangedCallback(name, oldValue, newValue) {
77
+ if (!this.isReady) return;
78
+ if (this.collection && name === 'collection-page-offset') {
79
+ let newOffset = Number(newValue);
80
+ if (newOffset !== this.collection.meta.pageOffset) {
81
+ this.collection.fetchPage(newOffset);
82
+ }
83
+ this.updatePageNumberElements();
84
+ this.updatePageButtons();
85
+ } else {
86
+ this.render();
87
+ }
88
+ }
89
+
90
+ updatePageNumberElements() {
91
+ if (!this.collection) return;
92
+ let currentPageElements = this.shadowRoot.querySelector('slot[name="page-number"]').assignedElements();
93
+ let pageNumber = Math.floor(this.collection.meta.pageOffset / this.collection.meta.pageSize + 1);
94
+ for (let element of currentPageElements) {
95
+ if (element.nodeName === 'INPUT') {
96
+ element.value = pageNumber;
97
+ } else {
98
+ element.innerHTML = pageNumber;
99
+ }
100
+ }
101
+ }
102
+
103
+ updatePageButtons() {
104
+ if (this.collection) {
105
+ let previousPageElements = this.shadowRoot.querySelector('slot[name="previous-page"]').assignedElements();
106
+ let nextPageElements = this.shadowRoot.querySelector('slot[name="next-page"]').assignedElements();
107
+ if (this.collection.meta.pageOffset <= 0) {
108
+ for (let element of previousPageElements) {
109
+ element.setAttribute('disabled', '');
110
+ }
111
+ } else {
112
+ for (let element of previousPageElements) {
113
+ element.removeAttribute('disabled');
114
+ }
115
+ }
116
+ if (this.collection.meta.isLastPage) {
117
+ for (let element of nextPageElements) {
118
+ element.setAttribute('disabled', '');
119
+ }
120
+ } else {
121
+ for (let element of nextPageElements) {
122
+ element.removeAttribute('disabled');
123
+ }
124
+ }
125
+ }
126
+ }
127
+
128
+ renderReduction() {
129
+ if (!this.collection || !this.collection.isLoaded) return;
130
+ let viewportNode = this.shadowRoot.querySelector('slot[name="viewport"]').assignedNodes()[0];
131
+ if (!viewportNode) return;
132
+
133
+ let itemTemplate = this.shadowRoot.querySelector('slot[name="item"]').assignedNodes()[0];
134
+ let noItemTemplate = this.shadowRoot.querySelector('slot[name="no-item"]').assignedNodes()[0];
135
+
136
+ if (noItemTemplate && !this.collection.value.length) {
137
+ viewportNode.innerHTML = noItemTemplate.innerHTML;
138
+ } else if (itemTemplate) {
139
+ let type = this.getAttribute('type-alias') || this.collection.type;
140
+ let itemString = renderTemplate(
141
+ itemTemplate.innerHTML,
142
+ { [type]: this.collection.value }
143
+ );
144
+ viewportNode.innerHTML = itemString;
145
+ }
146
+ }
147
+
148
+ render() {
149
+ let collectionType = this.getAttribute('collection-type');
150
+ let collectionFields = this.getAttribute('collection-fields') || '';
151
+ let collectionView = this.getAttribute('collection-view');
152
+ let collectionPageSize = this.getAttribute('collection-page-size');
153
+ let collectionViewParams = this.getAttribute('collection-view-params') || '';
154
+ let collectionPageOffset = this.getAttribute('collection-page-offset');
155
+ let hideErrorLogs = this.hasAttribute('hide-error-logs');
156
+ let collectionReloadDelay = Number(
157
+ this.getAttribute('collection-reload-delay') || DEFAULT_RELOAD_DELAY
158
+ );
159
+ let viewParams = Object.fromEntries(
160
+ collectionViewParams.split(',')
161
+ .map(pair => pair.split('='))
162
+ .filter(([field]) => field)
163
+ );
164
+ if (this.collection) this.collection.destroy();
165
+
166
+ this.collection = new AGCollection({
167
+ socket: this.socket,
168
+ type: collectionType,
169
+ fields: collectionFields.split(',').map(field => field.trim()).filter(field => field),
170
+ view: collectionView,
171
+ viewParams,
172
+ pageSize: Number(collectionPageSize || 10),
173
+ pageOffset: Number(collectionPageOffset || 0),
174
+ changeReloadDelay: collectionReloadDelay
175
+ });
176
+
177
+ this.shadowRoot.innerHTML = `
178
+ <slot name="item"></slot>
179
+ <slot name="no-item"></slot>
180
+ <slot name="viewport"></slot>
181
+ <slot name="previous-page"></slot>
182
+ <slot name="page-number"></slot>
183
+ <slot name="next-page"></slot>
184
+ `;
185
+
186
+ let previousPageSlot = this.shadowRoot.querySelector('slot[name="previous-page"]');
187
+ previousPageSlot.addEventListener('click', () => {
188
+ this.goToPreviousPage();
189
+ });
190
+
191
+ let nextPageSlot = this.shadowRoot.querySelector('slot[name="next-page"]');
192
+ nextPageSlot.addEventListener('click', () => {
193
+ this.goToNextPage();
194
+ });
195
+
196
+ this.updatePageNumberElements();
197
+ this.updatePageButtons();
198
+
199
+ (async () => {
200
+ await this.collection.listener('load').once();
201
+ if (!this.collection.value.length) {
202
+ this.renderReduction();
203
+ }
204
+ this.setAttribute('is-loaded', '');
205
+ this.dispatchEvent(new CustomEvent('load', { bubbles: true }));
206
+ })();
207
+
208
+ (async () => {
209
+ for await (let event of this.collection.listener('change')) {
210
+ this.renderReduction();
211
+ this.updatePageButtons();
212
+ }
213
+ })();
214
+
215
+ if (!hideErrorLogs) {
216
+ (async () => {
217
+ for await (let { error } of this.collection.listener('error')) {
218
+ console.error(
219
+ `Collection reducer encountered an error: ${error.message}`
220
+ );
221
+ }
222
+ })();
223
+ }
224
+ }
225
+ }
226
+
227
+ window.customElements.define('collection-reducer', CollectionReducer);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "saasufy-components",
3
- "version": "1.5.2",
3
+ "version": "1.6.0",
4
4
  "description": "Components for Saasufy.",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/socket.js CHANGED
@@ -88,13 +88,14 @@ export class SocketProvider extends HTMLElement {
88
88
  getSocket() {
89
89
  if (this.saasufySocket) {
90
90
  let sanitizedURL = this.getSanitizedURL();
91
- if (sanitizedURL && this.saasufySocket.uri() !== sanitizedURL) {
91
+ if (this.lastSaasufySocketURL !== sanitizedURL) {
92
92
  this.saasufySocket.connect(
93
93
  this.getSocketOptions()
94
94
  );
95
95
  } else {
96
96
  this.saasufySocket.connect();
97
97
  }
98
+ this.lastSaasufySocketURL = sanitizedURL;
98
99
  return this.saasufySocket;
99
100
  }
100
101
  return this.createSocket();
@@ -103,6 +104,7 @@ export class SocketProvider extends HTMLElement {
103
104
  createSocket() {
104
105
  let socketOptions = this.getSocketOptions();
105
106
  this.saasufySocket = create(socketOptions);
107
+ this.lastSaasufySocketURL = this.getSanitizedURL();
106
108
 
107
109
  return this.saasufySocket;
108
110
  }
package/utils.js CHANGED
@@ -167,6 +167,9 @@ let templateFormatters = {
167
167
  trim: (value) => String(value).trim(),
168
168
  fallback: (...args) => {
169
169
  return args.filter(arg => arg)[0];
170
+ },
171
+ joinFields: (list, field, sep) => {
172
+ return list.map(item => item[field]).join(sep);
170
173
  }
171
174
  };
172
175