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 +5 -0
- package/collection-reducer.js +227 -0
- package/model-input.js +5 -3
- package/package.json +1 -1
- package/utils.js +3 -0
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
|
-
|
|
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
|
-
|
|
187
|
+
let defaultValue = this.getAttribute('default-value') || '';
|
|
188
|
+
inputElement.value = fieldValue == null ? defaultValue : fieldValue;
|
|
187
189
|
}
|
|
188
190
|
}
|
|
189
191
|
})();
|
package/package.json
CHANGED
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
|
|