saasufy-components 2.6.2 → 2.8.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 +147 -1
- package/collection-adder.js +79 -8
- package/input-provider.js +11 -1
- package/model-input.js +27 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,23 +1,169 @@
|
|
|
1
1
|
# Saasufy Components
|
|
2
2
|
Components for Saasufy
|
|
3
3
|
|
|
4
|
+
## Overview
|
|
5
|
+
|
|
6
|
+
Many Saasufy components use a combination of `<template slot="item">` and `<div slot="viewport">`. Both of these are known as `slotted` elements.
|
|
7
|
+
|
|
8
|
+
A `<template slot="item">` is an inert/hidden element which the component uses to format its output. The `<div slot="viewport">` is an element which will contain the rendered output of the component.
|
|
9
|
+
|
|
10
|
+
Components which follow this pattern take templates as input and render them inside the specified `<div slot="viewport">` element. Note that the viewport can be any element and not necessarily a `div`.
|
|
11
|
+
|
|
12
|
+
The most important thing to remember is that the element marked with the `slot="item"` attribute serves as a template for your component and the element marked with `slot="viewport"` serves as a container for its output. Although the `item` slot is commonly used, components may require different slot names for input templates so it's important to read the documentation carefully.
|
|
13
|
+
|
|
14
|
+
A component's output consists of instances of your template after they've been populated with data from the component.
|
|
15
|
+
You can apply custom CSS/styling to elements that are rendered inside a viewport - It may be useful to add a custom `class` attribute to your viewport element to allow targeting using CSS selectors.
|
|
16
|
+
|
|
4
17
|
## How to use
|
|
5
18
|
|
|
6
|
-
|
|
19
|
+
To use components, you just need to include them into your `.html` file inside your `<head>` tag like this:
|
|
20
|
+
|
|
21
|
+
```html
|
|
22
|
+
<script src="https://saasufy.com/node_modules/saasufy-components/socket.js" type="module" defer></script>
|
|
23
|
+
<script src="https://saasufy.com/node_modules/saasufy-components/app-router.js" type="module" defer></script>
|
|
24
|
+
<script src="https://saasufy.com/node_modules/saasufy-components/collection-browser.js" type="module" defer></script>
|
|
25
|
+
<script src="https://saasufy.com/node_modules/saasufy-components/collection-adder.js" type="module" defer></script>
|
|
26
|
+
<script src="https://saasufy.com/node_modules/saasufy-components/collection-deleter.js" type="module" defer></script>
|
|
27
|
+
<script src="https://saasufy.com/node_modules/saasufy-components/collection-reducer.js" type="module" defer></script>
|
|
28
|
+
<script src="https://saasufy.com/node_modules/saasufy-components/model-input.js" type="module" defer></script>
|
|
29
|
+
<script src="https://saasufy.com/node_modules/saasufy-components/model-text.js" type="module" defer></script>
|
|
30
|
+
<script src="https://saasufy.com/node_modules/saasufy-components/model-viewer.js" type="module" defer></script>
|
|
31
|
+
<script src="https://saasufy.com/node_modules/saasufy-components/input-provider.js" type="module" defer></script>
|
|
32
|
+
<script src="https://saasufy.com/node_modules/saasufy-components/confirm-modal.js" type="module" defer></script>
|
|
33
|
+
<script src="https://saasufy.com/node_modules/saasufy-components/render-group.js" type="module" defer></script>
|
|
34
|
+
<script src="https://saasufy.com/node_modules/saasufy-components/conditional-group.js" type="module" defer></script>
|
|
35
|
+
<script src="https://saasufy.com/node_modules/saasufy-components/collection-adder-group.js" type="module" defer></script>
|
|
36
|
+
<script src="https://saasufy.com/node_modules/saasufy-components/log-in-form.js" type="module" defer></script>
|
|
37
|
+
<script src="https://saasufy.com/node_modules/saasufy-components/log-out.js" type="module" defer></script>
|
|
38
|
+
<script src="https://saasufy.com/node_modules/saasufy-components/oauth-link.js" type="module" defer></script>
|
|
39
|
+
<script src="https://saasufy.com/node_modules/saasufy-components/oauth-handler.js" type="module" defer></script>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
You should only add the script tags for the components which your page uses to avoid wasting bandwidth and unnecessarily delaying page load for your users.
|
|
43
|
+
The `.js` file name of the component matches the names of the HTML tags which the script provides.
|
|
44
|
+
The only exception is the `socket-provider` component which is located inside the `socket.js` file - This is because that file exposes multiple components which are used behind the scenes by other components.
|
|
45
|
+
You should always include the `socket.js` script as shown above whenever you use components which depend on data from Saasufy.
|
|
46
|
+
|
|
47
|
+
Linking the scripts directly from `saasufy.com` as shown above is the simplest way to get started as it doesn't require loading anything or hosting the scripts yourself.
|
|
48
|
+
An alternative approach is to download Saasufy components using `npm install saasufy-components` and then link to them from your own hosting provider.
|
|
7
49
|
|
|
8
50
|
## Components
|
|
9
51
|
|
|
52
|
+
### app-router
|
|
53
|
+
|
|
54
|
+
A component which allows you build apps which have multiple pages. Inside it, you should specify a template for each page in your app along with the route/path to bind each page to.
|
|
55
|
+
This component also supports simple redirects as well as redirects based on the user's current authentication state. Redirects can be soft or hard; a soft redirect does not change the current URL/path, a hard redirect does.
|
|
56
|
+
|
|
57
|
+
**Example usage**
|
|
58
|
+
|
|
59
|
+
```html
|
|
60
|
+
<app-router>
|
|
61
|
+
<template slot="page" route-path="/:productName">
|
|
62
|
+
<div class="product-title">Product: {{capitalize(productName)}}</div>
|
|
63
|
+
</template>
|
|
64
|
+
<div slot="viewport"></div>
|
|
65
|
+
</app-router>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
This example shows how an infinite number of pages can be represented with a single route template.
|
|
69
|
+
In this case, the `/:productName` inside the `route-path` attribute of the slotted `<template slot="page">` element serves as a placeholder variable which can match any text.
|
|
70
|
+
You can then use such route variables inside the page template itself as shown with `<div class="product-title">Product: {{capitalize(productName)}}</div>` - In this case, the `productName` variable which comes from the route path is first capitalized using the `capitalize` utility function before it is rendered into the template.
|
|
71
|
+
Note that the `app-router` can contain any number of pages/routes so the `slot="page"` attribute can be re-used multiple times, e.g:
|
|
72
|
+
|
|
73
|
+
```html
|
|
74
|
+
<app-router>
|
|
75
|
+
<template slot="page" route-path="/home">
|
|
76
|
+
<div>This is the home page</div>
|
|
77
|
+
</template>
|
|
78
|
+
<template slot="page" route-path="/about-us">
|
|
79
|
+
<div>This is the about-us page</div>
|
|
80
|
+
</template>
|
|
81
|
+
<template slot="page" route-path="/products">
|
|
82
|
+
<div>This is the products page</div>
|
|
83
|
+
</template>
|
|
84
|
+
<div slot="viewport"></div>
|
|
85
|
+
</app-router>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The app router path is based on the `location.hash` so the path section in your browser's address bar needs to start with the `#` character.
|
|
89
|
+
So for example, if your `index.html` file is served up from the URL `http://mywebsite.com`, then, to activate the `/products` route as in the example above, you would need to type the URL `http://mywebsite.com#/products`.
|
|
90
|
+
|
|
91
|
+
**Attributes of app-router**
|
|
92
|
+
|
|
93
|
+
- `default-page`: The path/route of the default page to send the user to in case no routes are matched. Commonly used to point to a `/page-not-found` page. For the home page, it's typically recommended to create a page template with `route-path=""` instead.
|
|
94
|
+
- `debounce-delay`: The number of milliseconds to wait before changing the route. This can help to avoid multiple renders if the route changes rapidly (e.g. when doing hard redirects). Defaults to 100ms.
|
|
95
|
+
|
|
96
|
+
**Attributes of slotted page templates**
|
|
97
|
+
|
|
98
|
+
- `redirect`: An optional path/route which this page should redirect to. Note that the content of this page will not be shown so it should always be empty.
|
|
99
|
+
- `auth-redirect`: An optional path/route which this page should redirect to if the user is authenticated. If the user is not authenticated, then the content of the template will be displayed as normal.
|
|
100
|
+
- `no-auth-redirect`: An optional path/route which this page should redirect to if the user is not authenticated. If the user is authenticated, then the content of the template will be displayed as normal.
|
|
101
|
+
- `hard-redirect`: An optional, additional attribute which can be specified alongside a `redirect`, `auth-redirect` or `no-auth-redirect` attribute to ensure that any redirect will change the URL in the address bar. Redirects are `soft` redirects by default; this means that they redirect the user to the target page but keep the current URL path unchanged.
|
|
102
|
+
|
|
10
103
|
### socket-provider
|
|
11
104
|
|
|
12
105
|
A top level component which connects to your Saasufy service and inside which you can place other components which depends on Saasufy data.
|
|
13
106
|
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
107
|
|
|
108
|
+
**Example usage**
|
|
109
|
+
|
|
110
|
+
```html
|
|
111
|
+
<socket-provider url="wss://saasufy.com/sid7890/socketcluster/">
|
|
112
|
+
<!-- Elements which rely on Saasufy data go here. -->
|
|
113
|
+
</socket-provider>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Attributes**
|
|
117
|
+
|
|
118
|
+
- `url` (required): Specifies the URL for the Saasufy service to use as the data store for all components which are placed inside it.
|
|
119
|
+
- `auth-token-name`: Allows you to specify a custom key for your token - This will be used as the key in localStorage. You generally do not need to set this attribute. It is intended for situations were an app has multiple `socket-provider` elements connecting different services with different/separate authentication flows.
|
|
120
|
+
|
|
15
121
|
### collection-browser
|
|
16
122
|
|
|
17
123
|
Used for rendering collections as lists, tables or other sequences based on a specific view using a template.
|
|
18
124
|
Supports pagination by allowing you to specify custom buttons or links to navigate between pages.
|
|
19
125
|
Can also perform basic CRUD operations such as deleting or creating records by listening for events from child components.
|
|
20
126
|
|
|
127
|
+
**Example usage**
|
|
128
|
+
|
|
129
|
+
```html
|
|
130
|
+
<collection-browser
|
|
131
|
+
collection-type="Chat"
|
|
132
|
+
collection-fields="username,message,createdAt"
|
|
133
|
+
collection-view="recentView"
|
|
134
|
+
collection-view-params=""
|
|
135
|
+
collection-page-size="50"
|
|
136
|
+
>
|
|
137
|
+
<template slot="item">
|
|
138
|
+
<div>
|
|
139
|
+
<div class="chat-username">
|
|
140
|
+
<b>{{Chat.username}}</b>
|
|
141
|
+
</div>
|
|
142
|
+
<div class="chat-message">{{Chat.message}}</div>
|
|
143
|
+
<div class="chat-created-at">{{date(Chat.createdAt)}}</div>
|
|
144
|
+
</div>
|
|
145
|
+
</template>
|
|
146
|
+
|
|
147
|
+
<div slot="viewport" class="chat-viewport"></div>
|
|
148
|
+
|
|
149
|
+
<div slot="loader" class="loading-spinner-container">
|
|
150
|
+
<div class="spinning">↻</div>
|
|
151
|
+
</div>
|
|
152
|
+
</collection-browser>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Attributes**
|
|
156
|
+
|
|
157
|
+
- `collection-type` (required): Specifies the type of collection to be used, which defines the dataset you want to work with. This should match a `Model` available in your Saasufy service.
|
|
158
|
+
- `collection-fields` (required): A comma-separated list of fields from the `Model` that you want to display or use. This lets you pick specific pieces of data from your collection to work with.
|
|
159
|
+
- `collection-view` (required): Determines the view of the collection. This should match one of the `Views` defined in your Saasufy service under that specific `Model`.
|
|
160
|
+
- `collection-view-params` (required): Parameters for the view, specified as comma-separated key-value pairs (e.g., key1=value1,key2=value2). These parameters can customize the behavior of the collection view. The keys must match `paramFields` specified in your Saasufy service under the relevant `View`.
|
|
161
|
+
- `collection-page-size`: Sets how many items from the collection are displayed at once. This is useful for pagination, allowing users to navigate through large sets of data in chunks.
|
|
162
|
+
- `collection-page-offset`: Indicates the current page offset in the collection's data. It’s like telling the browser which page of data you want to display initially.
|
|
163
|
+
- `type-alias`: Allows you to provide an alternative name for your `Model` to use when injecting values inside the template. This is useful for situations where you may have multiple `collection-browser` elements and/or `model-viewer` elements of the same type nested inside each other and want to avoid `Model` name clashes in the nested template definitions. For example, if the `type-alias` in the snippet above was set to `SubChat`, then `{{Chat.message}}` would become `{{SubChat.message}}`.
|
|
164
|
+
- `hide-error-logs`: A flag that, when present, suppresses error logs from being printed to the console. This can be used to keep the user interface clean of technical messages.
|
|
165
|
+
- `max-show-loader`: If this attribute is present, your slotted `loader` element will be shown as often as possible; this includes situations where the collection is merely refreshing itself. It is disabled by default.
|
|
166
|
+
|
|
21
167
|
### collection-adder
|
|
22
168
|
|
|
23
169
|
A form component for inserting data into collections.
|
package/collection-adder.js
CHANGED
|
@@ -10,7 +10,10 @@ class CollectionAdder extends SocketConsumer {
|
|
|
10
10
|
text: String,
|
|
11
11
|
textarea: String,
|
|
12
12
|
checkbox: Boolean,
|
|
13
|
-
number: Number
|
|
13
|
+
number: Number,
|
|
14
|
+
radio: String,
|
|
15
|
+
select: String,
|
|
16
|
+
'text-select': String
|
|
14
17
|
};
|
|
15
18
|
this.fieldPartsRegExp = /("[^"]*"|'[^']*'|\([^)]*\)|[^,()"']+)+/g;
|
|
16
19
|
this.inputTypeWithParamsRegExp = /^([^()]*)(\(([^)]*)\))?/;
|
|
@@ -33,10 +36,14 @@ class CollectionAdder extends SocketConsumer {
|
|
|
33
36
|
'collection-type',
|
|
34
37
|
'collection-fields',
|
|
35
38
|
'field-labels',
|
|
39
|
+
'option-labels',
|
|
36
40
|
'model-values',
|
|
37
41
|
'submit-button-label',
|
|
38
42
|
'hide-submit-button',
|
|
39
|
-
'success-message'
|
|
43
|
+
'success-message',
|
|
44
|
+
'autocapitalize',
|
|
45
|
+
'autocorrect',
|
|
46
|
+
'trim-spaces'
|
|
40
47
|
];
|
|
41
48
|
}
|
|
42
49
|
|
|
@@ -94,8 +101,19 @@ class CollectionAdder extends SocketConsumer {
|
|
|
94
101
|
messageContainer.classList.remove('error');
|
|
95
102
|
messageContainer.textContent = '';
|
|
96
103
|
|
|
104
|
+
let radioInputs = [...this.querySelectorAll('.collection-adder-radio')];
|
|
105
|
+
let radioData = {};
|
|
106
|
+
for (let radio of radioInputs) {
|
|
107
|
+
if (radio.checked) {
|
|
108
|
+
radioData[radio.name] = radio.value;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let trimSpaces = this.hasAttribute('trim-spaces');
|
|
113
|
+
|
|
97
114
|
let newModelData = {
|
|
98
115
|
...this.modelFieldValues,
|
|
116
|
+
...radioData,
|
|
99
117
|
...Object.fromEntries(
|
|
100
118
|
[...this.querySelectorAll('.collection-adder-input')]
|
|
101
119
|
.filter(input => input.value !== '')
|
|
@@ -108,7 +126,11 @@ class CollectionAdder extends SocketConsumer {
|
|
|
108
126
|
} else {
|
|
109
127
|
value = input.value;
|
|
110
128
|
}
|
|
111
|
-
|
|
129
|
+
let sanitizedValue = Type(value);
|
|
130
|
+
if (trimSpaces && typeof sanitizedValue === 'string') {
|
|
131
|
+
sanitizedValue = sanitizedValue.trim();
|
|
132
|
+
}
|
|
133
|
+
return [ input.name, sanitizedValue ];
|
|
112
134
|
})
|
|
113
135
|
)
|
|
114
136
|
};
|
|
@@ -171,6 +193,19 @@ class CollectionAdder extends SocketConsumer {
|
|
|
171
193
|
fieldValues: modelFieldLabels
|
|
172
194
|
} = this.getFieldDetails(this.getAttribute('field-labels'));
|
|
173
195
|
|
|
196
|
+
let {
|
|
197
|
+
fieldValues: optionLabels
|
|
198
|
+
} = this.getFieldDetails(this.getAttribute('option-labels'));
|
|
199
|
+
|
|
200
|
+
let autocapitalize = this.getAttribute('autocapitalize');
|
|
201
|
+
let autocorrect = this.getAttribute('autocorrect');
|
|
202
|
+
|
|
203
|
+
let extraAttributesString = `${
|
|
204
|
+
autocapitalize ? ` autocapitalize="${autocapitalize}"` : ''
|
|
205
|
+
}${
|
|
206
|
+
autocorrect ? ` autocorrect="${autocorrect}"` : ''
|
|
207
|
+
}`;
|
|
208
|
+
|
|
174
209
|
this.modelFieldValues = modelFieldValues;
|
|
175
210
|
|
|
176
211
|
if (this.collection) this.collection.destroy();
|
|
@@ -192,6 +227,7 @@ class CollectionAdder extends SocketConsumer {
|
|
|
192
227
|
inputTypeParams = inputTypeParts[3] ? inputTypeParts[3].split(',') : [];
|
|
193
228
|
}
|
|
194
229
|
let inputLabel = modelFieldLabels[field] == null ? field : modelFieldLabels[field];
|
|
230
|
+
let optionLabel = optionLabels[field] == null ? 'Options' : optionLabels[field];
|
|
195
231
|
if (inputType === 'checkbox') {
|
|
196
232
|
items.push(
|
|
197
233
|
`<label>
|
|
@@ -205,6 +241,18 @@ class CollectionAdder extends SocketConsumer {
|
|
|
205
241
|
}" />
|
|
206
242
|
</label>`
|
|
207
243
|
);
|
|
244
|
+
} else if (inputType === 'select') {
|
|
245
|
+
items.push(
|
|
246
|
+
`<label>
|
|
247
|
+
${
|
|
248
|
+
inputLabel
|
|
249
|
+
}
|
|
250
|
+
<select name="${field}" class="collection-adder-input collection-adder-select">
|
|
251
|
+
<option value="" selected disabled hidden>${optionLabel}</option>
|
|
252
|
+
${inputTypeParams.map(param => `<option value="${param}">${param}</option>`).join('')}
|
|
253
|
+
</select>
|
|
254
|
+
</label>`
|
|
255
|
+
);
|
|
208
256
|
} else if (inputType === 'text-select') {
|
|
209
257
|
items.push(
|
|
210
258
|
`<div class="collection-adder-select-text-row">
|
|
@@ -212,20 +260,43 @@ class CollectionAdder extends SocketConsumer {
|
|
|
212
260
|
field
|
|
213
261
|
}" placeholder="${
|
|
214
262
|
inputLabel
|
|
215
|
-
}" />
|
|
263
|
+
}"${extraAttributesString} />
|
|
216
264
|
<select class="collection-adder-select">
|
|
217
|
-
<option value="" selected disabled hidden
|
|
218
|
-
${inputTypeParams.map(param => `<option value="${param}">${param}</option>`)}
|
|
265
|
+
<option value="" selected disabled hidden>${optionLabel}</option>
|
|
266
|
+
${inputTypeParams.map(param => `<option value="${param}">${param}</option>`).join('')}
|
|
219
267
|
</select>
|
|
220
268
|
</div>`
|
|
221
269
|
);
|
|
270
|
+
} else if (inputType === 'radio') {
|
|
271
|
+
items.push(
|
|
272
|
+
`
|
|
273
|
+
${
|
|
274
|
+
inputLabel
|
|
275
|
+
}
|
|
276
|
+
${
|
|
277
|
+
inputTypeParams.map(
|
|
278
|
+
param => (
|
|
279
|
+
`<label>
|
|
280
|
+
<input class="collection-adder-radio" type="${
|
|
281
|
+
inputType
|
|
282
|
+
}" name="${
|
|
283
|
+
field
|
|
284
|
+
}" value=${
|
|
285
|
+
param
|
|
286
|
+
} />
|
|
287
|
+
${param}
|
|
288
|
+
</label>`
|
|
289
|
+
)
|
|
290
|
+
).join('')
|
|
291
|
+
}`
|
|
292
|
+
);
|
|
222
293
|
} else if (inputType === 'textarea') {
|
|
223
294
|
items.push(
|
|
224
295
|
`<textarea class="collection-adder-input" name="${
|
|
225
296
|
field
|
|
226
297
|
}" placeholder="${
|
|
227
298
|
inputLabel
|
|
228
|
-
}"></textarea>`
|
|
299
|
+
}"${extraAttributesString}></textarea>`
|
|
229
300
|
);
|
|
230
301
|
} else {
|
|
231
302
|
items.push(
|
|
@@ -235,7 +306,7 @@ class CollectionAdder extends SocketConsumer {
|
|
|
235
306
|
field
|
|
236
307
|
}" placeholder="${
|
|
237
308
|
inputLabel
|
|
238
|
-
}" />`
|
|
309
|
+
}"${extraAttributesString} />`
|
|
239
310
|
);
|
|
240
311
|
}
|
|
241
312
|
}
|
package/input-provider.js
CHANGED
|
@@ -28,7 +28,9 @@ class InputProvider extends HTMLElement {
|
|
|
28
28
|
'debounce-delay',
|
|
29
29
|
'options',
|
|
30
30
|
'value',
|
|
31
|
-
'height'
|
|
31
|
+
'height',
|
|
32
|
+
'autocapitalize',
|
|
33
|
+
'autocorrect'
|
|
32
34
|
];
|
|
33
35
|
}
|
|
34
36
|
|
|
@@ -59,6 +61,8 @@ class InputProvider extends HTMLElement {
|
|
|
59
61
|
render() {
|
|
60
62
|
this.innerHTML = '';
|
|
61
63
|
let inputId = this.getAttribute('input-id');
|
|
64
|
+
let autocapitalize = this.getAttribute('autocapitalize');
|
|
65
|
+
let autocorrect = this.getAttribute('autocorrect');
|
|
62
66
|
let inputList = this.getAttribute('list');
|
|
63
67
|
let type = this.getAttribute('type') || 'text';
|
|
64
68
|
let placeholder = this.getAttribute('placeholder');
|
|
@@ -97,6 +101,12 @@ class InputProvider extends HTMLElement {
|
|
|
97
101
|
if (inputId) {
|
|
98
102
|
this.inputElement.setAttribute('id', inputId);
|
|
99
103
|
}
|
|
104
|
+
if (autocapitalize) {
|
|
105
|
+
this.inputElement.setAttribute('autocapitalize', autocapitalize);
|
|
106
|
+
}
|
|
107
|
+
if (autocorrect) {
|
|
108
|
+
this.inputElement.setAttribute('autocorrect', autocorrect);
|
|
109
|
+
}
|
|
100
110
|
if (placeholder) {
|
|
101
111
|
this.inputElement.setAttribute('placeholder', placeholder);
|
|
102
112
|
}
|
package/model-input.js
CHANGED
|
@@ -36,7 +36,9 @@ class ModelInput extends SocketConsumer {
|
|
|
36
36
|
'enable-rebound',
|
|
37
37
|
'options',
|
|
38
38
|
'height',
|
|
39
|
-
'hide-error-logs'
|
|
39
|
+
'hide-error-logs',
|
|
40
|
+
'autocapitalize',
|
|
41
|
+
'autocorrect'
|
|
40
42
|
];
|
|
41
43
|
}
|
|
42
44
|
|
|
@@ -71,6 +73,8 @@ class ModelInput extends SocketConsumer {
|
|
|
71
73
|
let modelInstanceProperty = this.getAttribute('model-instance-property');
|
|
72
74
|
let showErrorMessage = this.hasAttribute('show-error-message');
|
|
73
75
|
let inputId = this.getAttribute('input-id');
|
|
76
|
+
let autocapitalize = this.getAttribute('autocapitalize');
|
|
77
|
+
let autocorrect = this.getAttribute('autocorrect');
|
|
74
78
|
let inputList = this.getAttribute('list');
|
|
75
79
|
let type = this.getAttribute('type') || 'text';
|
|
76
80
|
let placeholder = this.getAttribute('placeholder');
|
|
@@ -188,6 +192,12 @@ class ModelInput extends SocketConsumer {
|
|
|
188
192
|
if (inputId) {
|
|
189
193
|
this.inputElement.setAttribute('id', inputId);
|
|
190
194
|
}
|
|
195
|
+
if (autocapitalize) {
|
|
196
|
+
this.inputElement.setAttribute('autocapitalize', autocapitalize);
|
|
197
|
+
}
|
|
198
|
+
if (autocorrect) {
|
|
199
|
+
this.inputElement.setAttribute('autocorrect', autocorrect);
|
|
200
|
+
}
|
|
191
201
|
if (placeholder) {
|
|
192
202
|
this.inputElement.setAttribute('placeholder', placeholder);
|
|
193
203
|
}
|
|
@@ -208,19 +218,23 @@ class ModelInput extends SocketConsumer {
|
|
|
208
218
|
};
|
|
209
219
|
}
|
|
210
220
|
|
|
221
|
+
updateInputElement(inputElement, fieldValue) {
|
|
222
|
+
let consumers = this.getAttribute('consumers');
|
|
223
|
+
if (inputElement.type === 'checkbox') {
|
|
224
|
+
let checked = !!fieldValue;
|
|
225
|
+
inputElement.checked = checked;
|
|
226
|
+
updateConsumerElements(this.parentElement, consumers, checked);
|
|
227
|
+
} else {
|
|
228
|
+
let defaultValue = this.getAttribute('default-value') || '';
|
|
229
|
+
inputElement.value = fieldValue == null ? defaultValue : fieldValue;
|
|
230
|
+
updateConsumerElements(this.parentElement, consumers, inputElement.value);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
211
234
|
syncInputElementWithModel(inputElement, model, fieldName, messageContainerElement) {
|
|
212
235
|
if (model.isLoaded) {
|
|
213
|
-
let consumers = this.getAttribute('consumers');
|
|
214
236
|
let fieldValue = model.value[fieldName];
|
|
215
|
-
|
|
216
|
-
let checked = !!fieldValue;
|
|
217
|
-
inputElement.checked = checked;
|
|
218
|
-
updateConsumerElements(this.parentElement, consumers, checked);
|
|
219
|
-
} else {
|
|
220
|
-
let defaultValue = this.getAttribute('default-value') || '';
|
|
221
|
-
inputElement.value = fieldValue == null ? defaultValue : fieldValue;
|
|
222
|
-
updateConsumerElements(this.parentElement, consumers, inputElement.value);
|
|
223
|
-
}
|
|
237
|
+
this.updateInputElement(inputElement, fieldValue);
|
|
224
238
|
}
|
|
225
239
|
let stopSaving = this.saveInputElementOnEdit(inputElement, model, fieldName, messageContainerElement);
|
|
226
240
|
let changeConsumer = model.listener('change').createConsumer();
|
|
@@ -228,17 +242,8 @@ class ModelInput extends SocketConsumer {
|
|
|
228
242
|
for await (let event of changeConsumer) {
|
|
229
243
|
let hasFocus = document.activeElement === this.inputElement;
|
|
230
244
|
if (event.resourceField !== fieldName || (hasFocus && !event.isRemote)) continue;
|
|
231
|
-
let consumers = this.getAttribute('consumers');
|
|
232
245
|
let fieldValue = model.value[fieldName];
|
|
233
|
-
|
|
234
|
-
let checked = !!fieldValue;
|
|
235
|
-
inputElement.checked = checked;
|
|
236
|
-
updateConsumerElements(this.parentElement, consumers, checked);
|
|
237
|
-
} else {
|
|
238
|
-
let defaultValue = this.getAttribute('default-value') || '';
|
|
239
|
-
inputElement.value = fieldValue == null ? defaultValue : fieldValue;
|
|
240
|
-
updateConsumerElements(this.parentElement, consumers, inputElement.value);
|
|
241
|
-
}
|
|
246
|
+
this.updateInputElement(inputElement, fieldValue);
|
|
242
247
|
}
|
|
243
248
|
})();
|
|
244
249
|
return () => {
|
|
@@ -304,7 +309,7 @@ class ModelInput extends SocketConsumer {
|
|
|
304
309
|
inputElement.addEventListener('change', onInputChange);
|
|
305
310
|
|
|
306
311
|
let onInputKeyUp;
|
|
307
|
-
if (inputElement.type !== 'checkbox') {
|
|
312
|
+
if (inputElement.type !== 'checkbox' && inputElement.type !== 'select') {
|
|
308
313
|
onInputKeyUp = async (event) => {
|
|
309
314
|
debounce(async () => {
|
|
310
315
|
try {
|