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