@logimaxx/kviews.js 1.2.3

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/dist/index.js ADDED
@@ -0,0 +1,4039 @@
1
+ // src/utils.js
2
+ function dbg() {
3
+ if (typeof kviewsLogLevel !== "undefined" && kviewsLogLevel >= 3) {
4
+ console.trace(...arguments);
5
+ }
6
+ }
7
+ function log() {
8
+ if (typeof kviewsLogLevel !== "undefined" && kviewsLogLevel >= 2) {
9
+ console.log(...arguments);
10
+ }
11
+ }
12
+ function error() {
13
+ if (typeof kviewsLogLevel !== "undefined" && kviewsLogLevel >= 1) {
14
+ console.error(...arguments);
15
+ }
16
+ }
17
+ function trace() {
18
+ if (typeof kviewsLogLevel !== "undefined" && kviewsLogLevel >= 4) {
19
+ console.trace(...arguments);
20
+ }
21
+ }
22
+ function uid() {
23
+ return "uid_" + Math.random().toString(36).substr(2, 9);
24
+ }
25
+ function parseOptions(options) {
26
+ if (typeof options === "undefined") {
27
+ return {};
28
+ }
29
+ if (options.constructor === Object) {
30
+ return options;
31
+ }
32
+ throw new Error("Invalid options", options);
33
+ }
34
+ function deepmerge(target, source, optionsArgument) {
35
+ function defaultArrayMerge(target2, source2, optionsArgument2) {
36
+ let destination = target2.slice();
37
+ source2.forEach(function(e, i) {
38
+ if (typeof destination[i] === "undefined") {
39
+ destination[i] = cloneIfNecessary(e, optionsArgument2);
40
+ } else if (isMergeableObject(e)) {
41
+ destination[i] = deepmerge(target2[i], e, optionsArgument2);
42
+ } else if (target2.indexOf(e) === -1) {
43
+ destination.push(cloneIfNecessary(e, optionsArgument2));
44
+ }
45
+ });
46
+ return destination;
47
+ }
48
+ function isMergeableObject(val) {
49
+ var nonNullObject = val && typeof val === "object";
50
+ return nonNullObject && Object.prototype.toString.call(val) !== "[object RegExp]" && Object.prototype.toString.call(val) !== "[object Date]";
51
+ }
52
+ function emptyTarget(val) {
53
+ return Array.isArray(val) ? [] : {};
54
+ }
55
+ function cloneIfNecessary(value, optionsArgument2) {
56
+ let clone = optionsArgument2 && optionsArgument2.clone === true;
57
+ return clone && isMergeableObject(value) ? deepmerge(emptyTarget(value), value, optionsArgument2) : value;
58
+ }
59
+ function mergeObject(target2, source2, optionsArgument2) {
60
+ let destination = {};
61
+ if (isMergeableObject(target2)) {
62
+ Object.keys(target2).forEach(function(key) {
63
+ destination[key] = cloneIfNecessary(target2[key], optionsArgument2);
64
+ });
65
+ }
66
+ Object.keys(source2).forEach(function(key) {
67
+ if (!isMergeableObject(source2[key]) || !target2[key]) {
68
+ destination[key] = cloneIfNecessary(source2[key], optionsArgument2);
69
+ } else {
70
+ destination[key] = deepmerge(target2[key], source2[key], optionsArgument2);
71
+ }
72
+ });
73
+ return destination;
74
+ }
75
+ let array = Array.isArray(source);
76
+ let options = optionsArgument || { arrayMerge: defaultArrayMerge };
77
+ let arrayMerge = options.arrayMerge || defaultArrayMerge;
78
+ if (array) {
79
+ return Array.isArray(target) ? arrayMerge(target, source, optionsArgument) : cloneIfNecessary(source, optionsArgument);
80
+ } else {
81
+ return mergeObject(target, source, optionsArgument);
82
+ }
83
+ }
84
+ deepmerge.all = function deepmergeAll(array, optionsArgument) {
85
+ if (!Array.isArray(array) || array.length < 2) {
86
+ throw new Error("first argument should be an array with at least two elements");
87
+ }
88
+ return array.reduce(function(prev, next) {
89
+ return deepmerge(prev, next, optionsArgument);
90
+ });
91
+ };
92
+ function getBoundObjects(el) {
93
+ let db = {};
94
+ if (!el || $(el).length === 0) {
95
+ return db;
96
+ }
97
+ let boundData = $(el).data();
98
+ for (let key in boundData) {
99
+ if (typeof boundData[key] === "object" && key !== "instance") {
100
+ db[key] = boundData[key];
101
+ }
102
+ }
103
+ return db;
104
+ }
105
+ function template(text) {
106
+ if (typeof Handlebars !== "undefined") {
107
+ return Handlebars.compile(text);
108
+ }
109
+ throw new Error("Handlebars is required for template compilation");
110
+ }
111
+ function createOverlay(instance) {
112
+ return $(document.createElement("div")).text("Se incarca 123").addClass("komponent-overlay").data("asd", instance).attr(
113
+ "style",
114
+ "background: linear-gradient(135deg,rgb(191, 225, 205),rgb(236, 234, 232) 70%, #fca); text-align: center; position:absolute; z-index:100000;"
115
+ );
116
+ }
117
+
118
+ // src/apiBase.js
119
+ var apiBaseConfig = {
120
+ baseUrl: null,
121
+ basePath: null,
122
+ defaultHeaders: {}
123
+ };
124
+ function resolveRequestUrl(url) {
125
+ if (url == null || url === "") {
126
+ return url;
127
+ }
128
+ const s = typeof url === "string" ? url : String(url);
129
+ if (/^https?:\/\//i.test(s) || s.startsWith("//")) {
130
+ return s;
131
+ }
132
+ const base = apiBaseConfig.baseUrl || apiBaseConfig.basePath || "";
133
+ if (!base) {
134
+ return s;
135
+ }
136
+ const baseNorm = base.replace(/\/+$/, "");
137
+ const pathNorm = s.replace(/^\/+/, "");
138
+ if (!pathNorm) {
139
+ return baseNorm + "/";
140
+ }
141
+ return baseNorm + "/" + pathNorm;
142
+ }
143
+
144
+ // src/errors.js
145
+ var KViewsError = class extends Error {
146
+ constructor(message, options = {}) {
147
+ super(message);
148
+ this.name = "KViewsError";
149
+ this.options = options.options || {};
150
+ this.context = options.context || null;
151
+ if (Error.captureStackTrace) {
152
+ Error.captureStackTrace(this, this.constructor);
153
+ }
154
+ }
155
+ };
156
+ var KViewsHttpError = class extends KViewsError {
157
+ constructor(message, options = {}) {
158
+ super(message, options);
159
+ this.name = "KViewsHttpError";
160
+ this.status = options.status || 0;
161
+ this.statusText = options.statusText || "error";
162
+ this.responseText = options.responseText || null;
163
+ this.responseJSON = options.responseJSON || null;
164
+ this.jqXHR = options.jqXHR || null;
165
+ this.textStatus = options.textStatus || "error";
166
+ this.errorThrown = options.errorThrown || null;
167
+ }
168
+ };
169
+ var KViewsParseError = class extends KViewsError {
170
+ constructor(message, options = {}) {
171
+ super(message, options);
172
+ this.name = "KViewsParseError";
173
+ this.rawData = options.rawData || null;
174
+ this.parseStep = options.parseStep || null;
175
+ }
176
+ };
177
+ var KViewsUrlError = class extends KViewsError {
178
+ constructor(message, options = {}) {
179
+ super(message, options);
180
+ this.name = "KViewsUrlError";
181
+ this.url = options.url || null;
182
+ }
183
+ };
184
+ var KViewsNetworkError = class extends KViewsError {
185
+ constructor(message, options = {}) {
186
+ super(message, options);
187
+ this.name = "KViewsNetworkError";
188
+ this.originalError = options.originalError || null;
189
+ this.url = options.url || null;
190
+ }
191
+ };
192
+
193
+ // src/URL.js
194
+ var URL = class {
195
+ constructor(url) {
196
+ if (!url) {
197
+ throw new KViewsUrlError("URL is not provided", { url });
198
+ }
199
+ if (typeof url === "object" && url.hasOwnProperty("protocol")) {
200
+ Object.assign(this, url);
201
+ if (this.parameters && !this.parameters.toString) {
202
+ this._addParametersToString();
203
+ }
204
+ return;
205
+ }
206
+ if (url.constructor !== String) {
207
+ dbg("URL is not a string", url);
208
+ throw new KViewsUrlError("URL is not a string: " + url.toString(), { url });
209
+ }
210
+ const isAbsolute = /^[a-z]+:\/\//i.test(url);
211
+ if (isAbsolute && typeof window !== "undefined" && window.URL) {
212
+ try {
213
+ const standardUrl = new window.URL(url);
214
+ this.protocol = standardUrl.protocol ? standardUrl.protocol.replace(":", "") : null;
215
+ this.fqdn = standardUrl.hostname || null;
216
+ this.port = standardUrl.port || null;
217
+ this.path = standardUrl.pathname || null;
218
+ this.fragment = standardUrl.hash ? standardUrl.hash.replace("#", "") : null;
219
+ this.parameters = {};
220
+ if (standardUrl.search) {
221
+ const params = new URLSearchParams(standardUrl.search);
222
+ params.forEach((value, key) => {
223
+ this.parameters[key] = value;
224
+ });
225
+ }
226
+ } catch (e) {
227
+ this._parseRelativeUrl(url);
228
+ }
229
+ } else {
230
+ this._parseRelativeUrl(url);
231
+ }
232
+ this._addParametersToString();
233
+ }
234
+ /**
235
+ * Parse relative URL using regex fallback
236
+ * @private
237
+ */
238
+ _parseRelativeUrl(url) {
239
+ let regExp = /^((?:([a-z]+):)([\/]{2,3})([\w\.\-\_]+)(?::(\d+))?)?(?:(\/?[^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/i;
240
+ let parts = regExp.exec(url);
241
+ this.protocol = null;
242
+ this.fqdn = null;
243
+ this.port = null;
244
+ this.path = null;
245
+ this.parameters = {};
246
+ this.fragment = null;
247
+ if (typeof parts[2] !== "undefined") {
248
+ this.protocol = parts[2];
249
+ }
250
+ if (typeof parts[4] !== "undefined") {
251
+ this.fqdn = parts[4];
252
+ }
253
+ if (typeof parts[5] !== "undefined") {
254
+ this.port = parts[5];
255
+ }
256
+ if (typeof parts[6] !== "undefined") {
257
+ this.path = parts[6];
258
+ }
259
+ if (typeof parts[7] !== "undefined") {
260
+ try {
261
+ const params = new URLSearchParams(parts[7]);
262
+ params.forEach((value, key) => {
263
+ this.parameters[key] = value;
264
+ });
265
+ } catch (e) {
266
+ let tmp = parts[7].split("&");
267
+ tmp.forEach((item) => {
268
+ if (!item || item === "") {
269
+ return;
270
+ }
271
+ let eqPos = item.indexOf("=");
272
+ if (eqPos === -1) {
273
+ this.parameters[item] = "";
274
+ } else {
275
+ this.parameters[item.substr(0, eqPos)] = decodeURIComponent(item.substr(eqPos + 1));
276
+ }
277
+ });
278
+ }
279
+ }
280
+ if (typeof parts[8] !== "undefined") {
281
+ this.fragment = parts[8];
282
+ }
283
+ }
284
+ /**
285
+ * Add toString method to parameters object
286
+ * @private
287
+ */
288
+ _addParametersToString() {
289
+ if (!this.parameters.toString || this.parameters.toString === Object.prototype.toString) {
290
+ const self = this;
291
+ this.parameters.toString = function() {
292
+ if (typeof URLSearchParams !== "undefined") {
293
+ try {
294
+ const params = new URLSearchParams();
295
+ for (let para in this) {
296
+ if (this.hasOwnProperty(para) && para !== "toString") {
297
+ params.append(para, String(this[para]));
298
+ }
299
+ }
300
+ return params.toString();
301
+ } catch (e) {
302
+ }
303
+ }
304
+ let paras = [];
305
+ for (let para in this) {
306
+ if (this.hasOwnProperty(para) && para !== "toString") {
307
+ const value = String(this[para]);
308
+ paras.push(encodeURIComponent(para) + "=" + encodeURIComponent(value));
309
+ }
310
+ }
311
+ return paras.join("&");
312
+ };
313
+ }
314
+ }
315
+ toString() {
316
+ let str = "";
317
+ if (this.protocol && this.fqdn) {
318
+ if (typeof window !== "undefined" && window.URL) {
319
+ try {
320
+ const baseUrl = this.protocol + "://" + this.fqdn + (this.port ? ":" + this.port : "");
321
+ const url = new window.URL(this.path || "/", baseUrl);
322
+ if (this.parameters && Object.keys(this.parameters).length > 0) {
323
+ Object.getOwnPropertyNames(this.parameters).forEach((key) => {
324
+ if (key !== "toString") {
325
+ url.searchParams.set(key, this.parameters[key]);
326
+ }
327
+ });
328
+ }
329
+ if (this.fragment) {
330
+ url.hash = this.fragment;
331
+ }
332
+ return url.toString();
333
+ } catch (e) {
334
+ }
335
+ }
336
+ str += this.protocol + "://" + this.fqdn;
337
+ if (this.port) {
338
+ str += ":" + this.port;
339
+ }
340
+ }
341
+ if (this.path) {
342
+ str += this.path;
343
+ }
344
+ if (this.parameters && Object.keys(this.parameters).length > 0) {
345
+ str += "?" + this.parameters.toString();
346
+ }
347
+ if (this.fragment) {
348
+ str += "#" + this.fragment;
349
+ }
350
+ return str;
351
+ }
352
+ };
353
+ function createURL(url) {
354
+ return new URL(url);
355
+ }
356
+
357
+ // src/Storage.js
358
+ var Storage = class {
359
+ constructor(options = {}) {
360
+ let defaultOptions = {
361
+ url: null,
362
+ method: "GET"
363
+ };
364
+ options = parseOptions(options);
365
+ Object.assign(defaultOptions, options);
366
+ this.defaultOptions = defaultOptions;
367
+ }
368
+ /**
369
+ * Base sync method - uses Fetch API
370
+ */
371
+ sync(options) {
372
+ if (options.url && typeof options.url === "object" && options.url.toString) {
373
+ options.url = options.url.toString();
374
+ }
375
+ options.url = resolveRequestUrl(options.url);
376
+ options = Object.assign(
377
+ Object.assign({}, this.defaultOptions),
378
+ parseOptions(options)
379
+ );
380
+ const globalHeaders = apiBaseConfig.defaultHeaders && typeof apiBaseConfig.defaultHeaders === "object" ? apiBaseConfig.defaultHeaders : {};
381
+ const defaultHeaders = this.defaultOptions.headers && typeof this.defaultOptions.headers === "object" ? this.defaultOptions.headers : {};
382
+ const requestHeaders = options.headers && typeof options.headers === "object" ? options.headers : {};
383
+ options.headers = Object.assign({}, globalHeaders, defaultHeaders, requestHeaders);
384
+ if (!options.hasOwnProperty("url")) {
385
+ throw new Error("No URL provided");
386
+ }
387
+ if (options.url && typeof options.url === "object" && options.url.toString) {
388
+ options.url = options.url.toString();
389
+ }
390
+ const fetchOptions = {
391
+ method: options.method || "GET",
392
+ headers: {}
393
+ };
394
+ if (options.headers) {
395
+ Object.assign(fetchOptions.headers, options.headers);
396
+ }
397
+ if (options.contentType) {
398
+ fetchOptions.headers["Content-Type"] = options.contentType;
399
+ }
400
+ if (options.data && ["POST", "PUT", "PATCH"].includes(fetchOptions.method)) {
401
+ fetchOptions.body = options.data;
402
+ }
403
+ return fetch(options.url, fetchOptions).catch((fetchError) => {
404
+ throw new KViewsNetworkError(
405
+ fetchError instanceof Error ? fetchError.message : String(fetchError),
406
+ {
407
+ originalError: fetchError instanceof Error ? fetchError : new Error(String(fetchError)),
408
+ url: options.url,
409
+ options
410
+ }
411
+ );
412
+ }).then(async (response) => {
413
+ const jqXHR = {
414
+ status: response.status,
415
+ statusText: response.statusText,
416
+ responseText: null,
417
+ responseJSON: null,
418
+ getAllResponseHeaders: () => {
419
+ const headers = {};
420
+ response.headers.forEach((value, key) => {
421
+ headers[key] = value;
422
+ });
423
+ return Object.entries(headers).map(([key, value]) => `${key}: ${value}`).join("\r\n");
424
+ },
425
+ getResponseHeader: (name) => {
426
+ return response.headers.get(name);
427
+ }
428
+ };
429
+ const text = await response.text();
430
+ let data = text;
431
+ try {
432
+ data = JSON.parse(text);
433
+ } catch (e) {
434
+ }
435
+ jqXHR.responseText = text;
436
+ jqXHR.responseJSON = typeof data === "object" ? data : null;
437
+ if (!response.ok) {
438
+ const error2 = new KViewsHttpError(
439
+ `HTTP ${response.status}: ${response.statusText}`,
440
+ {
441
+ status: response.status,
442
+ statusText: response.statusText,
443
+ responseText: text,
444
+ responseJSON: typeof data === "object" ? data : null,
445
+ jqXHR,
446
+ options,
447
+ errorThrown: new Error(`HTTP ${response.status}: ${response.statusText}`)
448
+ }
449
+ );
450
+ error2.textStatus = "error";
451
+ throw error2;
452
+ }
453
+ return {
454
+ data,
455
+ textStatus: "success",
456
+ jqXHR
457
+ };
458
+ }).catch((error2) => {
459
+ if (error2 instanceof KViewsHttpError || error2 instanceof KViewsNetworkError) {
460
+ throw error2;
461
+ }
462
+ const jqXHR = {
463
+ status: 0,
464
+ statusText: "error",
465
+ responseText: null,
466
+ responseJSON: null,
467
+ getAllResponseHeaders: () => "",
468
+ getResponseHeader: () => null
469
+ };
470
+ const httpError = new KViewsHttpError(
471
+ error2 instanceof Error ? error2.message : String(error2),
472
+ {
473
+ status: 0,
474
+ statusText: "error",
475
+ responseText: null,
476
+ responseJSON: null,
477
+ jqXHR,
478
+ options,
479
+ errorThrown: error2 instanceof Error ? error2 : new Error(String(error2))
480
+ }
481
+ );
482
+ httpError.textStatus = "error";
483
+ throw httpError;
484
+ });
485
+ }
486
+ /**
487
+ * Create (POST) operation
488
+ */
489
+ create(ctx, url, opts, data) {
490
+ let options = {
491
+ context: ctx,
492
+ url,
493
+ method: "POST",
494
+ data
495
+ };
496
+ Object.assign(options, opts);
497
+ return this.sync(options);
498
+ }
499
+ /**
500
+ * Read (GET) operation
501
+ */
502
+ read(ctx, url, opts) {
503
+ let options = {
504
+ context: ctx,
505
+ url,
506
+ method: "GET"
507
+ };
508
+ Object.assign(options, opts);
509
+ return this.sync(options);
510
+ }
511
+ /**
512
+ * Delete (DELETE) operation
513
+ */
514
+ delete(ctx, url, opts) {
515
+ let options = {
516
+ context: ctx,
517
+ url,
518
+ method: "DELETE"
519
+ };
520
+ Object.assign(options, opts);
521
+ return this.sync(options);
522
+ }
523
+ /**
524
+ * Update (PATCH) operation
525
+ */
526
+ update(ctx, url, opts, data) {
527
+ let options = {
528
+ context: ctx,
529
+ url,
530
+ method: "PATCH",
531
+ contentType: "application/vnd.api+json",
532
+ data
533
+ };
534
+ Object.assign(options, opts);
535
+ return this.sync(options);
536
+ }
537
+ };
538
+
539
+ // src/dataParser.js
540
+ function getIncludedResources(doc) {
541
+ if (!doc || typeof doc !== "object") {
542
+ return [];
543
+ }
544
+ const includedData = doc.hasOwnProperty("included") ? doc.included : doc.hasOwnProperty("includes") ? doc.includes : null;
545
+ if (!includedData) {
546
+ return [];
547
+ }
548
+ if (!Array.isArray(includedData)) {
549
+ return [];
550
+ }
551
+ return includedData;
552
+ }
553
+ function buildResourceIndex(doc) {
554
+ const index = /* @__PURE__ */ new Map();
555
+ if (!doc || typeof doc !== "object") {
556
+ return index;
557
+ }
558
+ function indexResource(resource) {
559
+ if (!resource || typeof resource !== "object") {
560
+ return;
561
+ }
562
+ if (!resource.type || !resource.id) {
563
+ return;
564
+ }
565
+ const key = `${resource.type}/${resource.id}`;
566
+ if (!index.has(key)) {
567
+ index.set(key, resource);
568
+ }
569
+ }
570
+ if (doc.data) {
571
+ if (Array.isArray(doc.data)) {
572
+ doc.data.forEach(indexResource);
573
+ } else if (typeof doc.data === "object") {
574
+ indexResource(doc.data);
575
+ }
576
+ }
577
+ const included = getIncludedResources(doc);
578
+ included.forEach(indexResource);
579
+ return index;
580
+ }
581
+ function getResourceFromIndex(typeOrRef, id, resourceIndex) {
582
+ let type, resourceId;
583
+ if (typeof typeOrRef === "object" && typeOrRef !== null) {
584
+ type = typeOrRef.type;
585
+ resourceId = typeOrRef.id;
586
+ } else {
587
+ type = typeOrRef;
588
+ resourceId = id;
589
+ }
590
+ if (!type || !resourceId) {
591
+ return null;
592
+ }
593
+ const key = `${type}/${resourceId}`;
594
+ return resourceIndex.get(key) || null;
595
+ }
596
+ function hydrateResource(resource, resourceIndex, visited = /* @__PURE__ */ new Set()) {
597
+ if (!resource || typeof resource !== "object") {
598
+ return resource;
599
+ }
600
+ if (!resource.relationships) {
601
+ return resource;
602
+ }
603
+ const resourceKey = resource.type && resource.id ? `${resource.type}/${resource.id}` : null;
604
+ if (resourceKey && visited.has(resourceKey)) {
605
+ return resource;
606
+ }
607
+ if (resourceKey) {
608
+ visited.add(resourceKey);
609
+ }
610
+ Object.keys(resource.relationships).forEach((relName) => {
611
+ const rel = resource.relationships[relName];
612
+ if (rel === null) {
613
+ return;
614
+ }
615
+ if (rel.data !== void 0) {
616
+ if (rel.data === null) {
617
+ resource.relationships[relName] = null;
618
+ } else if (Array.isArray(rel.data)) {
619
+ resource.relationships[relName] = rel.data.map((ref) => {
620
+ const hydrated = getResourceFromIndex(ref, null, resourceIndex);
621
+ if (hydrated) {
622
+ return hydrateResource(
623
+ hydrated,
624
+ // Use same object instance from index
625
+ resourceIndex,
626
+ new Set(visited)
627
+ // New visited set for each branch
628
+ );
629
+ }
630
+ return ref;
631
+ }).filter((r) => r !== null);
632
+ } else if (typeof rel.data === "object" && rel.data.type && rel.data.id) {
633
+ const hydrated = getResourceFromIndex(rel.data, null, resourceIndex);
634
+ if (hydrated) {
635
+ resource.relationships[relName] = hydrateResource(
636
+ hydrated,
637
+ // Use same object instance from index
638
+ resourceIndex,
639
+ new Set(visited)
640
+ // New visited set for each branch
641
+ );
642
+ } else {
643
+ resource.relationships[relName] = rel.data;
644
+ }
645
+ } else {
646
+ resource.relationships[relName] = rel.data;
647
+ }
648
+ } else {
649
+ resource.relationships[relName] = rel;
650
+ }
651
+ });
652
+ return resource;
653
+ }
654
+ function hydrateDocumentData(doc) {
655
+ if (!doc || typeof doc !== "object") {
656
+ throw new KViewsParseError("Invalid document: must be an object");
657
+ }
658
+ const resourceIndex = buildResourceIndex(doc);
659
+ if (!doc.data) {
660
+ return null;
661
+ }
662
+ const data = doc.data;
663
+ if (Array.isArray(data)) {
664
+ data.forEach((resource) => hydrateResource(resource, resourceIndex));
665
+ return data;
666
+ } else if (typeof data === "object") {
667
+ return hydrateResource(data, resourceIndex);
668
+ }
669
+ return data;
670
+ }
671
+ function parseItemData(data, options = {}) {
672
+ let hydratedResource;
673
+ let doc = data;
674
+ if (data && typeof data === "object" && data.type && data.id && !data.data) {
675
+ hydratedResource = data;
676
+ } else if (data && data.data) {
677
+ doc = data;
678
+ hydratedResource = hydrateDocumentData(doc);
679
+ } else {
680
+ hydratedResource = data;
681
+ }
682
+ if (!hydratedResource || typeof hydratedResource !== "object") {
683
+ throw new KViewsParseError("Invalid item data: must be an object");
684
+ }
685
+ if (doc && doc.links && doc.links.self && !hydratedResource.url) {
686
+ hydratedResource.url = createURL(doc.links.self);
687
+ }
688
+ return hydratedResource;
689
+ }
690
+ function parseCollectionData(doc) {
691
+ if (!doc || typeof doc !== "object") {
692
+ return [];
693
+ }
694
+ const hydratedData = hydrateDocumentData(doc);
695
+ if (!hydratedData) {
696
+ return [];
697
+ }
698
+ if (!Array.isArray(hydratedData)) {
699
+ return [hydratedData];
700
+ }
701
+ return hydratedData;
702
+ }
703
+ function parseDataForInsertOrUpdate(itemData) {
704
+ if (itemData === null) {
705
+ return null;
706
+ }
707
+ if (typeof itemData !== "object") {
708
+ throw new Error("Invalid item data: " + itemData);
709
+ }
710
+ if (itemData.constructor === Array || itemData.hasOwnProperty("items") && itemData.hasOwnProperty("length")) {
711
+ let resource2 = [];
712
+ itemData.forEach(function(item) {
713
+ resource2.push(parseDataForInsertOrUpdate(item));
714
+ });
715
+ return resource2;
716
+ }
717
+ if (itemData.constructor !== Object) {
718
+ throw new Error("Invalid case");
719
+ }
720
+ let resource = {};
721
+ if (!itemData.hasOwnProperty("attributes")) {
722
+ let tmp = { attributes: {} };
723
+ if (itemData.hasOwnProperty("type")) {
724
+ tmp.type = itemData.type;
725
+ }
726
+ Object.assign(tmp.attributes, itemData);
727
+ itemData = tmp;
728
+ }
729
+ Object.getOwnPropertyNames(itemData.attributes).forEach(function(attr) {
730
+ if (itemData.attributes[attr] && typeof itemData.attributes[attr] === "object") {
731
+ if (!resource.relationships) {
732
+ resource.relationships = {};
733
+ }
734
+ resource.relationships[attr] = {
735
+ data: parseDataForInsertOrUpdate(itemData.attributes[attr])
736
+ };
737
+ return;
738
+ }
739
+ if (!resource.attributes) {
740
+ resource.attributes = {};
741
+ }
742
+ resource.attributes[attr] = itemData.attributes[attr];
743
+ });
744
+ return resource;
745
+ }
746
+
747
+ // src/adapters/JsonApiAdapter.js
748
+ var JsonApiAdapter = class {
749
+ /** @type {string} */
750
+ name = "jsonapi";
751
+ /**
752
+ * Whether a remote response represents a single resource (not a collection).
753
+ *
754
+ * @param {object} data - Raw HTTP response body
755
+ * @returns {boolean}
756
+ */
757
+ isSingleItemResponse(data) {
758
+ return !!(data && data.data && typeof data.data === "object" && !Array.isArray(data.data));
759
+ }
760
+ /**
761
+ * Extract pagination metadata from a remote document.
762
+ *
763
+ * @param {object} data - Raw HTTP response body
764
+ * @returns {{ totalRecords?: number, offset?: number }}
765
+ */
766
+ extractMetadata(data) {
767
+ const meta = {};
768
+ if (!data || !data.hasOwnProperty("meta") || typeof data.meta !== "object") {
769
+ return meta;
770
+ }
771
+ if (data.meta.hasOwnProperty("totalRecords")) {
772
+ meta.totalRecords = data.meta.totalRecords * 1;
773
+ }
774
+ if (data.meta.hasOwnProperty("offset")) {
775
+ meta.offset = data.meta.offset;
776
+ }
777
+ return meta;
778
+ }
779
+ /**
780
+ * Apply extracted metadata to a Collection instance.
781
+ *
782
+ * @param {object} collection - Collection instance
783
+ * @param {{ totalRecords?: number, offset?: number }} meta
784
+ */
785
+ applyMetadata(collection, meta) {
786
+ if (meta.totalRecords !== void 0) {
787
+ collection.total = meta.totalRecords;
788
+ }
789
+ if (meta.offset !== void 0) {
790
+ collection.offset = meta.offset;
791
+ }
792
+ }
793
+ /**
794
+ * Parse a single-item remote document into a canonical resource object.
795
+ *
796
+ * @param {object} data - Raw HTTP response body
797
+ * @param {object} [options]
798
+ * @returns {object} Hydrated resource ready for Item.loadFromData()
799
+ */
800
+ parseItemResponse(data, options = {}) {
801
+ this.validateItemRemoteDoc(data, options);
802
+ return parseItemData(data, options);
803
+ }
804
+ /**
805
+ * Validate that a remote document is suitable for a single Item load.
806
+ *
807
+ * @param {object} data - Raw HTTP response body
808
+ * @param {object} [options]
809
+ * @param {object} [options.collection] - Parent collection (for type inference)
810
+ */
811
+ validateItemRemoteDoc(data) {
812
+ if (data?.data?.constructor === Array) {
813
+ throw new Error("Invalid configuration: resource type is item but server response is collection");
814
+ }
815
+ }
816
+ /**
817
+ * Infer resource type from a single-item remote document.
818
+ *
819
+ * @param {object} data - Raw HTTP response body
820
+ * @returns {string|undefined}
821
+ */
822
+ inferItemType(data) {
823
+ return data?.data?.type;
824
+ }
825
+ /**
826
+ * Parse a collection remote document into canonical resource objects.
827
+ *
828
+ * @param {object} doc - Raw HTTP response body
829
+ * @returns {{ items: Array<object>, meta: object }}
830
+ */
831
+ parseCollectionResponse(doc) {
832
+ const items = parseCollectionData(doc);
833
+ const meta = this.extractMetadata(doc);
834
+ return { items, meta };
835
+ }
836
+ /**
837
+ * Apply list query parameters to a URL object before a collection fetch.
838
+ *
839
+ * @param {import('../URL.js').URL} url - Collection URL
840
+ * @param {{ type?: string, offset?: number, pageSize?: number }} params
841
+ */
842
+ applyListQuery(url, params) {
843
+ const { type, offset, pageSize } = params;
844
+ if (typeof offset !== "undefined" && offset !== null && type) {
845
+ url.parameters[`page[${type}][offset]`] = offset;
846
+ }
847
+ if (typeof pageSize !== "undefined" && pageSize !== null && type) {
848
+ url.parameters[`page[${type}][limit]`] = pageSize;
849
+ }
850
+ }
851
+ /**
852
+ * Serialize plain item data for a create (POST) request.
853
+ *
854
+ * @param {object|Array} itemData - Single item or array of items
855
+ * @param {{ type?: string }} [context]
856
+ * @returns {{ body: string, contentType: string, headers?: object }}
857
+ */
858
+ serializeForCreate(itemData, context = {}) {
859
+ const doc = { data: parseDataForInsertOrUpdate(itemData) };
860
+ if (context.type) {
861
+ doc.type = context.type;
862
+ }
863
+ return {
864
+ body: JSON.stringify(doc),
865
+ contentType: "application/vnd.api+json"
866
+ };
867
+ }
868
+ /**
869
+ * Serialize changed fields for an update (PATCH) request.
870
+ *
871
+ * @param {object} toUpdate - Resource patch with id, type, attributes, relationships
872
+ * @returns {{ body: string, contentType: string }}
873
+ */
874
+ serializeForUpdate(toUpdate) {
875
+ return {
876
+ body: JSON.stringify({ data: toUpdate }),
877
+ contentType: "application/vnd.api+json"
878
+ };
879
+ }
880
+ /**
881
+ * Serialize a runtime relationship value to JSON:API wire format.
882
+ *
883
+ * @param {object|Array|null} rel - Runtime relationship value
884
+ * @returns {object} JSON:API relationship: { data: ... }
885
+ */
886
+ serializeRelationship(rel) {
887
+ if (rel === null) {
888
+ return { data: null };
889
+ }
890
+ if (rel && typeof rel === "object" && !Array.isArray(rel)) {
891
+ if (rel.id) {
892
+ const result = { data: { id: rel.id } };
893
+ if (rel.type) {
894
+ result.data.type = rel.type;
895
+ }
896
+ return result;
897
+ }
898
+ if (rel.hasOwnProperty("toJSON")) {
899
+ const json = rel.toJSON();
900
+ const result = { data: { id: json.id } };
901
+ if (json.type) {
902
+ result.data.type = json.type;
903
+ }
904
+ return result;
905
+ }
906
+ return { data: null };
907
+ }
908
+ if (Array.isArray(rel)) {
909
+ return {
910
+ data: rel.map((item) => {
911
+ if (item && typeof item === "object") {
912
+ if (item.type && item.id) {
913
+ const result = { id: item.id };
914
+ if (item.type) {
915
+ result.type = item.type;
916
+ }
917
+ return result;
918
+ }
919
+ if (item.hasOwnProperty("toJSON")) {
920
+ const json = item.toJSON();
921
+ const result = { id: json.id };
922
+ if (json.type) {
923
+ result.type = json.type;
924
+ }
925
+ return result;
926
+ }
927
+ }
928
+ return item;
929
+ }).filter((item) => item && item.id)
930
+ };
931
+ }
932
+ return { data: null };
933
+ }
934
+ };
935
+
936
+ // src/adapters/plainUtils.js
937
+ function getPath(obj, path) {
938
+ if (!obj || !path || typeof path !== "string") {
939
+ return void 0;
940
+ }
941
+ return path.split(".").reduce((current, key) => {
942
+ if (current == null || typeof current !== "object") {
943
+ return void 0;
944
+ }
945
+ return current[key];
946
+ }, obj);
947
+ }
948
+ function extractCollectionRows(doc, itemsPath) {
949
+ if (Array.isArray(doc)) {
950
+ return doc;
951
+ }
952
+ if (!doc || typeof doc !== "object") {
953
+ return [];
954
+ }
955
+ if (itemsPath) {
956
+ const rows = getPath(doc, itemsPath);
957
+ return Array.isArray(rows) ? rows : [];
958
+ }
959
+ if (Array.isArray(doc.data)) {
960
+ return doc.data;
961
+ }
962
+ if (Array.isArray(doc.items)) {
963
+ return doc.items;
964
+ }
965
+ if (Array.isArray(doc.results)) {
966
+ return doc.results;
967
+ }
968
+ return [];
969
+ }
970
+ function extractTotalRecords(doc, totalPath) {
971
+ if (!doc || typeof doc !== "object") {
972
+ return void 0;
973
+ }
974
+ if (totalPath) {
975
+ const value = getPath(doc, totalPath);
976
+ return value != null ? value * 1 : void 0;
977
+ }
978
+ for (const path of ["total", "count", "totalCount", "meta.totalRecords"]) {
979
+ const value = getPath(doc, path);
980
+ if (value != null) {
981
+ return value * 1;
982
+ }
983
+ }
984
+ return void 0;
985
+ }
986
+ function extractOffset(doc, offsetPath) {
987
+ if (!doc || typeof doc !== "object") {
988
+ return void 0;
989
+ }
990
+ if (offsetPath) {
991
+ const value = getPath(doc, offsetPath);
992
+ return value != null ? value : void 0;
993
+ }
994
+ for (const path of ["offset", "meta.offset"]) {
995
+ const value = getPath(doc, path);
996
+ if (value != null) {
997
+ return value;
998
+ }
999
+ }
1000
+ return void 0;
1001
+ }
1002
+
1003
+ // src/adapters/PlainRestAdapter.js
1004
+ var PlainRestAdapter = class {
1005
+ /** @type {string} */
1006
+ name = "plain";
1007
+ /**
1008
+ * @param {object} [opts]
1009
+ * @param {string|null} [opts.itemsPath] - Dot path to item array (null = auto-detect)
1010
+ * @param {string|null} [opts.itemPath] - Dot path to single item in a wrapper document
1011
+ * @param {string|null} [opts.totalPath] - Dot path to total count (null = auto-detect)
1012
+ * @param {string|null} [opts.offsetPath] - Dot path to offset (null = auto-detect)
1013
+ * @param {string} [opts.idField] - Primary key field name
1014
+ * @param {string|null} [opts.typeField] - Resource type field on wire objects
1015
+ * @param {'offset'|'page'} [opts.paginationStyle] - Query param style for list fetches
1016
+ * @param {string} [opts.offsetParam] - Offset query parameter name
1017
+ * @param {string} [opts.limitParam] - Page size query parameter name
1018
+ * @param {string} [opts.pageParam] - Page number query parameter name (1-based)
1019
+ * @param {boolean} [opts.embedRelationships] - Embed nested objects on write (default: id stub)
1020
+ */
1021
+ constructor(opts = {}) {
1022
+ this.itemsPath = opts.itemsPath ?? null;
1023
+ this.itemPath = opts.itemPath ?? null;
1024
+ this.totalPath = opts.totalPath ?? null;
1025
+ this.offsetPath = opts.offsetPath ?? null;
1026
+ this.idField = opts.idField ?? "id";
1027
+ this.typeField = opts.typeField ?? "type";
1028
+ this.paginationStyle = opts.paginationStyle ?? "offset";
1029
+ this.offsetParam = opts.offsetParam ?? "offset";
1030
+ this.limitParam = opts.limitParam ?? "limit";
1031
+ this.pageParam = opts.pageParam ?? "page";
1032
+ this.embedRelationships = opts.embedRelationships ?? false;
1033
+ }
1034
+ /**
1035
+ * @param {object|Array} data
1036
+ * @returns {boolean}
1037
+ */
1038
+ isSingleItemResponse(data) {
1039
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
1040
+ return false;
1041
+ }
1042
+ if (this.itemPath) {
1043
+ const item = getPath(data, this.itemPath);
1044
+ if (Array.isArray(item)) {
1045
+ return false;
1046
+ }
1047
+ if (item && typeof item === "object") {
1048
+ return item[this.idField] != null;
1049
+ }
1050
+ return false;
1051
+ }
1052
+ const rows = extractCollectionRows(data, this.itemsPath);
1053
+ if (rows.length > 0) {
1054
+ return false;
1055
+ }
1056
+ if (data.data && Array.isArray(data.data)) {
1057
+ return false;
1058
+ }
1059
+ if (data.data && typeof data.data === "object" && data.data[this.idField] != null) {
1060
+ return true;
1061
+ }
1062
+ return data[this.idField] != null;
1063
+ }
1064
+ /**
1065
+ * @param {object} data
1066
+ * @returns {{ totalRecords?: number, offset?: number }}
1067
+ */
1068
+ extractMetadata(data) {
1069
+ const meta = {};
1070
+ const totalRecords = extractTotalRecords(data, this.totalPath);
1071
+ if (totalRecords !== void 0) {
1072
+ meta.totalRecords = totalRecords;
1073
+ }
1074
+ const offset = extractOffset(data, this.offsetPath);
1075
+ if (offset !== void 0) {
1076
+ meta.offset = offset;
1077
+ }
1078
+ return meta;
1079
+ }
1080
+ /**
1081
+ * @param {object} collection
1082
+ * @param {{ totalRecords?: number, offset?: number }} meta
1083
+ */
1084
+ applyMetadata(collection, meta) {
1085
+ if (meta.totalRecords !== void 0) {
1086
+ collection.total = meta.totalRecords;
1087
+ }
1088
+ if (meta.offset !== void 0) {
1089
+ collection.offset = meta.offset;
1090
+ }
1091
+ }
1092
+ /**
1093
+ * @param {object} data
1094
+ * @param {object} [options]
1095
+ * @returns {object}
1096
+ */
1097
+ parseItemResponse(data, options = {}) {
1098
+ this.validateItemRemoteDoc(data, options);
1099
+ const raw = this.extractRawItem(data);
1100
+ const defaultType = options.collection?.type ?? options.type;
1101
+ return this.normalize(raw, defaultType);
1102
+ }
1103
+ /**
1104
+ * @param {object} data
1105
+ */
1106
+ validateItemRemoteDoc(data) {
1107
+ if (Array.isArray(data)) {
1108
+ throw new Error("Invalid configuration: resource type is item but server response is collection");
1109
+ }
1110
+ if (this.itemPath) {
1111
+ const item = getPath(data, this.itemPath);
1112
+ if (Array.isArray(item)) {
1113
+ throw new Error("Invalid configuration: resource type is item but server response is collection");
1114
+ }
1115
+ return;
1116
+ }
1117
+ if (data?.data && Array.isArray(data.data)) {
1118
+ throw new Error("Invalid configuration: resource type is item but server response is collection");
1119
+ }
1120
+ }
1121
+ /**
1122
+ * @param {object} data
1123
+ * @returns {string|undefined}
1124
+ */
1125
+ inferItemType(data) {
1126
+ const raw = this.extractRawItem(data);
1127
+ if (!raw || typeof raw !== "object") {
1128
+ return void 0;
1129
+ }
1130
+ return raw[this.typeField];
1131
+ }
1132
+ /**
1133
+ * @param {object|Array} doc
1134
+ * @param {object} [options]
1135
+ * @returns {{ items: Array<object>, meta: object }}
1136
+ */
1137
+ parseCollectionResponse(doc, options = {}) {
1138
+ const rows = extractCollectionRows(doc, this.itemsPath);
1139
+ const defaultType = options.type;
1140
+ const items = rows.map((row) => this.normalize(row, defaultType));
1141
+ const meta = this.extractMetadata(doc);
1142
+ return { items, meta };
1143
+ }
1144
+ /**
1145
+ * @param {import('../URL.js').URL} url
1146
+ * @param {{ offset?: number, pageSize?: number }} params
1147
+ */
1148
+ applyListQuery(url, params) {
1149
+ const { offset, pageSize } = params;
1150
+ if (typeof pageSize === "undefined" || pageSize === null) {
1151
+ return;
1152
+ }
1153
+ if (this.paginationStyle === "page") {
1154
+ const safeOffset = typeof offset === "undefined" || offset === null ? 0 : offset;
1155
+ const page = Math.floor(safeOffset / pageSize) + 1;
1156
+ url.parameters[this.pageParam] = page;
1157
+ url.parameters[this.limitParam] = pageSize;
1158
+ return;
1159
+ }
1160
+ if (typeof offset !== "undefined" && offset !== null) {
1161
+ url.parameters[this.offsetParam] = offset;
1162
+ }
1163
+ url.parameters[this.limitParam] = pageSize;
1164
+ }
1165
+ /**
1166
+ * @param {object|Array} itemData
1167
+ * @param {object} [context]
1168
+ * @returns {{ body: string, contentType: string }}
1169
+ */
1170
+ serializeForCreate(itemData, context = {}) {
1171
+ const defaultType = context.type;
1172
+ let payload;
1173
+ if (Array.isArray(itemData)) {
1174
+ payload = itemData.map((item) => this.flattenForWire(this.coerceToCanonical(item, defaultType)));
1175
+ } else {
1176
+ payload = this.flattenForWire(this.coerceToCanonical(itemData, defaultType));
1177
+ }
1178
+ return {
1179
+ body: JSON.stringify(payload),
1180
+ contentType: "application/json"
1181
+ };
1182
+ }
1183
+ /**
1184
+ * @param {object} toUpdate
1185
+ * @returns {{ body: string, contentType: string }}
1186
+ */
1187
+ serializeForUpdate(toUpdate) {
1188
+ const relationships = {};
1189
+ Object.entries(toUpdate.relationships || {}).forEach(([name, rel]) => {
1190
+ relationships[name] = this.unwrapRelationship(rel);
1191
+ });
1192
+ const payload = this.flattenForWire({
1193
+ id: toUpdate.id,
1194
+ type: toUpdate.type,
1195
+ attributes: toUpdate.attributes,
1196
+ relationships
1197
+ });
1198
+ return {
1199
+ body: JSON.stringify(payload),
1200
+ contentType: "application/json"
1201
+ };
1202
+ }
1203
+ /**
1204
+ * @param {object|Array|null} rel
1205
+ * @returns {object|Array|null|undefined}
1206
+ */
1207
+ serializeRelationship(rel) {
1208
+ return this.unwrapRelationship(rel);
1209
+ }
1210
+ /**
1211
+ * @param {object} data
1212
+ * @returns {object}
1213
+ * @private
1214
+ */
1215
+ extractRawItem(data) {
1216
+ if (this.itemPath) {
1217
+ return getPath(data, this.itemPath);
1218
+ }
1219
+ if (data?.data && typeof data.data === "object" && !Array.isArray(data.data)) {
1220
+ return data.data;
1221
+ }
1222
+ return data;
1223
+ }
1224
+ /**
1225
+ * @param {object} row
1226
+ * @param {string|undefined} defaultType
1227
+ * @returns {object}
1228
+ */
1229
+ normalize(row, defaultType) {
1230
+ if (!row || typeof row !== "object" || Array.isArray(row)) {
1231
+ throw new Error("Invalid item data: must be an object");
1232
+ }
1233
+ if (row.attributes && typeof row.attributes === "object") {
1234
+ const relationships2 = {};
1235
+ Object.entries(row.relationships || {}).forEach(([name, value]) => {
1236
+ if (value === null) {
1237
+ relationships2[name] = null;
1238
+ } else if (Array.isArray(value)) {
1239
+ relationships2[name] = value.map((entry) => this.normalize(entry, defaultType));
1240
+ } else {
1241
+ relationships2[name] = this.normalize(value, defaultType);
1242
+ }
1243
+ });
1244
+ const normalized2 = {
1245
+ attributes: { ...row.attributes },
1246
+ relationships: relationships2
1247
+ };
1248
+ if (row.id != null) {
1249
+ normalized2.id = String(row.id);
1250
+ }
1251
+ if (row.type ?? defaultType) {
1252
+ normalized2.type = row.type ?? defaultType;
1253
+ }
1254
+ return normalized2;
1255
+ }
1256
+ const attributes = {};
1257
+ const relationships = {};
1258
+ let id;
1259
+ let type;
1260
+ Object.entries(row).forEach(([key, value]) => {
1261
+ if (key === this.idField) {
1262
+ id = value;
1263
+ return;
1264
+ }
1265
+ if (key === this.typeField) {
1266
+ type = value;
1267
+ return;
1268
+ }
1269
+ if (value === null || value === void 0) {
1270
+ attributes[key] = value;
1271
+ return;
1272
+ }
1273
+ if (Array.isArray(value)) {
1274
+ if (value.length > 0 && value.every((entry) => this.isNestedResource(entry))) {
1275
+ relationships[key] = value.map((entry) => this.normalize(entry, defaultType));
1276
+ } else {
1277
+ attributes[key] = value;
1278
+ }
1279
+ return;
1280
+ }
1281
+ if (this.isNestedResource(value)) {
1282
+ relationships[key] = this.normalize(value, defaultType);
1283
+ return;
1284
+ }
1285
+ attributes[key] = value;
1286
+ });
1287
+ const normalized = { attributes, relationships };
1288
+ if (id != null) {
1289
+ normalized.id = String(id);
1290
+ }
1291
+ if (type ?? defaultType) {
1292
+ normalized.type = type ?? defaultType;
1293
+ }
1294
+ return normalized;
1295
+ }
1296
+ /**
1297
+ * @param {*} value
1298
+ * @returns {boolean}
1299
+ * @private
1300
+ */
1301
+ isNestedResource(value) {
1302
+ return value && typeof value === "object" && !Array.isArray(value) && value[this.idField] != null;
1303
+ }
1304
+ /**
1305
+ * @param {object} data
1306
+ * @param {string|undefined} defaultType
1307
+ * @returns {object}
1308
+ * @private
1309
+ */
1310
+ coerceToCanonical(data, defaultType) {
1311
+ if (!data || typeof data !== "object") {
1312
+ return data;
1313
+ }
1314
+ if (data.attributes || data.relationships) {
1315
+ return data;
1316
+ }
1317
+ return this.normalize(data, defaultType);
1318
+ }
1319
+ /**
1320
+ * @param {object} canonical
1321
+ * @returns {object}
1322
+ * @private
1323
+ */
1324
+ flattenForWire(canonical) {
1325
+ if (!canonical || typeof canonical !== "object") {
1326
+ return canonical;
1327
+ }
1328
+ const result = { ...canonical.attributes || {} };
1329
+ if (canonical.id != null) {
1330
+ result[this.idField] = canonical.id;
1331
+ }
1332
+ if (canonical.type != null && this.typeField) {
1333
+ result[this.typeField] = canonical.type;
1334
+ }
1335
+ Object.entries(canonical.relationships || {}).forEach(([name, rel]) => {
1336
+ if (rel === null) {
1337
+ result[name] = null;
1338
+ return;
1339
+ }
1340
+ if (Array.isArray(rel)) {
1341
+ result[name] = rel.map((entry) => this.relationshipToWire(entry));
1342
+ return;
1343
+ }
1344
+ result[name] = this.relationshipToWire(rel);
1345
+ });
1346
+ return result;
1347
+ }
1348
+ /**
1349
+ * @param {object|null|undefined} rel
1350
+ * @returns {object|null|undefined}
1351
+ * @private
1352
+ */
1353
+ relationshipToWire(rel) {
1354
+ if (!rel) {
1355
+ return null;
1356
+ }
1357
+ if (this.embedRelationships && rel.attributes) {
1358
+ return this.flattenForWire(rel);
1359
+ }
1360
+ const stub = { [this.idField]: rel.id ?? rel[this.idField] };
1361
+ if ((rel.type ?? rel[this.typeField]) != null) {
1362
+ stub[this.typeField] = rel.type ?? rel[this.typeField];
1363
+ }
1364
+ return stub;
1365
+ }
1366
+ /**
1367
+ * @param {*} rel
1368
+ * @returns {object|Array|null|undefined}
1369
+ * @private
1370
+ */
1371
+ unwrapRelationship(rel) {
1372
+ if (rel == null) {
1373
+ return null;
1374
+ }
1375
+ if (rel.data !== void 0) {
1376
+ if (rel.data === null) {
1377
+ return null;
1378
+ }
1379
+ if (Array.isArray(rel.data)) {
1380
+ return rel.data.map((entry) => ({ ...entry }));
1381
+ }
1382
+ return { ...rel.data };
1383
+ }
1384
+ if (Array.isArray(rel)) {
1385
+ return rel.map((entry) => this.unwrapRelationship(entry));
1386
+ }
1387
+ if (typeof rel === "object") {
1388
+ if (rel.attributes) {
1389
+ return rel;
1390
+ }
1391
+ return { ...rel };
1392
+ }
1393
+ return rel;
1394
+ }
1395
+ };
1396
+
1397
+ // src/adapters/resolveAdapter.js
1398
+ var registry = /* @__PURE__ */ new Map([
1399
+ ["jsonapi", new JsonApiAdapter()],
1400
+ ["plain", new PlainRestAdapter()]
1401
+ ]);
1402
+ var defaultAdapter = "jsonapi";
1403
+ function registerAdapter(name, adapter) {
1404
+ if (!name || typeof name !== "string") {
1405
+ throw new Error("Adapter name must be a non-empty string");
1406
+ }
1407
+ registry.set(name, adapter);
1408
+ }
1409
+ function setDefaultAdapter(adapter) {
1410
+ defaultAdapter = adapter;
1411
+ }
1412
+ function getDefaultAdapter() {
1413
+ return defaultAdapter;
1414
+ }
1415
+ function resolveAdapter(adapter) {
1416
+ if (adapter && typeof adapter === "object") {
1417
+ return adapter;
1418
+ }
1419
+ const name = typeof adapter === "string" ? adapter : defaultAdapter;
1420
+ if (typeof name === "object") {
1421
+ return name;
1422
+ }
1423
+ const resolved = registry.get(name);
1424
+ if (!resolved) {
1425
+ throw new Error(`Unknown data adapter: ${name}`);
1426
+ }
1427
+ return resolved;
1428
+ }
1429
+
1430
+ // src/ItemView.js
1431
+ var ItemView = class {
1432
+ constructor(params) {
1433
+ if (params && params.isView) {
1434
+ return params;
1435
+ }
1436
+ this.type = "ItemView";
1437
+ this.dataBindings = null;
1438
+ this.template = null;
1439
+ this.container = null;
1440
+ this.collectionView = null;
1441
+ this.item = null;
1442
+ this.el = null;
1443
+ this.id = uid();
1444
+ this.isView = true;
1445
+ this.callbacks = {};
1446
+ if (params && (params.length || params.nodeName && !params.jquery)) {
1447
+ dbg("params is actually a jquery object or an html node", params);
1448
+ let $el = $(params);
1449
+ if ($el.data("view")) {
1450
+ return $el.data("view");
1451
+ }
1452
+ let tmp = $("<div>").append($el.clone(true));
1453
+ let html = tmp.html().replace(/&lt;%/gi, "<%").replace(/&lt;/gi, "<").replace(/%&gt;/gi, "%>").replace(/&gt;/gi, ">").replace(/&amp;/gi, "&");
1454
+ params = {
1455
+ template: template(html),
1456
+ el: $el
1457
+ };
1458
+ if ($el.attr("id")) {
1459
+ params.id = $el.attr("id");
1460
+ }
1461
+ tmp.remove();
1462
+ }
1463
+ try {
1464
+ params = parseOptions(params);
1465
+ } catch (e) {
1466
+ throw new Error("Error on ItemView", this, e);
1467
+ }
1468
+ Object.assign(this, params);
1469
+ if (this.el !== null) {
1470
+ this.dataBindings = getBoundObjects(this.el);
1471
+ }
1472
+ }
1473
+ /**
1474
+ * Event listener registration
1475
+ */
1476
+ on(event, cb) {
1477
+ if (!this.callbacks[event]) {
1478
+ this.callbacks[event] = [];
1479
+ }
1480
+ this.callbacks[event].push(cb);
1481
+ return this;
1482
+ }
1483
+ /**
1484
+ * Unbind from item
1485
+ */
1486
+ unbind() {
1487
+ if (this.item) {
1488
+ this.item.unbindView(this);
1489
+ }
1490
+ }
1491
+ /**
1492
+ * Create element from template
1493
+ */
1494
+ createElementFromTemplate() {
1495
+ if (this.template == null) {
1496
+ dbg("Warning: no template defined. Nothing to render");
1497
+ return null;
1498
+ }
1499
+ if (!this.item) {
1500
+ dbg("Warning: no item bound to view. Cannot render template.");
1501
+ return null;
1502
+ }
1503
+ let el;
1504
+ try {
1505
+ const renderContext = this.item.getRenderContext();
1506
+ console.log("renderContext", renderContext);
1507
+ let html = this.template(renderContext);
1508
+ el = $(html).attr("data-type", "item").attr("id", this.id).data("view", this).data("instance", this.item);
1509
+ } catch (e) {
1510
+ console.log("Error create view from template", e, this.item);
1511
+ el = $("<div>Could not render view: <strong>" + e.toString() + "</strong></div>");
1512
+ }
1513
+ return el;
1514
+ }
1515
+ /**
1516
+ * After render callback
1517
+ */
1518
+ afterrender() {
1519
+ if (!this.callbacks.afterrender) {
1520
+ return;
1521
+ }
1522
+ console.log("afterend of view", this);
1523
+ this.callbacks.afterrender.forEach((cb) => cb(this));
1524
+ }
1525
+ /**
1526
+ * Render the view
1527
+ */
1528
+ render(doNotAttachToContainer = false, addontop = false) {
1529
+ log("ItemView.render called", this.item, this.el);
1530
+ let renderedEl = this.createElementFromTemplate();
1531
+ if (!renderedEl) {
1532
+ return null;
1533
+ }
1534
+ log("View item", this.item);
1535
+ if (this.item && this.item.uievents) {
1536
+ log("UI events", this.item.uievents);
1537
+ this.item.uievents.forEach((action) => {
1538
+ log("UI event", action, renderedEl);
1539
+ if (action.selector && action.event && action.callback) {
1540
+ const actionEls = $(renderedEl).find(action.selector);
1541
+ log("UI event els", action, renderedEl, actionEls);
1542
+ actionEls.on(action.event, (event) => {
1543
+ event.preventDefault();
1544
+ log("UIevent triggered", event, this.item, this);
1545
+ action.callback(event, this.item, this);
1546
+ });
1547
+ }
1548
+ });
1549
+ }
1550
+ if (doNotAttachToContainer) {
1551
+ this.el = renderedEl;
1552
+ return this.el;
1553
+ }
1554
+ if (this.el) {
1555
+ let oldEl = this.el;
1556
+ if (!oldEl.jquery) {
1557
+ oldEl = $(oldEl);
1558
+ }
1559
+ oldEl.off();
1560
+ this.el = $(renderedEl).insertBefore(oldEl);
1561
+ oldEl.remove();
1562
+ this.afterrender();
1563
+ return this;
1564
+ }
1565
+ this.el = renderedEl;
1566
+ this.afterrender();
1567
+ if (!this.container) {
1568
+ return this;
1569
+ }
1570
+ $(this.el).appendTo(this.container.el);
1571
+ return this;
1572
+ }
1573
+ /**
1574
+ * Render empty state
1575
+ */
1576
+ renderEmpty(returnView) {
1577
+ if (this.item && this.item.emptyview && this.el) {
1578
+ let emptyView = $(this.item.emptyview).clone(true).css("display", "block");
1579
+ $(this.el).replaceWith(emptyView);
1580
+ }
1581
+ }
1582
+ /**
1583
+ * Remove view with animation
1584
+ */
1585
+ remove(idx) {
1586
+ return new Promise((resolve) => {
1587
+ if (this.item && this.item.collection) {
1588
+ this.item.collection._trigger("afterrender", this.item.collection);
1589
+ }
1590
+ if (this.el) {
1591
+ let $el = this.el.jquery ? this.el : $(this.el);
1592
+ $el.fadeOut({
1593
+ complete: () => {
1594
+ $el.remove();
1595
+ resolve();
1596
+ }
1597
+ });
1598
+ } else {
1599
+ resolve();
1600
+ }
1601
+ });
1602
+ }
1603
+ /**
1604
+ * Destroy view and clean up resources
1605
+ *
1606
+ * Removes event handlers, jQuery data, and DOM references
1607
+ */
1608
+ destroy() {
1609
+ if (this.el) {
1610
+ const $el = this.el.jquery ? this.el : $(this.el);
1611
+ $el.off();
1612
+ $el.removeData();
1613
+ }
1614
+ this.callbacks = {};
1615
+ if (this.item) {
1616
+ this.item.unbindView(this);
1617
+ }
1618
+ this.item = null;
1619
+ this.container = null;
1620
+ this.collectionView = null;
1621
+ this.el = null;
1622
+ this.template = null;
1623
+ this.dataBindings = null;
1624
+ return this;
1625
+ }
1626
+ };
1627
+
1628
+ // src/Item.js
1629
+ var Item = class _Item {
1630
+ constructor(options = {}, data = null) {
1631
+ this.id = null;
1632
+ this.type = null;
1633
+ this.attributes = {};
1634
+ this.relationships = {};
1635
+ this.views = [];
1636
+ this.collection = null;
1637
+ this.url = null;
1638
+ this.updateUrl = null;
1639
+ this.deleteUrl = null;
1640
+ this.strict = false;
1641
+ this.shadow = null;
1642
+ this.syncOp = null;
1643
+ this.emptyview = null;
1644
+ this.uievents = [];
1645
+ this.callbacks = {};
1646
+ this.adapter = null;
1647
+ try {
1648
+ Object.assign(this, parseOptions(options));
1649
+ } catch (e) {
1650
+ throw new Error("Error on Item init", e);
1651
+ }
1652
+ this.adapter = resolveAdapter(
1653
+ options.adapter ?? (options.collection && options.collection.adapter)
1654
+ );
1655
+ this.storage = options.storage || new Storage(
1656
+ (() => {
1657
+ const storageOpts = Object.assign({}, options.ajaxOpts || {});
1658
+ if (options.headers && typeof options.headers === "object") {
1659
+ storageOpts.headers = Object.assign(
1660
+ {},
1661
+ storageOpts.headers || {},
1662
+ options.headers
1663
+ );
1664
+ }
1665
+ return storageOpts;
1666
+ })()
1667
+ );
1668
+ let render = false;
1669
+ if (data) {
1670
+ log("Loading data", data);
1671
+ try {
1672
+ this.loadFromData(data);
1673
+ render = true;
1674
+ } catch (e) {
1675
+ console.error("Error loading data", e);
1676
+ }
1677
+ }
1678
+ if (this.url) {
1679
+ this.setUrl(this.url);
1680
+ }
1681
+ if (this.deleteUrl) {
1682
+ log("deleteUrl", this.deleteUrl);
1683
+ this.setUrl(this.deleteUrl, "delete");
1684
+ }
1685
+ if (this.updateUrl) {
1686
+ this.setUrl(this.updateUrl, "update");
1687
+ }
1688
+ if (this.insertUrl) {
1689
+ this.setUrl(this.insertUrl, "insert");
1690
+ }
1691
+ this.views.forEach((view) => {
1692
+ view.item = this;
1693
+ });
1694
+ if (options.itemListeners && typeof options.itemListeners === "object") {
1695
+ Object.getOwnPropertyNames(options.itemListeners).forEach((eventName) => {
1696
+ log("apply item listener", eventName, options.itemListeners[eventName]);
1697
+ this.on(eventName, options.itemListeners[eventName]);
1698
+ });
1699
+ }
1700
+ if (render) {
1701
+ log("Rendering data", data);
1702
+ this.render();
1703
+ }
1704
+ }
1705
+ /**
1706
+ * Event listener registration
1707
+ */
1708
+ on(eventName, cb) {
1709
+ if (typeof this.callbacks[eventName] === "undefined") {
1710
+ this.callbacks[eventName] = [];
1711
+ }
1712
+ this.callbacks[eventName].push(cb);
1713
+ return this;
1714
+ }
1715
+ /**
1716
+ * Remove event listener(s)
1717
+ * @param {string} eventName - Event name
1718
+ * @param {Function} [cb] - Optional callback to remove. If not provided, removes all listeners for the event
1719
+ * @returns {Item} This instance for chaining
1720
+ */
1721
+ off(eventName, cb) {
1722
+ if (!eventName) {
1723
+ this.callbacks = {};
1724
+ return this;
1725
+ }
1726
+ if (!this.callbacks[eventName]) {
1727
+ return this;
1728
+ }
1729
+ if (cb) {
1730
+ const index = this.callbacks[eventName].indexOf(cb);
1731
+ if (index > -1) {
1732
+ this.callbacks[eventName].splice(index, 1);
1733
+ }
1734
+ if (this.callbacks[eventName].length === 0) {
1735
+ delete this.callbacks[eventName];
1736
+ }
1737
+ } else {
1738
+ delete this.callbacks[eventName];
1739
+ }
1740
+ return this;
1741
+ }
1742
+ /**
1743
+ * Register a one-time event listener
1744
+ * @param {string} eventName - Event name
1745
+ * @param {Function} cb - Callback function
1746
+ * @returns {Item} This instance for chaining
1747
+ */
1748
+ once(eventName, cb) {
1749
+ const wrapper = (...args) => {
1750
+ cb(...args);
1751
+ this.off(eventName, wrapper);
1752
+ };
1753
+ return this.on(eventName, wrapper);
1754
+ }
1755
+ /**
1756
+ * Check if event has listeners
1757
+ * @param {string} eventName - Event name
1758
+ * @returns {boolean} True if event has listeners
1759
+ */
1760
+ hasListeners(eventName) {
1761
+ return this.callbacks[eventName] && Array.isArray(this.callbacks[eventName]) && this.callbacks[eventName].length > 0;
1762
+ }
1763
+ /**
1764
+ * Trigger an event (internal helper)
1765
+ * @private
1766
+ * @param {string} eventName - Event name
1767
+ * @param {...any} args - Arguments to pass to callbacks
1768
+ */
1769
+ _trigger(eventName, ...args) {
1770
+ if (this.callbacks[eventName] && Array.isArray(this.callbacks[eventName])) {
1771
+ this.callbacks[eventName].forEach((cb) => {
1772
+ if (typeof cb === "function") {
1773
+ cb(...args);
1774
+ }
1775
+ });
1776
+ }
1777
+ }
1778
+ /**
1779
+ * Emit/trigger an event manually
1780
+ * @param {string} eventName - Event name
1781
+ * @param {...any} args - Arguments to pass to callbacks
1782
+ * @returns {Item} This instance for chaining
1783
+ */
1784
+ emit(eventName, ...args) {
1785
+ this._trigger(eventName, ...args);
1786
+ return this;
1787
+ }
1788
+ /**
1789
+ * Set URL for this item
1790
+ */
1791
+ setUrl(url, type) {
1792
+ switch (type) {
1793
+ case "delete":
1794
+ this.deleteUrl = createURL(url);
1795
+ break;
1796
+ case "update":
1797
+ this.updateUrl = createURL(url);
1798
+ break;
1799
+ case "insert":
1800
+ this.insertUrl = createURL(url);
1801
+ break;
1802
+ default:
1803
+ this.url = createURL(url);
1804
+ this.deleteUrl = typeof this.deleteUrl == "string" ? createURL(this.deleteUrl) : this.deleteUrl ?? createURL(this.url);
1805
+ this.updateUrl = typeof this.updateUrl == "string" ? createURL(this.updateUrl) : this.updateUrl ?? createURL(this.url);
1806
+ break;
1807
+ }
1808
+ return this;
1809
+ }
1810
+ /**
1811
+ * Load from remote
1812
+ *
1813
+ * Canonical method for loading item data from API
1814
+ */
1815
+ loadFromRemote() {
1816
+ return this.loadFromDataSource();
1817
+ }
1818
+ load(data) {
1819
+ return data ? this.loadFromData(data) : this.loadFromRemote();
1820
+ }
1821
+ /**
1822
+ * Load from data source (internal implementation)
1823
+ * @private
1824
+ */
1825
+ loadFromDataSource() {
1826
+ let loaders = [];
1827
+ const overlay = createOverlay(this);
1828
+ this.views.forEach((itemView) => {
1829
+ if (itemView.el) {
1830
+ let $el = $(itemView.el);
1831
+ let loader = overlay.clone();
1832
+ loader.insertBefore(itemView.el).width($el.width()).height($el.height());
1833
+ loaders.push(loader);
1834
+ }
1835
+ });
1836
+ return new Promise((resolve, reject) => {
1837
+ if (!this.url) {
1838
+ reject(new Error("No valid URL provided"));
1839
+ return;
1840
+ }
1841
+ this._trigger("beforeload", this);
1842
+ let urlString = this.url.toString ? this.url.toString() : this.url;
1843
+ this.storage.read(this, urlString, {}).then((resp) => {
1844
+ let data = resp.data;
1845
+ this.loadFromRemoteDoc(data).render();
1846
+ this._trigger("load", this);
1847
+ loaders.forEach((loader) => {
1848
+ loader.remove();
1849
+ });
1850
+ resolve(this);
1851
+ }).catch((error2) => {
1852
+ dbg("fail to load item resource", this.url, error2);
1853
+ if (error2 instanceof Error && error2.jqXHR) {
1854
+ this.fail(error2.jqXHR, error2.textStatus || "error", error2.errorThrown || error2);
1855
+ reject(error2);
1856
+ } else if (error2 && error2.jqXHR) {
1857
+ this.fail(error2.jqXHR, error2.textStatus, error2.errorThrown);
1858
+ reject(error2);
1859
+ } else {
1860
+ this.fail(null, "error", error2);
1861
+ reject(error2);
1862
+ }
1863
+ });
1864
+ });
1865
+ }
1866
+ /**
1867
+ * @deprecated Use loadFromRemote() instead
1868
+ * Alias for backward compatibility
1869
+ */
1870
+ refresh() {
1871
+ return this.loadFromRemote();
1872
+ }
1873
+ /**
1874
+ * @deprecated Use loadFromRemote() instead
1875
+ * Alias for backward compatibility
1876
+ */
1877
+ reload() {
1878
+ return this.loadFromRemote();
1879
+ }
1880
+ /**
1881
+ * @deprecated Use loadFromRemote() instead
1882
+ * Internal method, use loadFromRemote() for public API
1883
+ * @private
1884
+ */
1885
+ load_from_data_source() {
1886
+ return this.loadFromDataSource();
1887
+ }
1888
+ /**
1889
+ * Unbind a view from this item
1890
+ */
1891
+ unbindView(view) {
1892
+ let found = false;
1893
+ for (let i = 0; i < this.views.length; i++) {
1894
+ if (this.views[i] === view) {
1895
+ found = i;
1896
+ }
1897
+ }
1898
+ if (found !== false) {
1899
+ this.views.splice(found, 1);
1900
+ }
1901
+ }
1902
+ /**
1903
+ * Bind a view to this item
1904
+ */
1905
+ bindView(view, returnView) {
1906
+ let $el = $(view);
1907
+ if ($el.length === 0) {
1908
+ throw new Error("Nothing to bind to: empty view element");
1909
+ }
1910
+ if (!(view instanceof ItemView)) {
1911
+ view = new ItemView(view);
1912
+ }
1913
+ let bound = false;
1914
+ this.views.forEach((v) => {
1915
+ dbg("bind to existing view", v.el);
1916
+ if (v === view) {
1917
+ bound = true;
1918
+ }
1919
+ });
1920
+ if (bound) {
1921
+ return returnView ? view : this;
1922
+ }
1923
+ view.item = this;
1924
+ this.views.push(view);
1925
+ return returnView ? view : this;
1926
+ }
1927
+ /**
1928
+ * Load from a remote API document (format determined by adapter).
1929
+ *
1930
+ * @param {object} data - Raw HTTP response body
1931
+ * @returns {Item} This instance for chaining
1932
+ */
1933
+ loadFromRemoteDoc(data) {
1934
+ dbg("Load from remote doc", data);
1935
+ if (this.collection && !this.collection.type) {
1936
+ const inferredType = this.adapter.inferItemType(data);
1937
+ if (inferredType) {
1938
+ this.type = inferredType;
1939
+ }
1940
+ }
1941
+ this.adapter.validateItemRemoteDoc(data, { collection: this.collection });
1942
+ const parsedData = this.adapter.parseItemResponse(data, { collection: this.collection });
1943
+ Object.assign(this, parsedData);
1944
+ if (this.url) {
1945
+ this.url = createURL(this.url);
1946
+ }
1947
+ return this;
1948
+ }
1949
+ /**
1950
+ * @deprecated Use loadFromRemoteDoc() instead
1951
+ * @param {object} data - Raw HTTP response body
1952
+ * @returns {Item} This instance for chaining
1953
+ */
1954
+ loadFromJSONAPIDoc(data) {
1955
+ return this.loadFromRemoteDoc(data);
1956
+ }
1957
+ /**
1958
+ * Load from data object
1959
+ */
1960
+ loadFromData(data, render = false) {
1961
+ if (data === null || typeof data !== "object" || data.constructor !== Object) {
1962
+ dbg("cannot load ", data, " into ", this);
1963
+ throw new Error("Cannot load data into item");
1964
+ }
1965
+ if (!data.hasOwnProperty("attributes") && !data.hasOwnProperty("id") && !data.hasOwnProperty("type")) {
1966
+ dbg("need to normalize data", data);
1967
+ let attributes = {};
1968
+ let relationships = {};
1969
+ Object.getOwnPropertyNames(data).forEach((propName) => {
1970
+ if (data[propName] && data[propName].constructor === Object) {
1971
+ relationships[propName] = new _Item().loadFromData(data[propName]);
1972
+ return;
1973
+ }
1974
+ if (data[propName] && data[propName].constructor === Array) {
1975
+ relationships[propName] = data[propName];
1976
+ return;
1977
+ }
1978
+ attributes[propName] = data[propName];
1979
+ });
1980
+ data = {
1981
+ attributes
1982
+ };
1983
+ if (Object.getOwnPropertyNames(relationships).length) {
1984
+ data.relationships = relationships;
1985
+ }
1986
+ }
1987
+ Object.assign(this, data);
1988
+ this._trigger("load", this);
1989
+ if (render) {
1990
+ this.render();
1991
+ }
1992
+ return this;
1993
+ }
1994
+ /**
1995
+ * Handle failure
1996
+ */
1997
+ fail(xhr, statusText, error2) {
1998
+ dbg("item.fail", xhr, statusText, error2);
1999
+ this.views.forEach((view) => {
2000
+ if (xhr && xhr.status === 404) {
2001
+ view.renderEmpty();
2002
+ }
2003
+ });
2004
+ }
2005
+ /**
2006
+ * Get render context - safe view model for templates
2007
+ *
2008
+ * RENDER CONTEXT CONTRACT:
2009
+ *
2010
+ * Returns a template-friendly object where:
2011
+ * - Attributes are exposed directly (e.g., {{title}} not {{attributes.title}})
2012
+ * - Relationships are flattened to plain objects with attributes, id, type
2013
+ * - All data is shallow-copied to prevent mutation of internal state
2014
+ *
2015
+ * Relationship representation strategy:
2016
+ * - To-one: { id, type, ...attributes } (flattened plain object)
2017
+ * - To-many: Array of { id, type, ...attributes } (array of flattened objects)
2018
+ * - Null relationships: null
2019
+ *
2020
+ * This ensures Handlebars templates can access data directly:
2021
+ * {{title}} - item attribute
2022
+ * {{author.name}} - relationship attribute
2023
+ * {{#each tags}}{{name}}{{/each}} - relationship array
2024
+ *
2025
+ * @returns {Object} Render context object safe for template rendering
2026
+ */
2027
+ getRenderContext() {
2028
+ function deepCloneStatic(value, seen = /* @__PURE__ */ new WeakMap()) {
2029
+ if (value === null || typeof value !== "object") {
2030
+ return value;
2031
+ }
2032
+ if (seen.has(value)) {
2033
+ return seen.get(value);
2034
+ }
2035
+ if (Array.isArray(value)) {
2036
+ const clonedArray = [];
2037
+ seen.set(value, clonedArray);
2038
+ value.forEach((item) => clonedArray.push(deepCloneStatic(item, seen)));
2039
+ return clonedArray;
2040
+ }
2041
+ const clonedObject = {};
2042
+ seen.set(value, clonedObject);
2043
+ Object.keys(value).forEach((key) => {
2044
+ clonedObject[key] = deepCloneStatic(value[key], seen);
2045
+ });
2046
+ return clonedObject;
2047
+ }
2048
+ function isResourceNode(obj) {
2049
+ return obj instanceof _Item || obj.attributes != null && typeof obj.attributes === "object";
2050
+ }
2051
+ function isReferenceStub(obj) {
2052
+ if (!obj || typeof obj !== "object" || Array.isArray(obj) || isResourceNode(obj)) {
2053
+ return false;
2054
+ }
2055
+ if (obj.id == null) {
2056
+ return false;
2057
+ }
2058
+ return Object.keys(obj).every((key) => key === "id" || key === "type");
2059
+ }
2060
+ function copyntransform(obj, cache = /* @__PURE__ */ new WeakMap()) {
2061
+ if (!obj) {
2062
+ return null;
2063
+ }
2064
+ if (typeof obj !== "object") {
2065
+ return obj;
2066
+ }
2067
+ if (cache.has(obj)) {
2068
+ return cache.get(obj);
2069
+ }
2070
+ if (isReferenceStub(obj)) {
2071
+ const stub = { id: obj.id };
2072
+ cache.set(obj, stub);
2073
+ return stub;
2074
+ }
2075
+ if (!isResourceNode(obj)) {
2076
+ if (obj.id != null) {
2077
+ const flat = { id: obj.id };
2078
+ Object.keys(obj).forEach((key) => {
2079
+ if (key === "id" || key === "type" || key === "relationships" || key === "attributes") {
2080
+ return;
2081
+ }
2082
+ flat[key] = deepCloneStatic(obj[key]);
2083
+ });
2084
+ cache.set(obj, flat);
2085
+ return flat;
2086
+ }
2087
+ const cloned = deepCloneStatic(obj);
2088
+ cache.set(obj, cloned);
2089
+ return cloned;
2090
+ }
2091
+ const result = { id: obj.id };
2092
+ cache.set(obj, result);
2093
+ Object.assign(result, deepCloneStatic(obj.attributes ?? {}));
2094
+ Object.keys(obj.relationships ?? {}).forEach((relName) => {
2095
+ if (Array.isArray(obj.relationships[relName])) {
2096
+ result[relName] = obj.relationships[relName].map((item) => copyntransform(item, cache));
2097
+ } else {
2098
+ result[relName] = copyntransform(obj.relationships[relName], cache);
2099
+ }
2100
+ });
2101
+ return result;
2102
+ }
2103
+ return copyntransform(this);
2104
+ }
2105
+ /**
2106
+ * Convert to JSON:API format
2107
+ *
2108
+ * Serializes item to JSON:API format for API requests.
2109
+ * This method is side-effect free - it does not mutate this.relationships.
2110
+ *
2111
+ * Runtime relationships (hydrated objects) are converted to JSON:API
2112
+ * relationship format: { data: { type, id } } or { data: [{ type, id }, ...] }
2113
+ *
2114
+ * @returns {Object} JSON:API formatted object
2115
+ */
2116
+ toJSON() {
2117
+ let json = {
2118
+ type: this.type,
2119
+ attributes: this.attributes || {}
2120
+ };
2121
+ if (this.id) {
2122
+ json.id = this.id;
2123
+ }
2124
+ if (this.relationships && Object.keys(this.relationships).length > 0) {
2125
+ json.relationships = {};
2126
+ for (let relName in this.relationships) {
2127
+ if (!this.relationships.hasOwnProperty(relName)) {
2128
+ continue;
2129
+ }
2130
+ const rel = this.relationships[relName];
2131
+ if (rel === null) {
2132
+ json.relationships[relName] = { data: null };
2133
+ continue;
2134
+ }
2135
+ if (rel && typeof rel === "object" && !Array.isArray(rel)) {
2136
+ if (rel.type && rel.id) {
2137
+ json.relationships[relName] = {
2138
+ data: {
2139
+ type: rel.type,
2140
+ id: rel.id
2141
+ }
2142
+ };
2143
+ } else if (rel.hasOwnProperty("toJSON")) {
2144
+ json.relationships[relName] = {
2145
+ data: rel.toJSON()
2146
+ };
2147
+ } else {
2148
+ continue;
2149
+ }
2150
+ continue;
2151
+ }
2152
+ if (Array.isArray(rel)) {
2153
+ json.relationships[relName] = {
2154
+ data: rel.map((item) => {
2155
+ if (item && typeof item === "object") {
2156
+ if (item.type && item.id) {
2157
+ return {
2158
+ type: item.type,
2159
+ id: item.id
2160
+ };
2161
+ } else if (item.hasOwnProperty("toJSON")) {
2162
+ return item.toJSON();
2163
+ }
2164
+ }
2165
+ return item;
2166
+ })
2167
+ };
2168
+ continue;
2169
+ }
2170
+ continue;
2171
+ }
2172
+ }
2173
+ dbg("item.json", json);
2174
+ return json;
2175
+ }
2176
+ /**
2177
+ * Serialize a single relationship to JSON:API wire format
2178
+ *
2179
+ * Converts runtime relationship state (hydrated objects, arrays, null) to
2180
+ * JSON:API relationship format for wire transmission.
2181
+ *
2182
+ * IMPORTANT: This is a serialization function - it does NOT mutate runtime state.
2183
+ * Runtime relationships remain as hydrated objects/arrays/null.
2184
+ *
2185
+ * @param {Object|Array|null} rel - Runtime relationship value
2186
+ * @returns {Object} JSON:API relationship format: { data: { type, id } } or { data: [{ type, id }, ...] } or { data: null }
2187
+ */
2188
+ _serializeRelationshipToWireFormat(rel) {
2189
+ return this.adapter.serializeRelationship(rel);
2190
+ }
2191
+ /**
2192
+ * Sync pending operations
2193
+ */
2194
+ sync() {
2195
+ if (this.syncOp) {
2196
+ let syncOp = this.syncOp;
2197
+ dbg("Syncing", this, syncOp);
2198
+ this.syncOp = null;
2199
+ return syncOp();
2200
+ } else {
2201
+ dbg("Nothing to sync on", this);
2202
+ }
2203
+ }
2204
+ /**
2205
+ * Perform update operation
2206
+ *
2207
+ * Builds PATCH payload with changed attributes and relationships.
2208
+ *
2209
+ * IMPORTANT: Runtime relationship state (hydrated objects/arrays/null) is serialized
2210
+ * to JSON:API wire format ({ data: { type, id } }) for transmission. Runtime state
2211
+ * remains unchanged - this is a serialization layer, not a state mutation.
2212
+ */
2213
+ perform_update(opts) {
2214
+ let options = {
2215
+ rerender: true
2216
+ };
2217
+ Object.assign(options, opts);
2218
+ return new Promise((resolve, reject) => {
2219
+ let toUpdate = {
2220
+ id: this.id,
2221
+ attributes: {},
2222
+ relationships: {}
2223
+ };
2224
+ if (this.type) {
2225
+ toUpdate.type = this.type;
2226
+ }
2227
+ Object.getOwnPropertyNames(this.attributes).forEach((attrName) => {
2228
+ if (this.shadow && this.shadow.attributes[attrName] !== this.attributes[attrName]) {
2229
+ toUpdate.attributes[attrName] = this.attributes[attrName];
2230
+ }
2231
+ });
2232
+ Object.getOwnPropertyNames(this.relationships).forEach((relaName) => {
2233
+ if (this.shadow && this.shadow.relationships[relaName] !== this.relationships[relaName]) {
2234
+ const runtimeRel = this.relationships[relaName];
2235
+ toUpdate.relationships[relaName] = this._serializeRelationshipToWireFormat(runtimeRel);
2236
+ }
2237
+ });
2238
+ if (!Object.getOwnPropertyNames(toUpdate.attributes).length && !Object.getOwnPropertyNames(toUpdate.relationships).length) {
2239
+ this.syncOp = null;
2240
+ resolve(this);
2241
+ return;
2242
+ }
2243
+ const payload = this.adapter.serializeForUpdate(toUpdate);
2244
+ if (opts && opts.justSimulate) {
2245
+ dbg(payload.body);
2246
+ resolve(this);
2247
+ return;
2248
+ }
2249
+ let updateUrlString = this.updateUrl.toString ? this.updateUrl.toString() : this.updateUrl;
2250
+ this.storage.update(this, updateUrlString, { contentType: payload.contentType }, payload.body).then((resp) => {
2251
+ let newData = this.adapter.parseItemResponse(resp.data);
2252
+ Object.assign(this, newData);
2253
+ this.shadow = null;
2254
+ if (options.rerender) {
2255
+ this.views.forEach((view) => {
2256
+ view.render();
2257
+ this._trigger("afterrender", this, view);
2258
+ });
2259
+ }
2260
+ this._trigger("update", this);
2261
+ if (this.collection) {
2262
+ this.collection.onupdate();
2263
+ }
2264
+ resolve(this);
2265
+ }).catch((error2) => {
2266
+ dbg("Update NOK", this.updateUrl, patchData, error2);
2267
+ if (error2 instanceof Error && error2.jqXHR) {
2268
+ reject(error2);
2269
+ } else if (error2.jqXHR) {
2270
+ reject(error2);
2271
+ } else {
2272
+ reject(error2 instanceof Error ? error2 : new Error(String(error2)));
2273
+ }
2274
+ });
2275
+ });
2276
+ }
2277
+ /**
2278
+ * Update item
2279
+ */
2280
+ update(updateData, opts) {
2281
+ if (!updateData || updateData.constructor !== Object) {
2282
+ return;
2283
+ }
2284
+ let updateOptions = {
2285
+ sync: true,
2286
+ rerender: true
2287
+ };
2288
+ if (opts && opts.constructor === Object) {
2289
+ Object.assign(updateOptions, opts);
2290
+ }
2291
+ if (!this.shadow) {
2292
+ this.shadow = { attributes: {}, relationships: {} };
2293
+ Object.assign(this.shadow.attributes, this.attributes);
2294
+ Object.assign(this.shadow.relationships, this.relationships);
2295
+ }
2296
+ const updateRelation = (rel, data) => {
2297
+ dbg("update relation", rel, data);
2298
+ if (data === null) {
2299
+ return null;
2300
+ }
2301
+ if (rel && Array.isArray(rel)) {
2302
+ if (Array.isArray(data)) {
2303
+ return data.map((item) => {
2304
+ if (typeof item === "object" && item !== null) {
2305
+ return new _Item().loadFromData(item);
2306
+ }
2307
+ return item;
2308
+ });
2309
+ }
2310
+ dbg("to fix: array relationship update");
2311
+ return rel;
2312
+ }
2313
+ if (typeof data === "object" || data === null) {
2314
+ dbg("Update 1:1 relation");
2315
+ let item = new _Item().loadFromData(data);
2316
+ dbg("relation", item);
2317
+ return item;
2318
+ }
2319
+ if (typeof data === "string" || typeof data === "number") {
2320
+ dbg("Update 1:1 relation with id", data);
2321
+ if (rel && rel.id && (rel.id === data || String(rel.id) === String(data))) {
2322
+ return rel;
2323
+ }
2324
+ const newRel = {
2325
+ id: String(data)
2326
+ };
2327
+ if (rel && rel.type) {
2328
+ newRel.type = rel.type;
2329
+ }
2330
+ return newRel;
2331
+ }
2332
+ return rel;
2333
+ };
2334
+ Object.getOwnPropertyNames(this.relationships).forEach((relName) => {
2335
+ if (!updateData.hasOwnProperty(relName)) {
2336
+ return;
2337
+ }
2338
+ if (updateData[relName] === null) {
2339
+ this.relationships[relName] = null;
2340
+ return;
2341
+ }
2342
+ this.relationships[relName] = updateRelation(this.relationships[relName], updateData[relName]);
2343
+ delete updateData[relName];
2344
+ });
2345
+ Object.getOwnPropertyNames(updateData).forEach((attrName) => {
2346
+ if (updateData[attrName] && typeof updateData[attrName] === "object") {
2347
+ if (!this.strict && typeof this.relationships[attrName] === "undefined") {
2348
+ this.relationships[attrName] = updateRelation(this.relationships[attrName], updateData[attrName]);
2349
+ }
2350
+ return;
2351
+ }
2352
+ if (!this.shadow.attributes.hasOwnProperty(attrName)) {
2353
+ if (!this.strict) {
2354
+ this.attributes[attrName] = updateData[attrName];
2355
+ }
2356
+ return;
2357
+ }
2358
+ if (updateData[attrName] !== this.shadow.attributes[attrName]) {
2359
+ this.attributes[attrName] = updateData[attrName];
2360
+ }
2361
+ });
2362
+ dbg("updateOptions", updateOptions);
2363
+ if (updateOptions.sync) {
2364
+ return this.perform_update(updateOptions);
2365
+ }
2366
+ return new Promise((resolve) => {
2367
+ this.syncOp = () => this.perform_update(updateOptions);
2368
+ this.views.forEach((view) => {
2369
+ if (updateOptions.rerender) {
2370
+ view.render();
2371
+ }
2372
+ });
2373
+ resolve();
2374
+ });
2375
+ }
2376
+ /**
2377
+ * Remove item
2378
+ */
2379
+ remove() {
2380
+ return new Promise((resolve, reject) => {
2381
+ let ps = [];
2382
+ for (let i = this.views.length - 1; i >= 0; i--) {
2383
+ ps.push(this.views[i].remove());
2384
+ }
2385
+ let collection = this.collection;
2386
+ if (collection) {
2387
+ ps.push(collection.removeItem(this));
2388
+ }
2389
+ Promise.all(ps).then(() => {
2390
+ this._trigger("remove", this);
2391
+ if (collection) {
2392
+ console.log("removed");
2393
+ collection.onupdate();
2394
+ }
2395
+ }).finally(() => resolve());
2396
+ });
2397
+ }
2398
+ /**
2399
+ * Delete item
2400
+ */
2401
+ async delete(ops) {
2402
+ if (!this.deleteUrl) {
2403
+ return this.remove();
2404
+ }
2405
+ let deleteOps = {
2406
+ sync: true
2407
+ };
2408
+ if (ops && ops.constructor === Object) {
2409
+ Object.assign(deleteOps, ops);
2410
+ }
2411
+ try {
2412
+ log("delete", this.deleteUrl.toString());
2413
+ await this.storage.delete(this, this.deleteUrl.toString(), {});
2414
+ await this.remove();
2415
+ } catch (error2) {
2416
+ dbg("Error deleting item", error2);
2417
+ throw error2;
2418
+ }
2419
+ }
2420
+ /**
2421
+ * Render item
2422
+ */
2423
+ render(collectionView, addontop = false) {
2424
+ dbg("Render from item", this);
2425
+ this.views.forEach((view) => {
2426
+ if (typeof collectionView === "undefined") {
2427
+ dbg("collectionView is undefined so render view");
2428
+ view.render();
2429
+ } else if (view.container === collectionView) {
2430
+ dbg("collectionView matches view container so render view");
2431
+ view.render(false, addontop);
2432
+ }
2433
+ dbg("trigger afterrender", this, view, view.el);
2434
+ this._trigger("afterrender", this, view);
2435
+ });
2436
+ return this;
2437
+ }
2438
+ /**
2439
+ * Destroy item and clean up resources
2440
+ *
2441
+ * Removes event handlers, views, and clears references.
2442
+ * Safe to call multiple times.
2443
+ *
2444
+ * @returns {Item} This instance for chaining
2445
+ */
2446
+ destroy() {
2447
+ const viewsToDestroy = this.views ? [...this.views] : [];
2448
+ viewsToDestroy.forEach((view) => {
2449
+ if (view && typeof view.destroy === "function") {
2450
+ view.destroy();
2451
+ }
2452
+ });
2453
+ this.views = [];
2454
+ this.callbacks = {};
2455
+ if (this.collection) {
2456
+ this.collection = null;
2457
+ }
2458
+ this.storage = null;
2459
+ this.url = null;
2460
+ this.updateUrl = null;
2461
+ this.deleteUrl = null;
2462
+ this.attributes = {};
2463
+ this.relationships = {};
2464
+ this.shadow = null;
2465
+ this.views = [];
2466
+ return this;
2467
+ }
2468
+ };
2469
+
2470
+ // src/CollectionView.js
2471
+ var CollectionView = class {
2472
+ constructor(options = {}) {
2473
+ this.el = null;
2474
+ this.type = "CollectionView";
2475
+ this.container = null;
2476
+ this.collection = null;
2477
+ this.itemsContainer = null;
2478
+ this.allowempty = true;
2479
+ try {
2480
+ Object.assign(this, parseOptions(options));
2481
+ } catch (e) {
2482
+ throw new Error("Error on CollectionView init", e);
2483
+ }
2484
+ this.dataBindings = getBoundObjects(this.el);
2485
+ }
2486
+ /**
2487
+ * Reset the view
2488
+ */
2489
+ reset(force) {
2490
+ if (this.allowempty || force) {
2491
+ if (this.el) {
2492
+ $(this.el).empty();
2493
+ }
2494
+ }
2495
+ return this;
2496
+ }
2497
+ /**
2498
+ * Render the collection view
2499
+ */
2500
+ render() {
2501
+ dbg("Render _collectionView", this.collection);
2502
+ if (this.collection && this.collection.navtype === "page") {
2503
+ this.reset();
2504
+ }
2505
+ if (this.collection && this.collection.items.length === 0) {
2506
+ this.renderEmpty();
2507
+ return this;
2508
+ }
2509
+ if (this.collection) {
2510
+ this.collection.items.forEach((item) => {
2511
+ item.render(this);
2512
+ });
2513
+ }
2514
+ return this;
2515
+ }
2516
+ /**
2517
+ * Render empty state
2518
+ */
2519
+ renderEmpty() {
2520
+ if (!this.collection || !this.collection.emptyview) {
2521
+ return this;
2522
+ }
2523
+ this.reset();
2524
+ $(this.el).append(this.collection.emptyview);
2525
+ return this;
2526
+ }
2527
+ /**
2528
+ * Destroy view and clean up resources
2529
+ */
2530
+ destroy() {
2531
+ if (this.el) {
2532
+ const $el = $(this.el);
2533
+ $el.empty();
2534
+ $el.removeData();
2535
+ }
2536
+ this.collection = null;
2537
+ this.el = null;
2538
+ this.container = null;
2539
+ this.itemsContainer = null;
2540
+ this.dataBindings = null;
2541
+ return this;
2542
+ }
2543
+ };
2544
+
2545
+ // src/Paging.js
2546
+ var Paging = class {
2547
+ constructor(pagingEl, collection) {
2548
+ this.collection = collection;
2549
+ this.el = $(pagingEl);
2550
+ this.collection.paging = this;
2551
+ this.iniOffset = (this.collection.offset ? this.collection.offset : 0) * 1;
2552
+ this.defaultPageSize = 20;
2553
+ this.pageSize = this.collection.pageSize;
2554
+ this.setupPageSizeInput();
2555
+ this.setupOffsetInput();
2556
+ this.buttons = this.extractButtons();
2557
+ log("buttons", this.buttons);
2558
+ this.setupTotalCount();
2559
+ this.render();
2560
+ }
2561
+ /**
2562
+ * Setup page size input handler
2563
+ */
2564
+ setupPageSizeInput() {
2565
+ let pageSizeInp = $(this.collection.pagesizeinp);
2566
+ if (pageSizeInp.length) {
2567
+ this.collection.setPageSize(pageSizeInp.val());
2568
+ pageSizeInp.off("change").on("change", () => {
2569
+ if (this.collection.setPageSize(pageSizeInp.val())) {
2570
+ this.collection.loadFromRemote();
2571
+ }
2572
+ });
2573
+ }
2574
+ }
2575
+ /**
2576
+ * Setup offset input handler
2577
+ */
2578
+ setupOffsetInput() {
2579
+ let offsetInp = $(this.collection.offsetinp);
2580
+ if (offsetInp.length) {
2581
+ this.collection.setOffset(offsetInp.val());
2582
+ offsetInp.off("change").on("change", () => {
2583
+ if (this.collection.setOffset(offsetInp.val())) {
2584
+ this.collection.loadFromRemote();
2585
+ }
2586
+ });
2587
+ }
2588
+ }
2589
+ /**
2590
+ * Extract button templates from container
2591
+ */
2592
+ extractButtons() {
2593
+ let buttons = {};
2594
+ const pageBtn = $(this.el).find("[name=page]");
2595
+ if (pageBtn.length) {
2596
+ buttons.page = pageBtn.clone();
2597
+ pageBtn.remove();
2598
+ }
2599
+ const prevBtn = $(this.el).find("[name=prev]");
2600
+ if (prevBtn.length) {
2601
+ buttons.prev = prevBtn.clone();
2602
+ prevBtn.remove();
2603
+ }
2604
+ const nextBtn = $(this.el).find("[name=next]");
2605
+ if (nextBtn.length) {
2606
+ buttons.next = nextBtn.clone();
2607
+ nextBtn.remove();
2608
+ }
2609
+ const firstBtn = $(this.el).find("[name=first]");
2610
+ if (firstBtn.length) {
2611
+ buttons.first = firstBtn.clone();
2612
+ firstBtn.remove();
2613
+ }
2614
+ const lastBtn = $(this.el).find("[name=last]");
2615
+ if (lastBtn.length) {
2616
+ buttons.last = lastBtn.clone();
2617
+ lastBtn.remove();
2618
+ }
2619
+ return buttons;
2620
+ }
2621
+ /**
2622
+ * Setup total count element
2623
+ */
2624
+ setupTotalCount() {
2625
+ this.$totalCount = $(this.collection.totalrecscount);
2626
+ }
2627
+ /**
2628
+ * Clear container
2629
+ */
2630
+ clearContainer() {
2631
+ $(this.el).empty();
2632
+ $(this.el).find("[data-type=pages]").empty();
2633
+ }
2634
+ /**
2635
+ * Update total count display
2636
+ */
2637
+ updateTotalCount(total) {
2638
+ if (this.$totalCount && this.$totalCount.length) {
2639
+ if (this.$totalCount[0].tagName === "INPUT") {
2640
+ this.$totalCount.val(total);
2641
+ } else {
2642
+ this.$totalCount.text(total);
2643
+ }
2644
+ }
2645
+ }
2646
+ /**
2647
+ * Create and append button element
2648
+ */
2649
+ appendButton(button, clickHandler, title) {
2650
+ let btn = button.clone();
2651
+ if (title !== void 0) {
2652
+ btn.attr("title", title);
2653
+ }
2654
+ btn.on("click", clickHandler);
2655
+ $(this.el).append(btn);
2656
+ return btn;
2657
+ }
2658
+ /**
2659
+ * Render pagination controls
2660
+ */
2661
+ render() {
2662
+ const pagesToShow = 5;
2663
+ const total = this.collection.total;
2664
+ log("Paging render", total, this.buttons);
2665
+ this.updateTotalCount(total);
2666
+ this.clearContainer();
2667
+ this.iniOffset = this.collection.offset * 1;
2668
+ if (this.collection.pageSize) {
2669
+ this.pageSize = this.collection.pageSize;
2670
+ } else if (total - this.iniOffset - this.collection.items.length > 0) {
2671
+ this.pageSize = this.collection.items.length;
2672
+ } else {
2673
+ this.pageSize = this.defaultPageSize;
2674
+ }
2675
+ this.pageSize = this.pageSize * 1;
2676
+ log("Paging obj", this, this.pageSize, total);
2677
+ if (this.pageSize > total) {
2678
+ return;
2679
+ }
2680
+ if (this.iniOffset > 0) {
2681
+ if (this.buttons.first) {
2682
+ this.appendButton(
2683
+ this.buttons.first,
2684
+ () => {
2685
+ this.collection.setOffset(0);
2686
+ this.collection.loadFromRemote();
2687
+ },
2688
+ 0
2689
+ );
2690
+ }
2691
+ if (this.buttons.prev) {
2692
+ this.appendButton(
2693
+ this.buttons.prev,
2694
+ () => {
2695
+ this.collection.setOffset(this.iniOffset - this.pageSize);
2696
+ this.collection.loadFromRemote();
2697
+ },
2698
+ this.iniOffset - this.pageSize
2699
+ );
2700
+ }
2701
+ }
2702
+ let lowerLimit = Math.floor(this.iniOffset / this.pageSize) - Math.floor(pagesToShow / 2);
2703
+ lowerLimit = lowerLimit < 0 ? 0 : lowerLimit;
2704
+ let upperLimit = Math.floor(this.iniOffset / this.pageSize) + Math.ceil(pagesToShow / 2);
2705
+ upperLimit = upperLimit * this.pageSize < total ? upperLimit : Math.ceil(total / this.pageSize);
2706
+ for (let i = lowerLimit; i < upperLimit; i++) {
2707
+ if (!this.buttons.page) {
2708
+ continue;
2709
+ }
2710
+ const pageOffset = i * this.pageSize;
2711
+ const isActive = Math.floor(this.iniOffset / this.pageSize) === i;
2712
+ let pageBtn = this.buttons.page.clone();
2713
+ pageBtn.text(i + 1).attr("title", pageOffset).on("click", () => {
2714
+ this.collection.setOffset(pageOffset);
2715
+ this.collection.loadFromRemote();
2716
+ });
2717
+ if (isActive) {
2718
+ pageBtn.addClass("active").off("click");
2719
+ }
2720
+ $(this.el).append(pageBtn);
2721
+ }
2722
+ const nxtOffset = this.iniOffset + this.pageSize;
2723
+ if (this.iniOffset + this.pageSize < total) {
2724
+ if (this.buttons.next) {
2725
+ this.appendButton(
2726
+ this.buttons.next,
2727
+ () => {
2728
+ this.collection.setOffset(nxtOffset);
2729
+ this.collection.loadFromRemote();
2730
+ },
2731
+ nxtOffset
2732
+ );
2733
+ }
2734
+ if (this.buttons.last) {
2735
+ const lastPageOffset = (Math.ceil(total / this.pageSize) - 1) * this.pageSize;
2736
+ log("last button", total, lastPageOffset, this.pageSize, this.offset);
2737
+ if (lastPageOffset > this.iniOffset * 1) {
2738
+ this.appendButton(
2739
+ this.buttons.last,
2740
+ () => {
2741
+ this.collection.setOffset(lastPageOffset);
2742
+ this.collection.loadFromRemote();
2743
+ },
2744
+ lastPageOffset
2745
+ );
2746
+ }
2747
+ }
2748
+ }
2749
+ let offsetInp = $(this.collection.offsetinp);
2750
+ if (offsetInp.length) {
2751
+ offsetInp.val(this.iniOffset);
2752
+ }
2753
+ }
2754
+ /**
2755
+ * Destroy paging and clean up resources
2756
+ */
2757
+ destroy() {
2758
+ if (this.collection && this.collection.pagesizeinp) {
2759
+ const pageSizeInp = $(this.collection.pagesizeinp);
2760
+ pageSizeInp.off("change");
2761
+ }
2762
+ if (this.collection && this.collection.offsetinp) {
2763
+ const offsetInp = $(this.collection.offsetinp);
2764
+ offsetInp.off("change");
2765
+ }
2766
+ if (this.el) {
2767
+ const $el = $(this.el);
2768
+ $el.empty();
2769
+ $el.off();
2770
+ $el.removeData();
2771
+ }
2772
+ this.collection = null;
2773
+ this.el = null;
2774
+ this.buttons = null;
2775
+ this.$totalCount = null;
2776
+ return this;
2777
+ }
2778
+ };
2779
+
2780
+ // src/Collection.js
2781
+ var Collection = class {
2782
+ constructor(opts = {}) {
2783
+ this.url = null;
2784
+ this.deleteUrl = null;
2785
+ this.insertUrl = null;
2786
+ this.updateUrl = null;
2787
+ this.paging = null;
2788
+ this.view = null;
2789
+ this.offset = 0;
2790
+ this.total = null;
2791
+ this.pageSize = 10;
2792
+ this.template = null;
2793
+ this.navtype = "page";
2794
+ this.type = null;
2795
+ this.emptyview = null;
2796
+ this.items = [];
2797
+ this.addontop = false;
2798
+ this.uievents = [];
2799
+ this.setAttrAsId = null;
2800
+ this.itemListeners = null;
2801
+ this.adapter = null;
2802
+ this.callbacks = {};
2803
+ this.iterator = -1;
2804
+ trace("Collection init", opts);
2805
+ try {
2806
+ opts = parseOptions(opts);
2807
+ } catch (e) {
2808
+ throw new Error("Error on Collection init", e);
2809
+ }
2810
+ Object.defineProperty(this, "length", {
2811
+ get() {
2812
+ return this.items.length;
2813
+ },
2814
+ enumerable: true,
2815
+ configurable: true
2816
+ });
2817
+ let options = Object.assign({}, opts);
2818
+ Object.assign(this, options);
2819
+ if (options.hasOwnProperty("paging") && $(options.paging).length) {
2820
+ this.paging = new Paging($(options.paging)[0], this);
2821
+ }
2822
+ if (this.url) {
2823
+ this.setUrl(this.url);
2824
+ }
2825
+ if (this.deleteUrl) {
2826
+ this.setUrl(this.deleteUrl, "delete");
2827
+ }
2828
+ if (this.updateUrl) {
2829
+ this.setUrl(this.updateUrl, "update");
2830
+ }
2831
+ if (this.insertUrl) {
2832
+ this.setUrl(this.insertUrl, "insert");
2833
+ }
2834
+ if (this.view) {
2835
+ this.view.collection = this;
2836
+ }
2837
+ if (this.total) {
2838
+ this.total = this.total * 1;
2839
+ }
2840
+ if (["page", "scroll"].indexOf(this.navtype) === -1) {
2841
+ throw new Error("Invalid navigations type. Should be page or scroll");
2842
+ }
2843
+ this.adapter = resolveAdapter(opts.adapter);
2844
+ this.storage = opts.hasOwnProperty("storage") ? opts.storage : new Storage(
2845
+ (() => {
2846
+ const storageOpts = Object.assign({}, opts.ajaxOpts || {});
2847
+ if (opts.headers && typeof opts.headers === "object") {
2848
+ storageOpts.headers = Object.assign(
2849
+ {},
2850
+ storageOpts.headers || {},
2851
+ opts.headers
2852
+ );
2853
+ }
2854
+ return storageOpts;
2855
+ })()
2856
+ );
2857
+ if (typeof opts.listeners === "object") {
2858
+ for (let event in opts.listeners) {
2859
+ this.on(event, opts.listeners[event]);
2860
+ }
2861
+ }
2862
+ if (opts.itemListeners && typeof opts.itemListeners === "object") {
2863
+ this.itemListeners = opts.itemListeners;
2864
+ } else if (opts.itemOn && typeof opts.itemOn === "object") {
2865
+ this.itemListeners = opts.itemOn;
2866
+ }
2867
+ }
2868
+ /**
2869
+ * Event listener registration
2870
+ */
2871
+ on(eventName, cb) {
2872
+ if (typeof this.callbacks[eventName] === "undefined") {
2873
+ this.callbacks[eventName] = [];
2874
+ }
2875
+ this.callbacks[eventName].push(cb);
2876
+ return this;
2877
+ }
2878
+ /**
2879
+ * Remove event listener(s)
2880
+ * @param {string} eventName - Event name
2881
+ * @param {Function} [cb] - Optional callback to remove. If not provided, removes all listeners for the event
2882
+ * @returns {Collection} This instance for chaining
2883
+ */
2884
+ off(eventName, cb) {
2885
+ if (!eventName) {
2886
+ this.callbacks = {};
2887
+ return this;
2888
+ }
2889
+ if (!this.callbacks[eventName]) {
2890
+ return this;
2891
+ }
2892
+ if (cb) {
2893
+ const index = this.callbacks[eventName].indexOf(cb);
2894
+ if (index > -1) {
2895
+ this.callbacks[eventName].splice(index, 1);
2896
+ }
2897
+ if (this.callbacks[eventName].length === 0) {
2898
+ delete this.callbacks[eventName];
2899
+ }
2900
+ } else {
2901
+ delete this.callbacks[eventName];
2902
+ }
2903
+ return this;
2904
+ }
2905
+ /**
2906
+ * Register a one-time event listener
2907
+ * @param {string} eventName - Event name
2908
+ * @param {Function} cb - Callback function
2909
+ * @returns {Collection} This instance for chaining
2910
+ */
2911
+ once(eventName, cb) {
2912
+ const wrapper = (...args) => {
2913
+ cb(...args);
2914
+ this.off(eventName, wrapper);
2915
+ };
2916
+ return this.on(eventName, wrapper);
2917
+ }
2918
+ /**
2919
+ * Check if event has listeners
2920
+ * @param {string} eventName - Event name
2921
+ * @returns {boolean} True if event has listeners
2922
+ */
2923
+ hasListeners(eventName) {
2924
+ return this.callbacks[eventName] && Array.isArray(this.callbacks[eventName]) && this.callbacks[eventName].length > 0;
2925
+ }
2926
+ /**
2927
+ * Trigger an event (internal helper)
2928
+ * @private
2929
+ * @param {string} eventName - Event name
2930
+ * @param {...any} args - Arguments to pass to callbacks
2931
+ */
2932
+ _trigger(eventName, ...args) {
2933
+ if (this.callbacks[eventName] && Array.isArray(this.callbacks[eventName])) {
2934
+ this.callbacks[eventName].forEach((cb) => {
2935
+ if (typeof cb === "function") {
2936
+ cb(...args);
2937
+ }
2938
+ });
2939
+ }
2940
+ }
2941
+ /**
2942
+ * Emit/trigger an event manually
2943
+ * @param {string} eventName - Event name
2944
+ * @param {...any} args - Arguments to pass to callbacks
2945
+ * @returns {Collection} This instance for chaining
2946
+ */
2947
+ emit(eventName, ...args) {
2948
+ this._trigger(eventName, ...args);
2949
+ return this;
2950
+ }
2951
+ /**
2952
+ * Show listeners (debug)
2953
+ */
2954
+ showlisteners() {
2955
+ dbg(this.callbacks);
2956
+ }
2957
+ /**
2958
+ * Remove item from collection
2959
+ *
2960
+ * Removes item from items array. Does NOT trigger update event
2961
+ * (that's handled by Item.remove() to avoid duplication).
2962
+ *
2963
+ * @param {Item} item - Item instance to remove
2964
+ * @returns {Promise} Resolves when item is removed
2965
+ */
2966
+ removeItem(item) {
2967
+ const index = this.items.findIndex((i) => i === item || i.id && item.id && i.id === item.id);
2968
+ if (index !== -1) {
2969
+ this.items.splice(index, 1);
2970
+ }
2971
+ return Promise.resolve();
2972
+ }
2973
+ /**
2974
+ * Set page size
2975
+ */
2976
+ setPageSize(val) {
2977
+ if (/^\d+$/.test(val)) {
2978
+ this.pageSize = val;
2979
+ return true;
2980
+ }
2981
+ return false;
2982
+ }
2983
+ /**
2984
+ * Empty collection
2985
+ */
2986
+ empty() {
2987
+ return this.clear();
2988
+ }
2989
+ /**
2990
+ * Set offset
2991
+ */
2992
+ setOffset(val) {
2993
+ if (/^\d+$/.test(val)) {
2994
+ this.offset = val;
2995
+ return true;
2996
+ }
2997
+ return false;
2998
+ }
2999
+ /**
3000
+ * Bulk update (not implemented)
3001
+ */
3002
+ update(data) {
3003
+ throw new Error("Not implemented... yet");
3004
+ }
3005
+ setUrl(url, type) {
3006
+ if (!url)
3007
+ return this;
3008
+ switch (type) {
3009
+ case "delete":
3010
+ this.deleteUrl = createURL(url);
3011
+ break;
3012
+ case "update":
3013
+ this.updateUrl = createURL(url);
3014
+ break;
3015
+ case "insert":
3016
+ this.insertUrl = createURL(url);
3017
+ break;
3018
+ default:
3019
+ log("setUrl", url);
3020
+ this.url = createURL(url);
3021
+ this.deleteUrl = typeof this.deleteUrl == "string" ? createURL(this.deleteUrl) : this.deleteUrl ?? createURL(this.url);
3022
+ this.updateUrl = typeof this.updateUrl == "string" ? createURL(this.updateUrl) : this.updateUrl ?? createURL(this.url);
3023
+ this.insertUrl = typeof this.insertUrl == "string" ? createURL(this.insertUrl) : this.insertUrl ?? createURL(this.url);
3024
+ break;
3025
+ }
3026
+ return this;
3027
+ }
3028
+ /**
3029
+ * Receive remote data
3030
+ *
3031
+ * Processes JSON:API document by hydrating relationships and extracting data array.
3032
+ * Uses the new explicit hydration layer to replace relationship references with
3033
+ * actual resource objects from included resources.
3034
+ *
3035
+ * IMPORTANT: For single item responses (e.g., from append/create), uses parseItemData()
3036
+ * to avoid wrapping in array and triggering collection replacement behavior.
3037
+ */
3038
+ receiveRemoteData(data) {
3039
+ dbg("Remote data received", data);
3040
+ if (this.adapter.isSingleItemResponse(data)) {
3041
+ const hydratedItem = this.adapter.parseItemResponse(data);
3042
+ this.adapter.applyMetadata(this, this.adapter.extractMetadata(data));
3043
+ if (this.items.length === 0) {
3044
+ this.view.reset(true);
3045
+ }
3046
+ dbg("Append single item to collection");
3047
+ let newItem = this.loadItem(hydratedItem);
3048
+ newItem.render(this.view, this.addontop);
3049
+ this._trigger("afterrender", this);
3050
+ return newItem;
3051
+ }
3052
+ const { items, meta } = this.adapter.parseCollectionResponse(data, { type: this.type });
3053
+ this.adapter.applyMetadata(this, meta);
3054
+ if (items == null) {
3055
+ return;
3056
+ }
3057
+ if (items.constructor === Array) {
3058
+ log("Append multiple items to collection");
3059
+ if (this.items.length === 0) {
3060
+ this.view.reset(true);
3061
+ }
3062
+ const loadedItems = [];
3063
+ items.forEach((item) => {
3064
+ const loadedItem = this.loadItem(item);
3065
+ if (loadedItem) {
3066
+ loadedItems.push(loadedItem);
3067
+ }
3068
+ });
3069
+ this.render();
3070
+ return loadedItems;
3071
+ }
3072
+ }
3073
+ /**
3074
+ * Extract metadata and data from JSON:API document
3075
+ *
3076
+ * Extracts metadata (totalRecords, offset) from JSON:API response meta object
3077
+ * and returns the hydrated data array. Data hydration is handled by
3078
+ * parseCollectionData() before this is called.
3079
+ *
3080
+ * @param {Object} data - JSON:API document (data property should already be hydrated)
3081
+ * @returns {Array} Array of hydrated item data objects
3082
+ */
3083
+ extractMetadataAndData(data) {
3084
+ dbg("extract metadata and data", data);
3085
+ if (!data.hasOwnProperty("data")) {
3086
+ return data;
3087
+ }
3088
+ this.adapter.applyMetadata(this, this.adapter.extractMetadata(data));
3089
+ return data.data;
3090
+ }
3091
+ /**
3092
+ * Parse collection data from JSON:API document (legacy alias)
3093
+ *
3094
+ * @deprecated Use extractMetadataAndData() instead
3095
+ * @param {Object} data - JSON:API document
3096
+ * @returns {Array} Array of hydrated item data objects
3097
+ */
3098
+ parse(data) {
3099
+ return this.extractMetadataAndData(data);
3100
+ }
3101
+ /**
3102
+ * Load from data
3103
+ */
3104
+ loadFromData(data) {
3105
+ dbg("collection load from data", data);
3106
+ if (data === null || typeof data !== "object" || data.constructor !== Array) {
3107
+ dbg("cannot load ", data, " into collection ", this);
3108
+ return this;
3109
+ }
3110
+ if (this.navtype === "page") {
3111
+ this.items = [];
3112
+ }
3113
+ data.forEach((item) => {
3114
+ this.loadItem(item);
3115
+ });
3116
+ if (this.view) {
3117
+ this.view.render();
3118
+ } else {
3119
+ dbg("collection does not have a view ", this);
3120
+ }
3121
+ this._trigger("load", this);
3122
+ return this;
3123
+ }
3124
+ /**
3125
+ * Next page
3126
+ */
3127
+ next() {
3128
+ this.offset = parseInt(this.offset) + parseInt(this.pageSize);
3129
+ this.loadFromRemote();
3130
+ }
3131
+ /**
3132
+ * Previous page
3133
+ */
3134
+ prev() {
3135
+ this.offset = parseInt(this.offset) - parseInt(this.pageSize);
3136
+ this.loadFromRemote();
3137
+ }
3138
+ /**
3139
+ * Clear collection
3140
+ *
3141
+ * Synchronously clears items array and renders empty state.
3142
+ * For async item cleanup, use destroy() instead.
3143
+ *
3144
+ * @returns {Collection} This instance for chaining
3145
+ */
3146
+ clear() {
3147
+ this.items = [];
3148
+ if (this.view) {
3149
+ this.view.render();
3150
+ }
3151
+ this._trigger("update", this);
3152
+ return this;
3153
+ }
3154
+ /**
3155
+ * Render collection
3156
+ */
3157
+ render() {
3158
+ if (this.view) {
3159
+ this.view.render();
3160
+ }
3161
+ this._trigger("afterrender", this);
3162
+ return this;
3163
+ }
3164
+ /**
3165
+ * Load from remote
3166
+ *
3167
+ * Canonical method for loading collection data from API
3168
+ */
3169
+ loadFromRemote() {
3170
+ return this.loadFromDataSource();
3171
+ }
3172
+ load(data) {
3173
+ return data ? this.loadFromData(data) : this.loadFromRemote();
3174
+ }
3175
+ /**
3176
+ * Load from data source (internal implementation)
3177
+ * @private
3178
+ */
3179
+ loadFromDataSource() {
3180
+ const overlay = createOverlay(this);
3181
+ let loader = null;
3182
+ if (this.view && this.view.el) {
3183
+ loader = $(overlay).clone().insertBefore(this.view.el).width($(this.view.el).width()).height($(this.view.el).height());
3184
+ }
3185
+ this._trigger("beforeload", this);
3186
+ return new Promise((resolve, reject) => {
3187
+ if (!this.url) {
3188
+ loader.remove();
3189
+ reject(new Error("No valid URL provided"));
3190
+ return;
3191
+ }
3192
+ this.adapter.applyListQuery(this.url, {
3193
+ type: this.type,
3194
+ offset: this.offset,
3195
+ pageSize: this.pageSize
3196
+ });
3197
+ let urlString = this.url.toString ? this.url.toString() : this.url;
3198
+ this.storage.read(this, urlString, {}).then((res) => {
3199
+ if (this.navtype === "page") {
3200
+ this.items = [];
3201
+ }
3202
+ this.receiveRemoteData(res.data);
3203
+ this._trigger("load", this);
3204
+ if (loader) {
3205
+ $(loader).remove();
3206
+ }
3207
+ if (this.paging) {
3208
+ this.paging.render();
3209
+ }
3210
+ resolve(this);
3211
+ }).catch((error2) => {
3212
+ if (error2 instanceof Error && error2.jqXHR) {
3213
+ this.fail(error2.jqXHR, error2.textStatus || "error", error2.errorThrown || error2);
3214
+ loader.remove();
3215
+ reject(error2);
3216
+ } else if (error2 && error2.jqXHR) {
3217
+ this.fail(error2.jqXHR, error2.textStatus, error2.errorThrown);
3218
+ loader.remove();
3219
+ reject(error2);
3220
+ } else {
3221
+ this.fail(null, "error", error2);
3222
+ loader.remove();
3223
+ reject(error2);
3224
+ }
3225
+ });
3226
+ });
3227
+ }
3228
+ /**
3229
+ * Handle failure
3230
+ */
3231
+ fail(xhr, txt, err) {
3232
+ dbg("Fail to load collection", xhr, txt, err, this);
3233
+ }
3234
+ /**
3235
+ * On update callback
3236
+ */
3237
+ onupdate() {
3238
+ console.log("onupdate");
3239
+ this._trigger("update", this);
3240
+ return this;
3241
+ }
3242
+ /**
3243
+ * Insert a single new item into collection
3244
+ *
3245
+ * Creates a single item via POST request and adds it to the collection.
3246
+ * The server response should contain the created item in JSON:API format.
3247
+ *
3248
+ * @param {Object} itemData - Single item data object (not an array)
3249
+ * @returns {Promise<Item>} Promise resolving to the created Item instance
3250
+ * @throws {Error} If itemData is an array (use batchInsert() instead)
3251
+ */
3252
+ insert(itemData) {
3253
+ if (Array.isArray(itemData)) {
3254
+ throw new Error("insert() expects a single item object. Use batchInsert() for multiple items.");
3255
+ }
3256
+ const payload = this.adapter.serializeForCreate(itemData, { type: this.type });
3257
+ return new Promise((resolve, reject) => {
3258
+ if (!this.insertUrl) {
3259
+ this.insertUrl = this.url;
3260
+ }
3261
+ let insertUrlString = this.insertUrl.toString ? this.insertUrl.toString() : this.insertUrl;
3262
+ this.storage.create(this, insertUrlString, { contentType: payload.contentType }, payload.body).then((resp) => {
3263
+ let data = resp.data;
3264
+ let newItem = this.receiveRemoteData(data);
3265
+ log("newItem", newItem);
3266
+ this.onupdate();
3267
+ resolve(newItem);
3268
+ }).catch((resp) => {
3269
+ dbg("fail to receive data", resp);
3270
+ reject(resp);
3271
+ });
3272
+ });
3273
+ }
3274
+ /**
3275
+ * Batch insert multiple items into collection
3276
+ *
3277
+ * Creates multiple items via POST request and adds them to the collection.
3278
+ * The server response should contain an array of created items in JSON:API format.
3279
+ *
3280
+ * @param {Array} itemsData - Array of item data objects
3281
+ * @returns {Promise<Array<Item>>} Promise resolving to array of created Item instances
3282
+ * @throws {Error} If itemsData is not an array
3283
+ */
3284
+ batchInsert(itemsData) {
3285
+ if (!Array.isArray(itemsData)) {
3286
+ throw new Error("batchInsert() expects an array of items. Use insert() for a single item.");
3287
+ }
3288
+ if (itemsData.length === 0) {
3289
+ return Promise.resolve([]);
3290
+ }
3291
+ const payload = this.adapter.serializeForCreate(itemsData, { type: this.type });
3292
+ return new Promise((resolve, reject) => {
3293
+ if (!this.insertUrl) {
3294
+ this.insertUrl = this.url;
3295
+ }
3296
+ let insertUrlString = this.insertUrl.toString ? this.insertUrl.toString() : this.insertUrl;
3297
+ this.storage.create(this, insertUrlString, { contentType: payload.contentType }, payload.body).then((resp) => {
3298
+ let data = resp.data;
3299
+ const result = this.receiveRemoteData(data);
3300
+ const newItems = Array.isArray(result) ? result : result ? [result] : [];
3301
+ log("batchInsert newItems", newItems);
3302
+ this.onupdate();
3303
+ resolve(newItems);
3304
+ }).catch((resp) => {
3305
+ dbg("fail to receive batch data", resp);
3306
+ reject(resp);
3307
+ });
3308
+ });
3309
+ }
3310
+ /**
3311
+ * @deprecated Use insert() for single items or batchInsert() for multiple items
3312
+ * This method is bivalent and will be removed in a future version.
3313
+ * Alias for backward compatibility - delegates to insert() or batchInsert() based on input type.
3314
+ */
3315
+ append(itemData) {
3316
+ if (Array.isArray(itemData)) {
3317
+ return this.batchInsert(itemData);
3318
+ } else {
3319
+ return this.insert(itemData);
3320
+ }
3321
+ }
3322
+ /**
3323
+ * @deprecated Use insert() instead
3324
+ * Alias for backward compatibility
3325
+ */
3326
+ createItem(itemData) {
3327
+ return this.insert(itemData);
3328
+ }
3329
+ /**
3330
+ * @deprecated Use insert() instead
3331
+ * Alias for backward compatibility
3332
+ */
3333
+ newItem(itemData) {
3334
+ return this.insert(itemData);
3335
+ }
3336
+ /**
3337
+ * Load item
3338
+ */
3339
+ loadItem(itemData) {
3340
+ log("loadItem from collection", itemData);
3341
+ if (!itemData) {
3342
+ log("no item data", itemData);
3343
+ return null;
3344
+ }
3345
+ let opts = {
3346
+ type: this.type,
3347
+ collection: this,
3348
+ uievents: this.uievents,
3349
+ storage: this.storage,
3350
+ adapter: this.adapter
3351
+ };
3352
+ if (this.setAttrAsId && itemData.id == null) {
3353
+ log("set item id from attribute", this.setAttrAsId, itemData);
3354
+ itemData.id = itemData.attributes[this.setAttrAsId];
3355
+ }
3356
+ if (itemData.id && this.url) {
3357
+ let tmp;
3358
+ const url = createURL(this.url.toString());
3359
+ url.path += "/" + itemData.id;
3360
+ opts.url = createURL(url.toString());
3361
+ const updateUrl = createURL(this.updateUrl.toString());
3362
+ updateUrl.path += "/" + itemData.id;
3363
+ opts.updateUrl = createURL(updateUrl.toString());
3364
+ const deleteUrl = createURL(this.deleteUrl.toString());
3365
+ deleteUrl.path += "/" + itemData.id;
3366
+ opts.deleteUrl = createURL(deleteUrl.toString());
3367
+ const insertUrl = createURL(this.insertUrl.toString());
3368
+ insertUrl.path += "/" + itemData.id;
3369
+ opts.insertUrl = createURL(insertUrl.toString());
3370
+ }
3371
+ if (this.itemListeners) {
3372
+ opts.itemListeners = this.itemListeners;
3373
+ }
3374
+ let newItem = new Item(opts).bindView(new ItemView({
3375
+ template: this.template,
3376
+ container: this.view
3377
+ })).loadFromData(itemData);
3378
+ if (this.addontop) {
3379
+ dbg("Add on top");
3380
+ this.items.unshift(newItem);
3381
+ } else {
3382
+ this.items.push(newItem);
3383
+ }
3384
+ return newItem;
3385
+ }
3386
+ /**
3387
+ * Destroy collection and clean up resources
3388
+ *
3389
+ * Removes event handlers, destroys all items and views, clears references.
3390
+ * Safe to call multiple times.
3391
+ *
3392
+ * @returns {Collection} This instance for chaining
3393
+ */
3394
+ destroy() {
3395
+ const itemsToDestroy = [...this.items];
3396
+ itemsToDestroy.forEach((item) => {
3397
+ if (item && typeof item.destroy === "function") {
3398
+ item.destroy();
3399
+ }
3400
+ });
3401
+ this.items = [];
3402
+ if (this.view && typeof this.view.destroy === "function") {
3403
+ this.view.destroy();
3404
+ }
3405
+ this.view = null;
3406
+ if (this.filtering && typeof this.filtering.destroy === "function") {
3407
+ this.filtering.destroy();
3408
+ }
3409
+ this.filtering = null;
3410
+ if (this.paging && typeof this.paging.destroy === "function") {
3411
+ this.paging.destroy();
3412
+ }
3413
+ this.paging = null;
3414
+ this.callbacks = {};
3415
+ this.storage = null;
3416
+ this.url = null;
3417
+ this.deleteUrl = null;
3418
+ this.insertUrl = null;
3419
+ this.updateUrl = null;
3420
+ this.items = [];
3421
+ this.total = null;
3422
+ this.offset = 0;
3423
+ return this;
3424
+ }
3425
+ };
3426
+
3427
+ // src/Filtering.js
3428
+ var Filtering = class {
3429
+ constructor(filterForm, collection) {
3430
+ this.collection = collection;
3431
+ this.el = filterForm;
3432
+ let $form = $(filterForm);
3433
+ $form.data("instance", collection).on("submit", (e) => {
3434
+ dbg("Filter form was submitted");
3435
+ e.preventDefault();
3436
+ this.handleSubmit($form[0]);
3437
+ }).on("reset", () => {
3438
+ delete this.collection.url.parameters.filter;
3439
+ this.collection.loadFromRemote();
3440
+ dbg("filter form reset");
3441
+ });
3442
+ }
3443
+ /**
3444
+ * Handle form submit
3445
+ */
3446
+ handleSubmit(form) {
3447
+ let filter = [];
3448
+ for (let i = 0; i < form.elements.length; i++) {
3449
+ let el = form.elements[i];
3450
+ let $el = $(el);
3451
+ let value = $el.val();
3452
+ let operator = $el.data("operator");
3453
+ if (el.name && value) {
3454
+ filter.push(
3455
+ el.name + (operator ? operator : "=") + value
3456
+ );
3457
+ }
3458
+ }
3459
+ this.collection.offset = 0;
3460
+ if (filter.length) {
3461
+ this.collection.url.parameters.filter = filter.join(",");
3462
+ } else {
3463
+ delete this.collection.url.parameters.filter;
3464
+ }
3465
+ this.collection.loadFromRemote();
3466
+ }
3467
+ /**
3468
+ * Destroy filtering and clean up resources
3469
+ */
3470
+ destroy() {
3471
+ if (this.el) {
3472
+ const $form = $(this.el);
3473
+ $form.off("submit");
3474
+ $form.off("reset");
3475
+ if (typeof $form.removeData === "function") {
3476
+ $form.removeData("instance");
3477
+ }
3478
+ }
3479
+ this.collection = null;
3480
+ this.el = null;
3481
+ return this;
3482
+ }
3483
+ };
3484
+
3485
+ // src/Sorting.js
3486
+ var Sorting = class {
3487
+ constructor(sortHeader, collection) {
3488
+ this.el = sortHeader;
3489
+ this.collection = collection;
3490
+ const $sorts = sortHeader.find("[data-sortfld]").data("instance", this.collection).on("click", this.sortNow.bind(this));
3491
+ }
3492
+ sortNow(ev) {
3493
+ let $lnk = $(ev.currentTarget);
3494
+ let fld = $lnk.data("sortfld");
3495
+ let dir = $lnk.data("sortdir");
3496
+ let inst = this.collection;
3497
+ let sort = inst.url.parameters.hasOwnProperty("sort") ? inst.url.parameters.sort : "";
3498
+ let sortArr = [];
3499
+ sort.split(",").forEach(function(item) {
3500
+ let res = /^(-*)([a-z0-9\-\_]+)$/.exec(item.trim());
3501
+ if (!res)
3502
+ return;
3503
+ if (res[2] == fld)
3504
+ return;
3505
+ sortArr.push(item);
3506
+ });
3507
+ switch (dir) {
3508
+ case "up":
3509
+ sortArr.push("-" + fld);
3510
+ $lnk.data("sortdir", "down");
3511
+ $lnk.find(".sort-up").hide();
3512
+ $lnk.find(".sort-down").show();
3513
+ $lnk.find(".sort-default").hide();
3514
+ break;
3515
+ case "down":
3516
+ $lnk.data("sortdir", null);
3517
+ $lnk.find(".sort-up").hide();
3518
+ $lnk.find(".sort-down").hide();
3519
+ $lnk.find(".sort-default").show();
3520
+ break;
3521
+ default:
3522
+ $lnk.data("sortdir", "up");
3523
+ sortArr.push(fld);
3524
+ $lnk.find(".sort-up").show();
3525
+ $lnk.find(".sort-down").hide();
3526
+ $lnk.find(".sort-default").hide();
3527
+ }
3528
+ let nxtSort = sortArr.join(",");
3529
+ if (sort !== nxtSort) {
3530
+ inst.url.parameters.sort = nxtSort;
3531
+ inst.loadFromRemote();
3532
+ }
3533
+ }
3534
+ destroy() {
3535
+ if (this.el) {
3536
+ $(this.el).find("[data-sortfld]").each(function(sort) {
3537
+ $(this).off("click");
3538
+ $(this).removeData("instance");
3539
+ });
3540
+ }
3541
+ return this;
3542
+ }
3543
+ };
3544
+
3545
+ // src/utilities.js
3546
+ var utilities = {
3547
+ /**
3548
+ * Fill form fields with data from instance
3549
+ */
3550
+ fillForm: function(form, instance) {
3551
+ let formEl = $(form)[0];
3552
+ if ($(form).prop("tagName") !== "FORM") {
3553
+ return null;
3554
+ }
3555
+ if (!instance || !instance.hasOwnProperty("attributes")) {
3556
+ return null;
3557
+ }
3558
+ Object.getOwnPropertyNames(instance.attributes).forEach((attrName) => {
3559
+ if (!formEl.elements.hasOwnProperty(attrName)) {
3560
+ return;
3561
+ }
3562
+ let val = instance.attributes[attrName];
3563
+ let inp = formEl.elements[attrName];
3564
+ let $inp = $(inp);
3565
+ if (instance.attributes[attrName] && typeof instance.attributes[attrName] === "object" && instance.attributes[attrName].hasOwnProperty("id")) {
3566
+ val = instance.attributes[attrName].id;
3567
+ }
3568
+ if ($inp.attr("type") === "date") {
3569
+ val = val ? val.substr(0, 10) : val;
3570
+ }
3571
+ $inp.val(val);
3572
+ });
3573
+ if (!instance.relationships) {
3574
+ return;
3575
+ }
3576
+ Object.getOwnPropertyNames(instance.relationships).forEach((relName) => {
3577
+ if (!formEl.elements.hasOwnProperty(relName)) {
3578
+ return;
3579
+ }
3580
+ if (!instance.relationships[relName]) {
3581
+ $(formEl.elements[relName]).val(null);
3582
+ return;
3583
+ }
3584
+ let rel = instance.relationships[relName];
3585
+ let formElRel = formEl.elements[relName];
3586
+ if (rel.constructor === Array) {
3587
+ let vals = [];
3588
+ rel.forEach((relItem) => {
3589
+ vals.push(relItem.id);
3590
+ });
3591
+ $(formElRel).val(vals);
3592
+ } else {
3593
+ dbg("set ", relName, rel);
3594
+ if (formElRel.tagName === "SELECT") {
3595
+ let lbl = $(formElRel).data("label");
3596
+ let lblVal = rel.hasOwnProperty("attributes") && rel.attributes[lbl] ? rel.attributes[lbl] : rel.id;
3597
+ $("<option>").val(rel.id).text(lblVal).appendTo($(formElRel));
3598
+ }
3599
+ $(formElRel).val(rel.id);
3600
+ }
3601
+ });
3602
+ },
3603
+ /**
3604
+ * Capture form submit event and redirect it to callback
3605
+ */
3606
+ captureFormSubmit: function(form, cb) {
3607
+ let formEl = $(form);
3608
+ if (formEl.prop("tagName") !== "FORM" || typeof cb !== "function") {
3609
+ return;
3610
+ }
3611
+ formEl.off("submit").on("submit", (event) => {
3612
+ event.preventDefault();
3613
+ let frm = formEl[0];
3614
+ cb(this.fetchFormData(frm), event);
3615
+ });
3616
+ return formEl;
3617
+ },
3618
+ /**
3619
+ * Fetch form data - handles array notation (name[]) and normalizes form element
3620
+ */
3621
+ fetchFormData: function(form) {
3622
+ let formEl = $(form)[0];
3623
+ let formElements = {};
3624
+ Object.getOwnPropertyNames(formEl.elements).forEach((item) => {
3625
+ let el = formEl.elements[item];
3626
+ let $item = $(el);
3627
+ if (!$item.attr("name") || $item.attr("name") === "") {
3628
+ return;
3629
+ }
3630
+ if ($item.attr("type") === "checkbox" && !$item[0].checked) {
3631
+ return;
3632
+ }
3633
+ let name = $item.attr("name");
3634
+ let value = $item.val();
3635
+ let arrayMatch = /(\w+)\[\]/.exec(name);
3636
+ if (arrayMatch) {
3637
+ let arrayName = arrayMatch[1];
3638
+ if (!formElements[arrayName]) {
3639
+ formElements[arrayName] = [];
3640
+ }
3641
+ formElements[arrayName].push(value);
3642
+ } else {
3643
+ formElements[name] = value;
3644
+ }
3645
+ });
3646
+ return formElements;
3647
+ },
3648
+ /**
3649
+ * Extract form data - alias for fetchFormData (for backward compatibility)
3650
+ */
3651
+ extractFormData: function(form) {
3652
+ return this.fetchFormData(form);
3653
+ }
3654
+ };
3655
+
3656
+ // src/KViews.js
3657
+ var KViews = class _KViews {
3658
+ /**
3659
+ * Helper: Extract and merge options from element and parameters
3660
+ */
3661
+ static prepareOptions(el, opts) {
3662
+ if (!el) {
3663
+ dbg("Warning: no DOM element provided for apiator");
3664
+ return null;
3665
+ }
3666
+ if (typeof opts === "string") {
3667
+ opts = {
3668
+ url: opts
3669
+ };
3670
+ }
3671
+ let options = { dataBindings: {}, addontop: false };
3672
+ options = Object.assign(options, $(el).data());
3673
+ try {
3674
+ Object.assign(options, parseOptions(opts));
3675
+ } catch (e) {
3676
+ throw new Error("Error on KViews init", e);
3677
+ }
3678
+ return options;
3679
+ }
3680
+ /**
3681
+ * Helper: Check for existing instance and update if found
3682
+ *
3683
+ * SAFE UPDATE CONTRACT:
3684
+ * Only updates whitelisted safe configuration options.
3685
+ * Does not overwrite internal runtime state (callbacks, items, views, etc.)
3686
+ *
3687
+ * Safe to update:
3688
+ * - url, updateUrl, deleteUrl, insertUrl (via setUrl)
3689
+ * - template, type, pageSize, offset (configuration)
3690
+ * - emptyview, filter, paging (view configuration)
3691
+ *
3692
+ * NOT updated (internal state):
3693
+ * - callbacks, items, views, storage, filtering, paging instances
3694
+ * - length, total, iterator (runtime state)
3695
+ */
3696
+ static getOrUpdateInstance(el, options) {
3697
+ let existingInstance = $(el).data("instance");
3698
+ if (existingInstance !== void 0) {
3699
+ const safeUpdateOptions = [
3700
+ "url",
3701
+ "updateUrl",
3702
+ "deleteUrl",
3703
+ "insertUrl",
3704
+ "template",
3705
+ "type",
3706
+ "pageSize",
3707
+ "offset",
3708
+ "emptyview",
3709
+ "filter",
3710
+ "paging",
3711
+ "addontop",
3712
+ "uievents",
3713
+ "setAttrAsId",
3714
+ "itemListeners",
3715
+ "itemOn",
3716
+ "headers",
3717
+ "adapter"
3718
+ ];
3719
+ if (options.url) {
3720
+ existingInstance.setUrl(options.url);
3721
+ delete options.url;
3722
+ }
3723
+ const parsedOptions = parseOptions(options);
3724
+ const safeUpdates = {};
3725
+ safeUpdateOptions.forEach((key) => {
3726
+ if (parsedOptions.hasOwnProperty(key)) {
3727
+ safeUpdates[key] = parsedOptions[key];
3728
+ }
3729
+ });
3730
+ Object.assign(existingInstance, safeUpdates);
3731
+ if (safeUpdates.adapter) {
3732
+ existingInstance.adapter = resolveAdapter(safeUpdates.adapter);
3733
+ }
3734
+ if (safeUpdates.headers && existingInstance.storage && existingInstance.storage.defaultOptions) {
3735
+ existingInstance.storage.defaultOptions.headers = Object.assign(
3736
+ {},
3737
+ existingInstance.storage.defaultOptions.headers || {},
3738
+ safeUpdates.headers
3739
+ );
3740
+ }
3741
+ return existingInstance;
3742
+ }
3743
+ return null;
3744
+ }
3745
+ /**
3746
+ * Helper: Handle emptyview option
3747
+ */
3748
+ static processEmptyView(options) {
3749
+ if (options.hasOwnProperty("emptyview")) {
3750
+ options.emptyview = $(options.emptyview).remove();
3751
+ }
3752
+ }
3753
+ /**
3754
+ * Helper: Attach listeners and finalize instance
3755
+ */
3756
+ static finalizeInstance(el, instance, options, listeners) {
3757
+ if (listeners) {
3758
+ Object.getOwnPropertyNames(listeners).forEach((eventName) => {
3759
+ instance.on(eventName, listeners[eventName]);
3760
+ });
3761
+ }
3762
+ $(el).data("instance", instance);
3763
+ dbg("instance", instance.url);
3764
+ if (instance.url && (typeof options.dontload === "undefined" || !options.dontload)) {
3765
+ log("loadFromRemote now", options, instance);
3766
+ instance.loadFromRemote();
3767
+ }
3768
+ return instance;
3769
+ }
3770
+ /**
3771
+ * Create collection instance
3772
+ */
3773
+ static createCollectionInstance(el, opts) {
3774
+ let options = _KViews.prepareOptions(el, opts);
3775
+ log("createCollectionInstance", options);
3776
+ if (!options) {
3777
+ return null;
3778
+ }
3779
+ let existingInstance = _KViews.getOrUpdateInstance(el, options);
3780
+ if (existingInstance) {
3781
+ return existingInstance;
3782
+ }
3783
+ dbg("init apiator collection on ", el, options);
3784
+ _KViews.processEmptyView(options);
3785
+ let listeners = options.on;
3786
+ delete options.on;
3787
+ dbg("Create collection instance", options);
3788
+ let templateTxt = $(el).length ? $(el).html() : null;
3789
+ if (options.template) {
3790
+ if (options.template instanceof jQuery) {
3791
+ dbg("template is jQuery object", options.template, el);
3792
+ let $tpl = $(options.template).clone().removeAttr("id");
3793
+ templateTxt = $("<div>").append($tpl).html();
3794
+ } else if (typeof options.template === "string") {
3795
+ dbg("template is raw text: can be either a jQuery selector or raw HTML", options.template, el);
3796
+ templateTxt = $("<div>").append($(options.template).clone().removeAttr("id")).html();
3797
+ }
3798
+ }
3799
+ if (templateTxt !== null) {
3800
+ templateTxt = templateTxt.replace(/&lt;/gi, "<").replace(/&gt;/gi, ">").replace(/&apos;/gi, "'").replace(/&quot;/gi, '"').replace(/&nbsp;/gi, " ").replace(/&amp;/gi, "&");
3801
+ options.template = template(templateTxt);
3802
+ }
3803
+ let collectionConfig = {
3804
+ el,
3805
+ itemsContainer: options.hasOwnProperty("container") ? $(options.container) : el,
3806
+ allowempty: options.disableempty !== true
3807
+ };
3808
+ options.view = new CollectionView(collectionConfig);
3809
+ log("Collection constructor", options);
3810
+ let instance = new Collection(options);
3811
+ if (options.hasOwnProperty("filter")) {
3812
+ let filterEl = $(options.filter);
3813
+ if (filterEl.length && filterEl.prop("tagName") === "FORM") {
3814
+ instance.filtering = new Filtering(filterEl, instance);
3815
+ }
3816
+ }
3817
+ if (options.hasOwnProperty("sort")) {
3818
+ log("setup sorting", options.sort);
3819
+ let sortEl = $(options.sort);
3820
+ if (sortEl.length) {
3821
+ instance.sorting = new Sorting(sortEl, instance);
3822
+ }
3823
+ }
3824
+ _KViews.finalizeInstance(el, instance, options, listeners);
3825
+ return instance;
3826
+ }
3827
+ /**
3828
+ * Create item instance
3829
+ */
3830
+ static createItemInstance(el, opts, data = null) {
3831
+ let options = _KViews.prepareOptions(el, opts);
3832
+ if (!options) {
3833
+ return null;
3834
+ }
3835
+ let existingInstance = _KViews.getOrUpdateInstance(el, options);
3836
+ if (existingInstance) {
3837
+ return existingInstance;
3838
+ }
3839
+ dbg("init apiator item on ", el, options);
3840
+ _KViews.processEmptyView(options);
3841
+ let listeners = options.on;
3842
+ delete options.on;
3843
+ options.template = null;
3844
+ let templateTxt = null;
3845
+ if ($(el).length) {
3846
+ var node = $(el)[0];
3847
+ templateTxt = node ? node.outerHTML : null;
3848
+ }
3849
+ if (templateTxt) {
3850
+ templateTxt = templateTxt.replace(/&lt;/gi, "<").replace(/&gt;/gi, ">").replace(/&apos;/gi, "'").replace(/&quot;/gi, '"').replace(/&nbsp;/gi, " ").replace(/&amp;/gi, "&");
3851
+ options.template = template(templateTxt);
3852
+ }
3853
+ let elId = $(el).attr("id");
3854
+ let instance = new Item(options, data).bindView(new ItemView({
3855
+ template: options.template,
3856
+ el,
3857
+ id: elId ? elId : null
3858
+ }));
3859
+ _KViews.finalizeInstance(el, instance, options, listeners);
3860
+ if (options.dontload && instance.attributes && Object.keys(instance.attributes).length > 0) {
3861
+ instance.render();
3862
+ }
3863
+ return instance;
3864
+ }
3865
+ static helpers = utilities;
3866
+ /**
3867
+ * Global default data adapter (name or instance). Defaults to 'jsonapi'.
3868
+ */
3869
+ static get defaultAdapter() {
3870
+ return getDefaultAdapter();
3871
+ }
3872
+ static set defaultAdapter(adapter) {
3873
+ setDefaultAdapter(adapter);
3874
+ }
3875
+ /**
3876
+ * Register a custom data adapter by name.
3877
+ *
3878
+ * @param {string} name
3879
+ * @param {object} adapter
3880
+ */
3881
+ static registerAdapter(name, adapter) {
3882
+ registerAdapter(name, adapter);
3883
+ }
3884
+ };
3885
+ Object.defineProperty(KViews, "baseUrl", {
3886
+ enumerable: true,
3887
+ configurable: true,
3888
+ get() {
3889
+ return apiBaseConfig.baseUrl;
3890
+ },
3891
+ set(v) {
3892
+ apiBaseConfig.baseUrl = v;
3893
+ }
3894
+ });
3895
+ Object.defineProperty(KViews, "basePath", {
3896
+ enumerable: true,
3897
+ configurable: true,
3898
+ get() {
3899
+ return apiBaseConfig.basePath;
3900
+ },
3901
+ set(v) {
3902
+ apiBaseConfig.basePath = v;
3903
+ }
3904
+ });
3905
+ Object.defineProperty(KViews, "defaultHeaders", {
3906
+ enumerable: true,
3907
+ configurable: true,
3908
+ get() {
3909
+ return apiBaseConfig.defaultHeaders;
3910
+ },
3911
+ set(v) {
3912
+ if (v && typeof v === "object" && !Array.isArray(v)) {
3913
+ apiBaseConfig.defaultHeaders = v;
3914
+ } else {
3915
+ apiBaseConfig.defaultHeaders = {};
3916
+ }
3917
+ }
3918
+ });
3919
+
3920
+ // src/index.js
3921
+ var index_default = KViews;
3922
+ if (typeof window !== "undefined") {
3923
+ window.KViews = KViews;
3924
+ }
3925
+ if (typeof $ !== "undefined" && $.fn) {
3926
+ $.fn.kviews = function(opts) {
3927
+ let el = this.length ? this[0] : this;
3928
+ let resourcetype = "collection";
3929
+ if (opts && opts.resourcetype) {
3930
+ resourcetype = opts.resourcetype;
3931
+ } else {
3932
+ let dataResourcetype = $(el).data("resourcetype");
3933
+ if (dataResourcetype) {
3934
+ resourcetype = dataResourcetype;
3935
+ }
3936
+ }
3937
+ if (resourcetype === "item") {
3938
+ return KViews.createItemInstance(el, opts);
3939
+ } else {
3940
+ return KViews.createCollectionInstance(el, opts);
3941
+ }
3942
+ };
3943
+ Object.defineProperty($.fn.kviews, "baseUrl", {
3944
+ enumerable: true,
3945
+ configurable: true,
3946
+ get() {
3947
+ return apiBaseConfig.baseUrl;
3948
+ },
3949
+ set(v) {
3950
+ apiBaseConfig.baseUrl = v;
3951
+ }
3952
+ });
3953
+ Object.defineProperty($.fn.kviews, "basePath", {
3954
+ enumerable: true,
3955
+ configurable: true,
3956
+ get() {
3957
+ return apiBaseConfig.basePath;
3958
+ },
3959
+ set(v) {
3960
+ apiBaseConfig.basePath = v;
3961
+ }
3962
+ });
3963
+ Object.defineProperty($.fn.kviews, "defaultHeaders", {
3964
+ enumerable: true,
3965
+ configurable: true,
3966
+ get() {
3967
+ return apiBaseConfig.defaultHeaders;
3968
+ },
3969
+ set(v) {
3970
+ if (v && typeof v === "object" && !Array.isArray(v)) {
3971
+ apiBaseConfig.defaultHeaders = v;
3972
+ } else {
3973
+ apiBaseConfig.defaultHeaders = {};
3974
+ }
3975
+ }
3976
+ });
3977
+ $.fn.kviewsCollection = function(opts) {
3978
+ let options = {
3979
+ resourcetype: "collection"
3980
+ };
3981
+ if (typeof opts === "undefined") {
3982
+ opts = {};
3983
+ }
3984
+ if (typeof opts === "string") {
3985
+ opts = {
3986
+ url: opts
3987
+ };
3988
+ }
3989
+ opts = Object.assign(opts, options);
3990
+ return this.kviews(opts);
3991
+ };
3992
+ $.fn.kviewsItem = function(opts) {
3993
+ let options = {
3994
+ resourcetype: "item"
3995
+ };
3996
+ if (typeof opts === "undefined") {
3997
+ opts = {};
3998
+ }
3999
+ if (typeof opts === "string") {
4000
+ opts = {
4001
+ url: opts
4002
+ };
4003
+ }
4004
+ opts = Object.assign(opts, options);
4005
+ return this.kviews(opts);
4006
+ };
4007
+ }
4008
+ export {
4009
+ Collection,
4010
+ CollectionView,
4011
+ Filtering,
4012
+ Item,
4013
+ ItemView,
4014
+ JsonApiAdapter,
4015
+ KViews,
4016
+ Paging,
4017
+ PlainRestAdapter,
4018
+ Sorting,
4019
+ Storage,
4020
+ URL,
4021
+ createOverlay,
4022
+ createURL,
4023
+ dbg,
4024
+ deepmerge,
4025
+ index_default as default,
4026
+ error,
4027
+ getBoundObjects,
4028
+ getDefaultAdapter,
4029
+ log,
4030
+ parseOptions,
4031
+ registerAdapter,
4032
+ resolveAdapter,
4033
+ setDefaultAdapter,
4034
+ template,
4035
+ trace,
4036
+ uid,
4037
+ utilities
4038
+ };
4039
+ //# sourceMappingURL=index.js.map