saasufy-components 1.5.3 → 1.7.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/model-input.js CHANGED
@@ -171,7 +171,8 @@ class ModelInput extends SocketConsumer {
171
171
  if (inputElement.type === 'checkbox') {
172
172
  inputElement.checked = !!fieldValue;
173
173
  } else {
174
- inputElement.value = fieldValue == null ? '' : fieldValue;
174
+ let defaultValue = this.getAttribute('default-value') || '';
175
+ inputElement.value = fieldValue == null ? defaultValue : fieldValue;
175
176
  }
176
177
  }
177
178
  let stopSaving = this.saveInputElementOnEdit(inputElement, model, fieldName, messageContainerElement);
@@ -181,9 +182,10 @@ class ModelInput extends SocketConsumer {
181
182
  if (event.resourceField !== fieldName || !event.isRemote) continue;
182
183
  let fieldValue = model.value[fieldName];
183
184
  if (inputElement.type === 'checkbox') {
184
- inputElement.checked = fieldValue;
185
+ inputElement.checked = !!fieldValue;
185
186
  } else {
186
- inputElement.value = fieldValue == null ? '' : fieldValue;
187
+ let defaultValue = this.getAttribute('default-value') || '';
188
+ inputElement.value = fieldValue == null ? defaultValue : fieldValue;
187
189
  }
188
190
  }
189
191
  })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "saasufy-components",
3
- "version": "1.5.3",
3
+ "version": "1.7.0",
4
4
  "description": "Components for Saasufy.",
5
5
  "main": "index.js",
6
6
  "scripts": {
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