cotomy 0.1.73 → 0.2.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/LICENSE +20 -20
- package/README.md +101 -93
- package/dist/browser/cotomy.js +91 -41
- package/dist/browser/cotomy.js.map +1 -1
- package/dist/browser/cotomy.min.js +1 -1
- package/dist/browser/cotomy.min.js.map +1 -1
- package/dist/cjs/index.cjs +1 -1
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/api.js +39 -13
- package/dist/esm/api.js.map +1 -1
- package/dist/esm/form.js +47 -27
- package/dist/esm/form.js.map +1 -1
- package/dist/esm/view.js +4 -0
- package/dist/esm/view.js.map +1 -1
- package/dist/types/api.d.ts +5 -0
- package/dist/types/form.d.ts +9 -7
- package/dist/types/view.d.ts +1 -0
- package/package.json +61 -61
package/LICENSE
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
The MIT License (MIT)
|
|
2
|
-
Copyright (c) 2025 Yasuhiro Arakawa
|
|
3
|
-
|
|
4
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
-
in the Software without restriction, including without limitation the rights
|
|
7
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
-
furnished to do so, subject to the following conditions:
|
|
10
|
-
|
|
11
|
-
The above copyright notice and this permission notice shall be included in all
|
|
12
|
-
copies or substantial portions of the Software.
|
|
13
|
-
|
|
14
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
-
SOFTWARE.
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
Copyright (c) 2025 Yasuhiro Arakawa
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
# Cotomy
|
|
2
|
-
|
|
3
|
-
> This library targets ES2020+.
|
|
4
|
-
> For older browsers (e.g. iOS 13 or IE), you will need a Polyfill such as `core-js`.
|
|
5
|
-
|
|
6
|
-
**Cotomy** is a lightweight framework for managing form behavior and page controllers in web applications.
|
|
7
|
-
It is suitable for both SPAs (Single Page Applications) and traditional web apps requiring dynamic form operations.
|
|
8
|
-
|
|
9
|
-
⚠️ **Warning**: This project is in early development. APIs may change without notice until version 1.0.0.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
To install Cotomy in your project, run the following command:
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
npm i cotomy
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
## Usage
|
|
19
|
-
|
|
1
|
+
# Cotomy
|
|
2
|
+
|
|
3
|
+
> This library targets ES2020+.
|
|
4
|
+
> For older browsers (e.g. iOS 13 or IE), you will need a Polyfill such as `core-js`.
|
|
5
|
+
|
|
6
|
+
**Cotomy** is a lightweight framework for managing form behavior and page controllers in web applications.
|
|
7
|
+
It is suitable for both SPAs (Single Page Applications) and traditional web apps requiring dynamic form operations.
|
|
8
|
+
|
|
9
|
+
⚠️ **Warning**: This project is in early development. APIs may change without notice until version 1.0.0.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
To install Cotomy in your project, run the following command:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm i cotomy
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
20
|
Cotomy will continue to expand with more detailed usage instructions and code examples added to the README in the future.
|
|
21
21
|
For the latest updates, please check the official documentation or repository regularly.
|
|
22
22
|
|
|
@@ -75,6 +75,7 @@ The View layer provides thin wrappers around DOM elements and window events.
|
|
|
75
75
|
- `append(child): this` / `prepend(child): this` / `appendAll(children): this`
|
|
76
76
|
- `insertBefore(sibling): this` / `insertAfter(sibling): this`
|
|
77
77
|
- `appendTo(target): this` / `prependTo(target): this`
|
|
78
|
+
- `clone(type?): CotomyElement` — Returns a deep-cloned element, optionally typed
|
|
78
79
|
- `clear(): this` — Removes all descendants and text
|
|
79
80
|
- `remove(): void`
|
|
80
81
|
- Geometry & visibility
|
|
@@ -170,8 +171,8 @@ The Form layer builds on `CotomyElement` for common form flows.
|
|
|
170
171
|
- `initialized: boolean` — Set after `initialize()`
|
|
171
172
|
- `submitAsync(): Promise<void>` — Abstract in base
|
|
172
173
|
- Routing & reload
|
|
173
|
-
- `method
|
|
174
|
-
- `actionUrl
|
|
174
|
+
- `method: string` — Getter that defaults to `get` in base; specialized in subclasses
|
|
175
|
+
- `actionUrl: string` — Getter that defaults to the `action` attribute or current path
|
|
175
176
|
- `reloadAsync(): Promise<void>` — Page reload using `CotomyWindow`
|
|
176
177
|
- `autoReload: boolean` — Backed by `data-cotomy-autoreload` (default true)
|
|
177
178
|
|
|
@@ -184,8 +185,8 @@ The Form layer builds on `CotomyElement` for common form flows.
|
|
|
184
185
|
|
|
185
186
|
- API integration
|
|
186
187
|
- `apiClient(): CotomyApi` — Override to inject a client; default creates a new one
|
|
187
|
-
- `actionUrl
|
|
188
|
-
- `method
|
|
188
|
+
- `actionUrl: string` — Uses `action` attribute
|
|
189
|
+
- `method: string` — Defaults to `post`
|
|
189
190
|
- `formData(): FormData` — Builds from form, converts `datetime-local` to ISO (UTC offset)
|
|
190
191
|
- `submitAsync()` — Calls `submitToApiAsync(formData)`
|
|
191
192
|
- `submitToApiAsync(formData): Promise<CotomyApiResponse>` — Uses `CotomyApi.submitAsync`
|
|
@@ -199,8 +200,8 @@ The Form layer builds on `CotomyElement` for common form flows.
|
|
|
199
200
|
- Surrogate key flow
|
|
200
201
|
- `data-cotomy-entity-key` — Holds the entity identifier if present
|
|
201
202
|
- `data-cotomy-identify` — Defaults to true; when true and `201 Created` is returned, the form extracts the key from `Location` and stores it in `data-cotomy-entity-key`
|
|
202
|
-
- `actionUrl
|
|
203
|
-
- `method
|
|
203
|
+
- `actionUrl` — Appends the key to the base `action` when present; otherwise normalizes trailing slash for collection URL
|
|
204
|
+
- `method` — `put` when key exists; otherwise `post` (unless `method` attribute is explicitly set)
|
|
204
205
|
|
|
205
206
|
### CotomyEntityFillApiForm
|
|
206
207
|
|
|
@@ -208,14 +209,21 @@ The Form layer builds on `CotomyElement` for common form flows.
|
|
|
208
209
|
- `initialize()` — Adds default fillers and triggers `loadAsync()` on `CotomyWindow.ready`
|
|
209
210
|
- `reloadAsync()` — Alias to `loadAsync()`
|
|
210
211
|
- `loadAsync(): Promise<CotomyApiResponse>` — Calls `CotomyApi.getAsync` when `canLoad()` is true
|
|
211
|
-
- `loadActionUrl(): string` — Defaults to `actionUrl
|
|
212
|
+
- `loadActionUrl(): string` — Defaults to `actionUrl`; override for custom endpoints
|
|
212
213
|
- `canLoad(): boolean` — Defaults to `hasEntityKey`
|
|
213
214
|
- Naming & binding
|
|
214
215
|
- `bindNameGenerator(): ICotomyBindNameGenerator` — Defaults to `CotomyBracketBindNameGenerator` (`user[name]`)
|
|
215
216
|
- `renderer(): CotomyViewRenderer` — Applies `[data-cotomy-bind]` to view elements
|
|
216
|
-
|
|
217
|
+
- `filler(type, (input, value))` — Register fillers; defaults provided for `datetime-local`, `checkbox`, `radio`
|
|
217
218
|
- Fills non-array, non-object fields by matching input/select/textarea `name`
|
|
218
219
|
|
|
220
|
+
#### Array binding
|
|
221
|
+
|
|
222
|
+
- Both `CotomyViewRenderer.applyAsync` and `CotomyEntityFillApiForm.fillAsync` resolve array elements by index via the active `ICotomyBindNameGenerator` (dot style → `items[0].name`, bracket style → `items[0][name]`).
|
|
223
|
+
- Cotomy does **not** create or clone templates for you. Prepare the necessary DOM (e.g., table rows, list items, individual inputs) ahead of time, then call `fillAsync`/`applyAsync` to populate the values.
|
|
224
|
+
- Primitive arrays (strings, numbers, booleans, etc.) are treated the same way—have matching `[data-cotomy-bind]`/`name` attributes ready for every index you want to show.
|
|
225
|
+
- If you need dynamic row counts, generate the markup yourself before invoking Cotomy; the framework purposely avoids mutating the structure so it does not get in your way.
|
|
226
|
+
|
|
219
227
|
Example:
|
|
220
228
|
|
|
221
229
|
```ts
|
|
@@ -228,69 +236,69 @@ form.submitFailed(e => console.warn("Submit failed", e.response.status));
|
|
|
228
236
|
```
|
|
229
237
|
|
|
230
238
|
### Entity API forms
|
|
231
|
-
|
|
232
|
-
`CotomyEntityApiForm` targets REST endpoints that identify records with a single surrogate key.
|
|
233
|
-
Attach `data-cotomy-entity-key="<id>"` to the form when editing an existing entity; omit the attribute (or leave it empty) to issue a `POST` to the base `action` URL.
|
|
234
|
-
On `201 Created`, the form reads the `Location` header and stores the generated key back into `data-cotomy-entity-key`, enabling subsequent `PUT` submissions.
|
|
235
|
-
Composite or natural keys are no longer supported—migrate any legacy markup that relied on `data-cotomy-keyindex` or multiple key inputs to the new surrogate-key flow.
|
|
236
|
-
When you must integrate with endpoints that still expect natural identifiers, subclass `CotomyEntityApiForm`/`CotomyEntityFillApiForm`, override `canLoad()` to supply your own load condition, and adjust `loadActionUrl()` (plus any submission hooks) to build the appropriate URL fragments.
|
|
237
|
-
|
|
238
|
-
The core of Cotomy is `CotomyElement`, which is constructed as a wrapper for `Element`.
|
|
239
|
-
By passing HTML and CSS strings to the constructor, it is possible to generate Element designs with a limited scope.
|
|
240
|
-
|
|
241
|
-
```typescript
|
|
242
|
-
const ce = new CotomyElement({
|
|
243
|
-
html: /* html */`
|
|
244
|
-
<div>
|
|
245
|
-
<p>Text</p>
|
|
246
|
-
</div>
|
|
247
|
-
`,
|
|
248
|
-
css: /* css */`
|
|
249
|
-
[scope] {
|
|
250
|
-
display: block;
|
|
251
|
-
}
|
|
252
|
-
[scope] > p {
|
|
253
|
-
text-align: center;
|
|
254
|
-
}
|
|
255
|
-
`
|
|
256
|
-
});
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
- `"display HTML in character literals with color coding"` → `"syntax highlighting for embedded HTML"`
|
|
260
|
-
- `"generate Element designs with a limited scope"` → `"generate scoped DOM elements with associated styles"`
|
|
261
|
-
|
|
262
|
-
## Development
|
|
263
|
-
|
|
264
|
-
Cotomy ships with both ESM (`dist/esm`) and CommonJS (`dist/cjs`) builds, plus generated type definitions in `dist/types`.
|
|
265
|
-
For direct `<script>` usage, browser-ready bundles are available at `dist/browser/cotomy.js` and `dist/browser/cotomy.min.js` (also served via the npm `unpkg` entry).
|
|
266
|
-
Include the minified build like so:
|
|
267
|
-
|
|
268
|
-
```html
|
|
269
|
-
<script src="https://unpkg.com/cotomy/dist/browser/cotomy.min.js"></script>
|
|
270
|
-
<script>
|
|
271
|
-
const el = new Cotomy.CotomyElement("<div>Hello</div>");
|
|
272
|
-
document.body.appendChild(el.element);
|
|
273
|
-
</script>
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
Run the build to refresh every target bundle:
|
|
277
|
-
|
|
278
|
-
```bash
|
|
279
|
-
npm install
|
|
280
|
-
npm run build
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
The Vitest-based test suite can be executed via:
|
|
284
|
-
|
|
285
|
-
```bash
|
|
286
|
-
npx vitest run
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
## License
|
|
290
|
-
|
|
291
|
-
This project is licensed under the [MIT License](LICENSE).
|
|
292
|
-
|
|
293
|
-
## Contact
|
|
294
|
-
|
|
295
|
-
You can reach out to me at: [yshr1920@gmail.com](mailto:yshr1920@gmail.com)
|
|
296
|
-
GitHub repository: [https://github.com/yshr1920/cotomy](https://github.com/yshr1920/cotomy)
|
|
239
|
+
|
|
240
|
+
`CotomyEntityApiForm` targets REST endpoints that identify records with a single surrogate key.
|
|
241
|
+
Attach `data-cotomy-entity-key="<id>"` to the form when editing an existing entity; omit the attribute (or leave it empty) to issue a `POST` to the base `action` URL.
|
|
242
|
+
On `201 Created`, the form reads the `Location` header and stores the generated key back into `data-cotomy-entity-key`, enabling subsequent `PUT` submissions.
|
|
243
|
+
Composite or natural keys are no longer supported—migrate any legacy markup that relied on `data-cotomy-keyindex` or multiple key inputs to the new surrogate-key flow.
|
|
244
|
+
When you must integrate with endpoints that still expect natural identifiers, subclass `CotomyEntityApiForm`/`CotomyEntityFillApiForm`, override `canLoad()` to supply your own load condition, and adjust `loadActionUrl()` (plus any submission hooks) to build the appropriate URL fragments.
|
|
245
|
+
|
|
246
|
+
The core of Cotomy is `CotomyElement`, which is constructed as a wrapper for `Element`.
|
|
247
|
+
By passing HTML and CSS strings to the constructor, it is possible to generate Element designs with a limited scope.
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
const ce = new CotomyElement({
|
|
251
|
+
html: /* html */`
|
|
252
|
+
<div>
|
|
253
|
+
<p>Text</p>
|
|
254
|
+
</div>
|
|
255
|
+
`,
|
|
256
|
+
css: /* css */`
|
|
257
|
+
[scope] {
|
|
258
|
+
display: block;
|
|
259
|
+
}
|
|
260
|
+
[scope] > p {
|
|
261
|
+
text-align: center;
|
|
262
|
+
}
|
|
263
|
+
`
|
|
264
|
+
});
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
- `"display HTML in character literals with color coding"` → `"syntax highlighting for embedded HTML"`
|
|
268
|
+
- `"generate Element designs with a limited scope"` → `"generate scoped DOM elements with associated styles"`
|
|
269
|
+
|
|
270
|
+
## Development
|
|
271
|
+
|
|
272
|
+
Cotomy ships with both ESM (`dist/esm`) and CommonJS (`dist/cjs`) builds, plus generated type definitions in `dist/types`.
|
|
273
|
+
For direct `<script>` usage, browser-ready bundles are available at `dist/browser/cotomy.js` and `dist/browser/cotomy.min.js` (also served via the npm `unpkg` entry).
|
|
274
|
+
Include the minified build like so:
|
|
275
|
+
|
|
276
|
+
```html
|
|
277
|
+
<script src="https://unpkg.com/cotomy/dist/browser/cotomy.min.js"></script>
|
|
278
|
+
<script>
|
|
279
|
+
const el = new Cotomy.CotomyElement("<div>Hello</div>");
|
|
280
|
+
document.body.appendChild(el.element);
|
|
281
|
+
</script>
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Run the build to refresh every target bundle:
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
npm install
|
|
288
|
+
npm run build
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
The Vitest-based test suite can be executed via:
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
npx vitest run
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## License
|
|
298
|
+
|
|
299
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
300
|
+
|
|
301
|
+
## Contact
|
|
302
|
+
|
|
303
|
+
You can reach out to me at: [yshr1920@gmail.com](mailto:yshr1920@gmail.com)
|
|
304
|
+
GitHub repository: [https://github.com/yshr1920/cotomy](https://github.com/yshr1920/cotomy)
|
package/dist/browser/cotomy.js
CHANGED
|
@@ -174,7 +174,7 @@ module.exports = cuid;
|
|
|
174
174
|
/***/ 826:
|
|
175
175
|
/***/ (function(module) {
|
|
176
176
|
|
|
177
|
-
!function(t,i){ true?module.exports=i():0}(this,(function(){"use strict";var t="minute",i=/[+-]\d\d(?::?\d\d)?/g,e=/([+-]|\d\d)/g;return function(s,f,n){var u=f.prototype;n.utc=function(t){var i={date:t,utc:!0,args:arguments};return new f(i)},u.utc=function(i){var e=n(this.toDate(),{locale:this.$L,utc:!0});return i?e.add(this.utcOffset(),t):e},u.local=function(){return n(this.toDate(),{locale:this.$L,utc:!1})};var
|
|
177
|
+
!function(t,i){ true?module.exports=i():0}(this,(function(){"use strict";var t="minute",i=/[+-]\d\d(?::?\d\d)?/g,e=/([+-]|\d\d)/g;return function(s,f,n){var u=f.prototype;n.utc=function(t){var i={date:t,utc:!0,args:arguments};return new f(i)},u.utc=function(i){var e=n(this.toDate(),{locale:this.$L,utc:!0});return i?e.add(this.utcOffset(),t):e},u.local=function(){return n(this.toDate(),{locale:this.$L,utc:!1})};var o=u.parse;u.parse=function(t){t.utc&&(this.$u=!0),this.$utils().u(t.$offset)||(this.$offset=t.$offset),o.call(this,t)};var r=u.init;u.init=function(){if(this.$u){var t=this.$d;this.$y=t.getUTCFullYear(),this.$M=t.getUTCMonth(),this.$D=t.getUTCDate(),this.$W=t.getUTCDay(),this.$H=t.getUTCHours(),this.$m=t.getUTCMinutes(),this.$s=t.getUTCSeconds(),this.$ms=t.getUTCMilliseconds()}else r.call(this)};var a=u.utcOffset;u.utcOffset=function(s,f){var n=this.$utils().u;if(n(s))return this.$u?0:n(this.$offset)?a.call(this):this.$offset;if("string"==typeof s&&(s=function(t){void 0===t&&(t="");var s=t.match(i);if(!s)return null;var f=(""+s[0]).match(e)||["-",0,0],n=f[0],u=60*+f[1]+ +f[2];return 0===u?0:"+"===n?u:-u}(s),null===s))return this;var u=Math.abs(s)<=16?60*s:s,o=this;if(f)return o.$offset=u,o.$u=0===s,o;if(0!==s){var r=this.$u?this.toDate().getTimezoneOffset():-1*this.utcOffset();(o=this.local().add(u+r,t)).$offset=u,o.$x.$localOffset=r}else o=this.utc();return o};var h=u.format;u.format=function(t){var i=t||(this.$u?"YYYY-MM-DDTHH:mm:ss[Z]":"");return h.call(this,i)},u.valueOf=function(){var t=this.$utils().u(this.$offset)?0:this.$offset+(this.$x.$localOffset||this.$d.getTimezoneOffset());return this.$d.valueOf()-6e4*t},u.isUTC=function(){return!!this.$u},u.toISOString=function(){return this.toDate().toISOString()},u.toString=function(){return this.toDate().toUTCString()};var l=u.toDate;u.toDate=function(t){return"s"===t&&this.$offset?n(this.format("YYYY-MM-DD HH:mm:ss:SSS")).toDate():l.call(this)};var c=u.diff;u.diff=function(t,i,e){if(t&&this.$u===t.$u)return c.call(this,t,i,e);var s=this.local(),f=n(t).local();return c.call(s,f,i,e)}}}));
|
|
178
178
|
|
|
179
179
|
/***/ })
|
|
180
180
|
|
|
@@ -994,6 +994,10 @@ class CotomyElement {
|
|
|
994
994
|
get element() {
|
|
995
995
|
return this._element;
|
|
996
996
|
}
|
|
997
|
+
clone(type) {
|
|
998
|
+
const ctor = (type ?? CotomyElement);
|
|
999
|
+
return new ctor(this.element.cloneNode(true));
|
|
1000
|
+
}
|
|
997
1001
|
get tagname() {
|
|
998
1002
|
return this.element.tagName.toLowerCase();
|
|
999
1003
|
}
|
|
@@ -2116,11 +2120,17 @@ class CotomyBracketBindNameGenerator {
|
|
|
2116
2120
|
create(name, parent) {
|
|
2117
2121
|
return parent ? `${parent}[${name}]` : name;
|
|
2118
2122
|
}
|
|
2123
|
+
createIndex(parent, index) {
|
|
2124
|
+
return this.create(String(index), parent);
|
|
2125
|
+
}
|
|
2119
2126
|
}
|
|
2120
2127
|
class CotomyDotBindNameGenerator {
|
|
2121
2128
|
create(name, parent) {
|
|
2122
2129
|
return parent ? `${parent}.${name}` : name;
|
|
2123
2130
|
}
|
|
2131
|
+
createIndex(parent, index) {
|
|
2132
|
+
return parent ? `${parent}[${index}]` : `[${index}]`;
|
|
2133
|
+
}
|
|
2124
2134
|
}
|
|
2125
2135
|
class CotomyViewRenderer {
|
|
2126
2136
|
constructor(element, bindNameGenerator) {
|
|
@@ -2184,30 +2194,50 @@ class CotomyViewRenderer {
|
|
|
2184
2194
|
}
|
|
2185
2195
|
return this;
|
|
2186
2196
|
}
|
|
2197
|
+
bindPrimitiveValue(propertyName, value) {
|
|
2198
|
+
this.element.find(`[data-cotomy-bind="${propertyName}" i]`).forEach(element => {
|
|
2199
|
+
if (CotomyDebugSettings.isEnabled(CotomyDebugFeature.Bind)) {
|
|
2200
|
+
console.debug(`Binding data to element [data-cotomy-bind="${propertyName}"]:`, value);
|
|
2201
|
+
}
|
|
2202
|
+
const type = element.attribute("data-cotomy-bindtype")?.toLowerCase();
|
|
2203
|
+
if (type && this._renderers[type]) {
|
|
2204
|
+
this._renderers[type](element, value);
|
|
2205
|
+
}
|
|
2206
|
+
else {
|
|
2207
|
+
element.text = String(value ?? "");
|
|
2208
|
+
}
|
|
2209
|
+
});
|
|
2210
|
+
}
|
|
2211
|
+
async applyArrayAsync(values, propertyName) {
|
|
2212
|
+
for (let index = 0; index < values.length; index++) {
|
|
2213
|
+
const item = values[index];
|
|
2214
|
+
const itemName = this.bindNameGenerator.createIndex(propertyName, index);
|
|
2215
|
+
if (Array.isArray(item)) {
|
|
2216
|
+
await this.applyArrayAsync(item, itemName);
|
|
2217
|
+
continue;
|
|
2218
|
+
}
|
|
2219
|
+
if (item && typeof item === "object") {
|
|
2220
|
+
await this.applyObjectAsync(item, itemName);
|
|
2221
|
+
continue;
|
|
2222
|
+
}
|
|
2223
|
+
this.bindPrimitiveValue(itemName, item);
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2187
2226
|
async applyObjectAsync(target, propertyName = undefined) {
|
|
2188
2227
|
if (!propertyName) {
|
|
2189
2228
|
this.element.find("[data-cotomy-bind]").forEach(e => e.clear());
|
|
2190
2229
|
}
|
|
2191
2230
|
for (const [key, value] of Object.entries(await target)) {
|
|
2192
2231
|
const pname = this.bindNameGenerator.create(key, propertyName);
|
|
2193
|
-
if (Array.isArray(value))
|
|
2232
|
+
if (Array.isArray(value)) {
|
|
2233
|
+
await this.applyArrayAsync(value, pname);
|
|
2194
2234
|
continue;
|
|
2235
|
+
}
|
|
2195
2236
|
if (value && typeof value === "object") {
|
|
2196
2237
|
await this.applyObjectAsync(value, pname);
|
|
2197
2238
|
continue;
|
|
2198
2239
|
}
|
|
2199
|
-
this.
|
|
2200
|
-
if (CotomyDebugSettings.isEnabled(CotomyDebugFeature.Bind)) {
|
|
2201
|
-
console.debug(`Binding data to element [data-cotomy-bind="${pname}"]:`, value);
|
|
2202
|
-
}
|
|
2203
|
-
const type = element.attribute("data-cotomy-bindtype")?.toLowerCase();
|
|
2204
|
-
if (type && this._renderers[type]) {
|
|
2205
|
-
this._renderers[type](element, value);
|
|
2206
|
-
}
|
|
2207
|
-
else {
|
|
2208
|
-
element.text = String(value ?? "");
|
|
2209
|
-
}
|
|
2210
|
-
});
|
|
2240
|
+
this.bindPrimitiveValue(pname, value);
|
|
2211
2241
|
}
|
|
2212
2242
|
}
|
|
2213
2243
|
async applyAsync(respose) {
|
|
@@ -2408,10 +2438,10 @@ class CotomyForm extends CotomyElement {
|
|
|
2408
2438
|
generateId(prefix = "__cotomy_form__") {
|
|
2409
2439
|
return super.generateId(prefix);
|
|
2410
2440
|
}
|
|
2411
|
-
method() {
|
|
2441
|
+
get method() {
|
|
2412
2442
|
return this.attribute("method") ?? "get";
|
|
2413
2443
|
}
|
|
2414
|
-
actionUrl() {
|
|
2444
|
+
get actionUrl() {
|
|
2415
2445
|
return this.attribute("action") ?? location.pathname + location.search;
|
|
2416
2446
|
}
|
|
2417
2447
|
async reloadAsync() {
|
|
@@ -2444,11 +2474,11 @@ class CotomyForm extends CotomyElement {
|
|
|
2444
2474
|
}
|
|
2445
2475
|
}
|
|
2446
2476
|
class CotomyQueryForm extends CotomyForm {
|
|
2447
|
-
method() {
|
|
2477
|
+
get method() {
|
|
2448
2478
|
return "get";
|
|
2449
2479
|
}
|
|
2450
2480
|
async submitAsync() {
|
|
2451
|
-
const url = this.actionUrl
|
|
2481
|
+
const url = this.actionUrl;
|
|
2452
2482
|
const queryParams = {};
|
|
2453
2483
|
const queryString = url.split("?")[1];
|
|
2454
2484
|
if (queryString) {
|
|
@@ -2490,7 +2520,7 @@ class CotomyApiForm extends CotomyForm {
|
|
|
2490
2520
|
apiClient() {
|
|
2491
2521
|
return new CotomyApi();
|
|
2492
2522
|
}
|
|
2493
|
-
actionUrl() {
|
|
2523
|
+
get actionUrl() {
|
|
2494
2524
|
return this.attribute("action");
|
|
2495
2525
|
}
|
|
2496
2526
|
apiFailed(handle) {
|
|
@@ -2517,7 +2547,7 @@ class CotomyApiForm extends CotomyForm {
|
|
|
2517
2547
|
console.error("Submit failed:", response);
|
|
2518
2548
|
}
|
|
2519
2549
|
}
|
|
2520
|
-
method() {
|
|
2550
|
+
get method() {
|
|
2521
2551
|
return this.attribute("method") ?? "post";
|
|
2522
2552
|
}
|
|
2523
2553
|
formData() {
|
|
@@ -2546,8 +2576,8 @@ class CotomyApiForm extends CotomyForm {
|
|
|
2546
2576
|
const api = this.apiClient();
|
|
2547
2577
|
try {
|
|
2548
2578
|
const response = await api.submitAsync({
|
|
2549
|
-
method: this.method
|
|
2550
|
-
action: this.actionUrl
|
|
2579
|
+
method: this.method,
|
|
2580
|
+
action: this.actionUrl,
|
|
2551
2581
|
body: formData,
|
|
2552
2582
|
});
|
|
2553
2583
|
return response;
|
|
@@ -2571,7 +2601,7 @@ class CotomyEntityApiForm extends CotomyApiForm {
|
|
|
2571
2601
|
get hasEntityKey() {
|
|
2572
2602
|
return !!this.entityKey;
|
|
2573
2603
|
}
|
|
2574
|
-
actionUrl() {
|
|
2604
|
+
get actionUrl() {
|
|
2575
2605
|
const action = this.attribute("action");
|
|
2576
2606
|
const normalized = action.replace(/\/+$/, "");
|
|
2577
2607
|
if (!this.entityKey) {
|
|
@@ -2579,7 +2609,7 @@ class CotomyEntityApiForm extends CotomyApiForm {
|
|
|
2579
2609
|
}
|
|
2580
2610
|
return `${normalized}/${encodeURIComponent(this.entityKey)}`;
|
|
2581
2611
|
}
|
|
2582
|
-
method() {
|
|
2612
|
+
get method() {
|
|
2583
2613
|
if (this.hasAttribute("method") && this.attribute("method") !== "") {
|
|
2584
2614
|
return this.attribute("method");
|
|
2585
2615
|
}
|
|
@@ -2621,7 +2651,7 @@ class CotomyEntityApiForm extends CotomyApiForm {
|
|
|
2621
2651
|
this.attribute("data-cotomy-entity-key", addedParts[0]);
|
|
2622
2652
|
}
|
|
2623
2653
|
else {
|
|
2624
|
-
const msg = `Location does not contain a single entity key segment.
|
|
2654
|
+
const msg = `Location does not contain a single entity key segment.
|
|
2625
2655
|
action="${baseAction}", location="${locPath}", added=["${addedParts.join('","')}"]`;
|
|
2626
2656
|
throw new Error(msg);
|
|
2627
2657
|
}
|
|
@@ -2679,7 +2709,7 @@ class CotomyEntityFillApiForm extends CotomyEntityApiForm {
|
|
|
2679
2709
|
await this.loadAsync();
|
|
2680
2710
|
}
|
|
2681
2711
|
loadActionUrl() {
|
|
2682
|
-
return this.actionUrl
|
|
2712
|
+
return this.actionUrl;
|
|
2683
2713
|
}
|
|
2684
2714
|
bindNameGenerator() {
|
|
2685
2715
|
return new CotomyBracketBindNameGenerator();
|
|
@@ -2710,32 +2740,52 @@ class CotomyEntityFillApiForm extends CotomyEntityApiForm {
|
|
|
2710
2740
|
throw error;
|
|
2711
2741
|
}
|
|
2712
2742
|
}
|
|
2743
|
+
applyValueToInputs(pname, value) {
|
|
2744
|
+
this.find(`input[name="${pname}" i]:not([data-cotomy-fill="false"]):not([multiple]),
|
|
2745
|
+
textarea[name="${pname}" i]:not([data-cotomy-fill="false"]),
|
|
2746
|
+
select[name="${pname}" i]:not([data-cotomy-fill="false"]):not([multiple])`).forEach(input => {
|
|
2747
|
+
if (CotomyDebugSettings.isEnabled(CotomyDebugFeature.Fill)) {
|
|
2748
|
+
console.debug(`Filling input[name="${pname}"] with value:`, value);
|
|
2749
|
+
}
|
|
2750
|
+
const type = input.attribute("type")?.toLowerCase();
|
|
2751
|
+
if (type && this._fillers[type]) {
|
|
2752
|
+
this._fillers[type](input, value);
|
|
2753
|
+
}
|
|
2754
|
+
else {
|
|
2755
|
+
input.value = String(value || "");
|
|
2756
|
+
}
|
|
2757
|
+
});
|
|
2758
|
+
}
|
|
2759
|
+
async fillArrayAsync(bindNameGenerator, values, propertyName) {
|
|
2760
|
+
for (let index = 0; index < values.length; index++) {
|
|
2761
|
+
const item = values[index];
|
|
2762
|
+
const itemName = bindNameGenerator.createIndex(propertyName, index);
|
|
2763
|
+
if (Array.isArray(item)) {
|
|
2764
|
+
await this.fillArrayAsync(bindNameGenerator, item, itemName);
|
|
2765
|
+
continue;
|
|
2766
|
+
}
|
|
2767
|
+
if (item && typeof item === "object") {
|
|
2768
|
+
await this.fillObjectAsync(bindNameGenerator, item, itemName);
|
|
2769
|
+
continue;
|
|
2770
|
+
}
|
|
2771
|
+
this.applyValueToInputs(itemName, item);
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2713
2774
|
async fillObjectAsync(bindNameGenerator, target, propertyName = undefined) {
|
|
2714
2775
|
for (const [key, value] of Object.entries(target)) {
|
|
2715
2776
|
if (key.endsWith('[]')) {
|
|
2716
2777
|
continue;
|
|
2717
2778
|
}
|
|
2718
2779
|
const pname = bindNameGenerator.create(key, propertyName);
|
|
2719
|
-
if (Array.isArray(value))
|
|
2780
|
+
if (Array.isArray(value)) {
|
|
2781
|
+
await this.fillArrayAsync(bindNameGenerator, value, pname);
|
|
2720
2782
|
continue;
|
|
2783
|
+
}
|
|
2721
2784
|
if (value && typeof value === "object") {
|
|
2722
2785
|
await this.fillObjectAsync(bindNameGenerator, value, pname);
|
|
2723
2786
|
continue;
|
|
2724
2787
|
}
|
|
2725
|
-
this.
|
|
2726
|
-
textarea[name="${pname}" i]:not([data-cotomy-fill="false"]),
|
|
2727
|
-
select[name="${pname}" i]:not([data-cotomy-fill="false"]):not([multiple])`).forEach(input => {
|
|
2728
|
-
if (CotomyDebugSettings.isEnabled(CotomyDebugFeature.Fill)) {
|
|
2729
|
-
console.debug(`Filling input[name="${pname}"] with value:`, value);
|
|
2730
|
-
}
|
|
2731
|
-
const type = input.attribute("type")?.toLowerCase();
|
|
2732
|
-
if (type && this._fillers[type]) {
|
|
2733
|
-
this._fillers[type](input, value);
|
|
2734
|
-
}
|
|
2735
|
-
else {
|
|
2736
|
-
input.value = String(value || "");
|
|
2737
|
-
}
|
|
2738
|
-
});
|
|
2788
|
+
this.applyValueToInputs(pname, value);
|
|
2739
2789
|
}
|
|
2740
2790
|
}
|
|
2741
2791
|
async fillAsync(response) {
|