retold-data-service 2.0.43 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "retold-data-service",
3
- "version": "2.0.43",
3
+ "version": "2.1.0",
4
4
  "description": "Serve up a whole model!",
5
5
  "main": "source/Retold-Data-Service.js",
6
6
  "bin": {
@@ -59,27 +59,29 @@
59
59
  },
60
60
  "homepage": "https://github.com/stevenvelozo/retold-data-service",
61
61
  "devDependencies": {
62
- "meadow-connection-sqlite": "^1.0.18",
62
+ "meadow-connection-sqlite": "^1.0.19",
63
63
  "pict-docuserve": "^0.1.5",
64
64
  "puppeteer": "^24.40.0",
65
- "quackage": "^1.1.1",
65
+ "quackage": "^1.1.2",
66
66
  "stricture": "^4.0.2",
67
67
  "supertest": "^7.2.2"
68
68
  },
69
69
  "dependencies": {
70
- "bibliograph": "^0.1.4",
71
- "fable": "^3.1.70",
70
+ "bibliograph": "^0.1.6",
71
+ "fable": "^3.1.71",
72
72
  "fable-serviceproviderbase": "^3.0.19",
73
- "meadow": "^2.0.35",
74
- "meadow-connection-mysql": "^1.0.17",
75
- "meadow-endpoints": "^4.0.15",
76
- "meadow-integration": "^1.0.35",
77
- "meadow-migrationmanager": "^0.0.13",
78
- "orator": "^6.0.4",
73
+ "meadow": "^2.0.37",
74
+ "meadow-connection-manager": "^1.1.0",
75
+ "pict-section-connection-form": "^0.0.1",
76
+ "meadow-connection-mysql": "^1.0.18",
77
+ "meadow-endpoints": "^4.0.17",
78
+ "meadow-integration": "^1.0.38",
79
+ "meadow-migrationmanager": "^0.0.14",
80
+ "orator": "^6.1.0",
79
81
  "orator-http-proxy": "^1.0.5",
80
82
  "orator-serviceserver-restify": "^2.0.10",
81
- "orator-static-server": "^2.0.4",
82
- "pict": "^1.0.363",
83
+ "orator-static-server": "^2.1.3",
84
+ "pict": "^1.0.365",
83
85
  "pict-section-histogram": "^1.0.0",
84
86
  "pict-sessionmanager": "^1.0.2",
85
87
  "stricture": "^4.0.2"
@@ -2,17 +2,57 @@
2
2
  * DataCloner Connection Management Routes
3
3
  *
4
4
  * Registers /clone/connection/* endpoints for managing the local database
5
- * connection (status, configure, test).
5
+ * connection (status, configure, test, schemas).
6
6
  *
7
7
  * @param {Object} pDataClonerService - The RetoldDataServiceDataCloner instance
8
8
  * @param {Object} pOratorServiceServer - The Orator ServiceServer instance
9
9
  */
10
+ const libMeadowConnectionManager = require('meadow-connection-manager');
11
+
10
12
  module.exports = (pDataClonerService, pOratorServiceServer) =>
11
13
  {
12
14
  let tmpFable = pDataClonerService.fable;
13
15
  let tmpCloneState = pDataClonerService.cloneState;
14
16
  let tmpPrefix = pDataClonerService.routePrefix;
15
17
 
18
+ // MCM is the canonical aggregator for connection-form schemas; the
19
+ // DataCloner browser UI fetches /clone/connection/schemas and renders
20
+ // its provider form straight off the result, instead of carrying
21
+ // per-provider HTML in the bundle. Cached on the service so we don't
22
+ // re-walk the filesystem on every poll.
23
+ let tmpSchemaMCM = null;
24
+ let tmpCachedSchemas = null;
25
+ let tmpGetSchemas = () =>
26
+ {
27
+ if (tmpCachedSchemas) { return tmpCachedSchemas; }
28
+ if (!tmpSchemaMCM)
29
+ {
30
+ tmpSchemaMCM = new libMeadowConnectionManager(tmpFable, {}, 'datacloner-mcm-formschemas');
31
+ }
32
+ // Defensive: an older MCM (< 1.1.0) won't have the aggregator
33
+ // method. Surface an empty array rather than throwing so the
34
+ // UI can fall back to a "schemas unavailable" message.
35
+ if (typeof(tmpSchemaMCM.getAllProviderFormSchemas) !== 'function')
36
+ {
37
+ tmpFable.log.warn('DataCloner: meadow-connection-manager is older than 1.1.0; connection form schemas are not available. Run npm update meadow-connection-manager.');
38
+ tmpCachedSchemas = [];
39
+ return tmpCachedSchemas;
40
+ }
41
+ tmpCachedSchemas = tmpSchemaMCM.getAllProviderFormSchemas();
42
+ return tmpCachedSchemas;
43
+ };
44
+
45
+ // GET /clone/connection/schemas
46
+ // Returns the form schemas for every provider whose module is
47
+ // installed in the host environment. Drives the connection
48
+ // section's UI (provider picker + per-provider field block).
49
+ pOratorServiceServer.get(`${tmpPrefix}/connection/schemas`,
50
+ (pRequest, pResponse, fNext) =>
51
+ {
52
+ pResponse.send(200, { Schemas: tmpGetSchemas() });
53
+ return fNext();
54
+ });
55
+
16
56
  // GET /clone/connection/status
17
57
  pOratorServiceServer.get(`${tmpPrefix}/connection/status`,
18
58
  (pRequest, pResponse, fNext) =>
@@ -11,6 +11,7 @@ const libViewSync = require('./views/PictView-DataCloner-Sync.js');
11
11
  const libViewExport = require('./views/PictView-DataCloner-Export.js');
12
12
  const libViewViewData = require('./views/PictView-DataCloner-ViewData.js');
13
13
  const libViewHistogram = require('pict-section-histogram');
14
+ const libViewConnectionForm = require('pict-section-connection-form');
14
15
 
15
16
  class DataClonerApplication extends libPictApplication
16
17
  {
@@ -45,11 +46,35 @@ class DataClonerApplication extends libPictApplication
45
46
  BarColor: '#4a90d9',
46
47
  Bins: []
47
48
  }, libViewHistogram);
49
+
50
+ // Shared schema-driven connection form. Renders into the slot
51
+ // the DataCloner-Connection accordion shell exposes; the
52
+ // provider's bootstrapConnectionSchemas() pumps the schemas in
53
+ // once the host's /clone/connection/schemas endpoint responds.
54
+ this.pict.addView('PictSection-ConnectionForm',
55
+ Object.assign({}, libViewConnectionForm.default_configuration,
56
+ {
57
+ ContainerSelector: '#DataCloner-Connection-FormSlot',
58
+ DefaultDestinationAddress: '#DataCloner-Connection-FormSlot',
59
+ SchemasAddress: 'AppData.DataCloner.Connection.Schemas',
60
+ ActiveAddress: 'AppData.DataCloner.Connection.ActiveProvider',
61
+ FieldIDPrefix: 'datacloner-conn'
62
+ }), libViewConnectionForm);
48
63
  }
49
64
 
50
65
  onAfterInitializeAsync(fCallback)
51
66
  {
52
- // Centralized state (replaces global variables)
67
+ // Centralized state (replaces global variables).
68
+ //
69
+ // PersistFields covers the static, non-connection inputs only.
70
+ // Connection-section fields (provider picker + per-provider
71
+ // inputs) are schema-driven now: their DOM ids and
72
+ // localStorage keys are derived at runtime from the host's
73
+ // /clone/connection/schemas response and persistence is hooked
74
+ // up by Pict-Provider-DataCloner#bootstrapConnectionSchemas
75
+ // after the schema-driven Connection view re-renders. See
76
+ // PictView-DataCloner-Connection.js for the field-id
77
+ // convention.
53
78
  this.pict.AppData.DataCloner =
54
79
  {
55
80
  FetchedTables: [],
@@ -67,30 +92,32 @@ class DataClonerApplication extends libPictApplication
67
92
  'serverURL', 'authMethod', 'authURI', 'checkURI',
68
93
  'cookieName', 'cookieValueAddr', 'cookieValueTemplate', 'loginMarker',
69
94
  'userName', 'password', 'schemaURL', 'pageSize', 'dateTimePrecisionMS',
70
- 'connProvider', 'sqliteFilePath',
71
- 'mysqlServer', 'mysqlPort', 'mysqlUser', 'mysqlPassword', 'mysqlDatabase', 'mysqlConnectionLimit',
72
- 'mssqlServer', 'mssqlPort', 'mssqlUser', 'mssqlPassword', 'mssqlDatabase', 'mssqlConnectionLimit',
73
- 'mssqlRequestTimeoutSec', 'mssqlConnectionTimeoutSec',
74
- 'mssqlConnectMaxAttempts', 'mssqlDDLMaxAttempts',
75
- 'mssqlRetryInitialDelaySec', 'mssqlRetryMaxDelaySec',
76
- 'postgresqlHost', 'postgresqlPort', 'postgresqlUser', 'postgresqlPassword', 'postgresqlDatabase', 'postgresqlConnectionLimit',
77
- 'solrHost', 'solrPort', 'solrCore', 'solrPath',
78
- 'mongodbHost', 'mongodbPort', 'mongodbUser', 'mongodbPassword', 'mongodbDatabase', 'mongodbConnectionLimit',
79
- 'rocksdbFolder',
80
- 'bibliographFolder',
81
95
  'syncMaxRecords'
82
- ]
96
+ ],
97
+ // Connection state — populated by bootstrapConnectionSchemas().
98
+ // Initialized empty here so the Connection view's first
99
+ // onBeforeRender finds a valid (if empty) shape.
100
+ Connection:
101
+ {
102
+ Schemas: [],
103
+ ActiveProvider: '',
104
+ ProviderOptions: [],
105
+ ProviderForms: [],
106
+ NoSchemasSlot: [{}],
107
+ PreviewText: 'Loading providers…'
108
+ }
83
109
  };
84
110
 
85
111
  // Make pict available for inline onclick handlers
86
112
  window.pict = this.pict;
87
113
 
88
- // Render layout (which chains child view renders via onAfterRender)
114
+ // Render layout (which chains child view renders via onAfterRender).
115
+ // The Connection view renders an empty shell here — the schemas
116
+ // arrive asynchronously and trigger a re-render once they land.
89
117
  this.pict.views['DataCloner-Layout'].render();
90
118
 
91
- // Post-render initialization
119
+ // Post-render initialization for the static (non-connection) UI.
92
120
  this.pict.providers.DataCloner.initPersistence();
93
- this.pict.views['DataCloner-Connection'].onProviderChange();
94
121
  this.pict.providers.DataCloner.restoreDeployedTables();
95
122
  this.pict.providers.DataCloner.startLiveStatusPolling();
96
123
  this.pict.providers.DataCloner.initAccordionPreviews();
@@ -98,6 +125,13 @@ class DataClonerApplication extends libPictApplication
98
125
  this.pict.views['DataCloner-Layout'].collapseAllSections();
99
126
  this.pict.providers.DataCloner.initAutoProcess();
100
127
 
128
+ // Async: fetch the host's connection-form schemas and re-render
129
+ // the Connection section. bootstrapConnectionSchemas restores
130
+ // localStorage values + hooks save listeners once the new DOM
131
+ // is in place, then invokes onProviderChange() to surface the
132
+ // active provider's form.
133
+ this.pict.providers.DataCloner.bootstrapConnectionSchemas(function () { /* fire-and-forget */ });
134
+
101
135
  return fCallback();
102
136
  }
103
137
  }
@@ -76,60 +76,25 @@ class DataClonerProvider extends libPictProvider
76
76
 
77
77
  updateAllPreviews()
78
78
  {
79
- // Section 1 — Database Connection
80
- let tmpProvider = document.getElementById('connProvider');
81
- if (!tmpProvider) return;
82
- tmpProvider = tmpProvider.value;
83
- let tmpPreview1 = tmpProvider;
84
- if (tmpProvider === 'SQLite')
85
- {
86
- let tmpPath = document.getElementById('sqliteFilePath').value || '~/headlight-liveconnect-local/cloned.sqlite';
87
- tmpPreview1 = 'SQLite at ' + tmpPath;
88
- }
89
- else if (tmpProvider === 'MySQL')
90
- {
91
- let tmpHost = document.getElementById('mysqlServer').value || '127.0.0.1';
92
- let tmpPort = document.getElementById('mysqlPort').value || '3306';
93
- let tmpUser = document.getElementById('mysqlUser').value || 'root';
94
- tmpPreview1 = 'MySQL on ' + tmpHost + ':' + tmpPort + ' as ' + tmpUser;
95
- }
96
- else if (tmpProvider === 'MSSQL')
97
- {
98
- let tmpHost = document.getElementById('mssqlServer').value || '127.0.0.1';
99
- let tmpPort = document.getElementById('mssqlPort').value || '1433';
100
- let tmpUser = document.getElementById('mssqlUser').value || 'sa';
101
- tmpPreview1 = 'MSSQL on ' + tmpHost + ':' + tmpPort + ' as ' + tmpUser;
102
- }
103
- else if (tmpProvider === 'PostgreSQL')
104
- {
105
- let tmpHost = document.getElementById('postgresqlHost').value || '127.0.0.1';
106
- let tmpPort = document.getElementById('postgresqlPort').value || '5432';
107
- let tmpUser = document.getElementById('postgresqlUser').value || 'postgres';
108
- tmpPreview1 = 'PostgreSQL on ' + tmpHost + ':' + tmpPort + ' as ' + tmpUser;
109
- }
110
- else if (tmpProvider === 'MongoDB')
111
- {
112
- let tmpHost = document.getElementById('mongodbHost').value || '127.0.0.1';
113
- let tmpPort = document.getElementById('mongodbPort').value || '27017';
114
- tmpPreview1 = 'MongoDB on ' + tmpHost + ':' + tmpPort;
115
- }
116
- else if (tmpProvider === 'Solr')
117
- {
118
- let tmpHost = document.getElementById('solrHost').value || '127.0.0.1';
119
- let tmpPort = document.getElementById('solrPort').value || '8983';
120
- tmpPreview1 = 'Solr on ' + tmpHost + ':' + tmpPort;
79
+ // Section 1 — Database Connection (schema-driven; the
80
+ // Connection view owns the heuristic that turns the active
81
+ // schema's field values into preview text). The view's
82
+ // _buildPreviewText reads live DOM values for the active
83
+ // provider and falls back to schema Defaults if a field's
84
+ // element doesn't exist yet.
85
+ let tmpConnView = this.pict.views['DataCloner-Connection'];
86
+ let tmpConnState = (this.pict.AppData.DataCloner && this.pict.AppData.DataCloner.Connection) || null;
87
+ let tmpPreview1Text;
88
+ if (tmpConnView && tmpConnState && (tmpConnState.Schemas || []).length > 0)
89
+ {
90
+ tmpPreview1Text = tmpConnView._buildPreviewText(tmpConnState);
121
91
  }
122
- else if (tmpProvider === 'RocksDB')
123
- {
124
- let tmpFolder = document.getElementById('rocksdbFolder').value || '~/headlight-liveconnect-local/rocksdb';
125
- tmpPreview1 = 'RocksDB at ' + tmpFolder;
126
- }
127
- else if (tmpProvider === 'Bibliograph')
92
+ else
128
93
  {
129
- let tmpFolder = document.getElementById('bibliographFolder').value || '~/headlight-liveconnect-local/bibliograph';
130
- tmpPreview1 = 'Bibliograph at ' + tmpFolder;
94
+ tmpPreview1Text = (tmpConnState && tmpConnState.PreviewText) || 'Loading providers…';
131
95
  }
132
- document.getElementById('preview1').textContent = tmpPreview1;
96
+ let tmpPreview1El = document.getElementById('preview1');
97
+ if (tmpPreview1El) { tmpPreview1El.textContent = tmpPreview1Text; }
133
98
 
134
99
  // Section 2 — Remote Session
135
100
  let tmpServerURL = document.getElementById('serverURL').value;
@@ -210,14 +175,12 @@ class DataClonerProvider extends libPictProvider
210
175
  {
211
176
  let tmpSelf = this;
212
177
 
178
+ // Static (non-connection) fields that drive accordion previews.
179
+ // Connection-section fields hook updateAllPreviews via
180
+ // _persistConnectionFields() once schemas load — see
181
+ // bootstrapConnectionSchemas().
213
182
  let tmpPreviewFields = [
214
- 'connProvider', 'sqliteFilePath',
215
- 'mysqlServer', 'mysqlPort', 'mysqlUser',
216
- 'mssqlServer', 'mssqlPort', 'mssqlUser',
217
- 'postgresqlHost', 'postgresqlPort', 'postgresqlUser',
218
- 'mongodbHost', 'mongodbPort',
219
- 'solrHost', 'solrPort',
220
- 'rocksdbFolder', 'bibliographFolder',
183
+ 'connProvider',
221
184
  'serverURL', 'userName',
222
185
  'schemaURL',
223
186
  'pageSize', 'dateTimePrecisionMS',
@@ -258,7 +221,17 @@ class DataClonerProvider extends libPictProvider
258
221
  saveField(pFieldId)
259
222
  {
260
223
  let tmpEl = document.getElementById(pFieldId);
261
- if (tmpEl)
224
+ if (!tmpEl) { return; }
225
+ // Checkboxes persist .checked, everything else persists .value.
226
+ // Older code special-cased a small set of checkbox ids (solrSecure,
227
+ // mssqlLegacyPagination, etc.); the schema-driven path no longer
228
+ // needs those — the checkbox handler in bootstrapConnectionSchemas
229
+ // stores 'true' / 'false' which restore picks up below.
230
+ if (tmpEl.type === 'checkbox')
231
+ {
232
+ localStorage.setItem('dataCloner_' + pFieldId, tmpEl.checked ? 'true' : 'false');
233
+ }
234
+ else
262
235
  {
263
236
  localStorage.setItem('dataCloner_' + pFieldId, tmpEl.value);
264
237
  }
@@ -274,38 +247,40 @@ class DataClonerProvider extends libPictProvider
274
247
  if (tmpSaved !== null)
275
248
  {
276
249
  let tmpEl = document.getElementById(tmpId);
277
- if (tmpEl) tmpEl.value = tmpSaved;
250
+ if (tmpEl)
251
+ {
252
+ if (tmpEl.type === 'checkbox')
253
+ {
254
+ tmpEl.checked = (tmpSaved === 'true');
255
+ }
256
+ else
257
+ {
258
+ tmpEl.value = tmpSaved;
259
+ }
260
+ }
278
261
  }
279
262
  }
280
263
 
281
- // Restore checkbox state
264
+ // Restore checkbox state for non-connection checkboxes that
265
+ // aren't in PersistFields (these all live outside the schema-
266
+ // driven Connection section, so they stay hardcoded).
282
267
  let tmpSyncDeleted = localStorage.getItem('dataCloner_syncDeletedRecords');
283
268
  if (tmpSyncDeleted !== null)
284
269
  {
285
- document.getElementById('syncDeletedRecords').checked = tmpSyncDeleted === 'true';
270
+ let tmpEl = document.getElementById('syncDeletedRecords');
271
+ if (tmpEl) tmpEl.checked = tmpSyncDeleted === 'true';
286
272
  }
287
- // Restore sync mode
288
273
  let tmpSyncMode = localStorage.getItem('dataCloner_syncMode');
289
274
  if (tmpSyncMode === 'Ongoing')
290
275
  {
291
- document.getElementById('syncModeOngoing').checked = true;
292
- }
293
- let tmpSolrSecure = localStorage.getItem('dataCloner_solrSecure');
294
- if (tmpSolrSecure !== null)
295
- {
296
- document.getElementById('solrSecure').checked = tmpSolrSecure === 'true';
297
- }
298
- let tmpMssqlLegacyPagination = localStorage.getItem('dataCloner_mssqlLegacyPagination');
299
- if (tmpMssqlLegacyPagination !== null)
300
- {
301
- let tmpEl = document.getElementById('mssqlLegacyPagination');
302
- if (tmpEl) tmpEl.checked = tmpMssqlLegacyPagination === 'true';
276
+ let tmpEl = document.getElementById('syncModeOngoing');
277
+ if (tmpEl) tmpEl.checked = true;
303
278
  }
304
- // Restore advanced ID pagination checkbox
305
279
  let tmpAdvancedIDPagination = localStorage.getItem('dataCloner_syncAdvancedIDPagination');
306
280
  if (tmpAdvancedIDPagination !== null)
307
281
  {
308
- document.getElementById('syncAdvancedIDPagination').checked = tmpAdvancedIDPagination === 'true';
282
+ let tmpEl = document.getElementById('syncAdvancedIDPagination');
283
+ if (tmpEl) tmpEl.checked = tmpAdvancedIDPagination === 'true';
309
284
  }
310
285
  }
311
286
 
@@ -347,25 +322,9 @@ class DataClonerProvider extends libPictProvider
347
322
  });
348
323
  });
349
324
 
350
- // Persist solr secure checkbox
351
- let tmpSolrSecureEl = document.getElementById('solrSecure');
352
- if (tmpSolrSecureEl)
353
- {
354
- tmpSolrSecureEl.addEventListener('change', function()
355
- {
356
- localStorage.setItem('dataCloner_solrSecure', this.checked);
357
- });
358
- }
359
-
360
- // Persist MSSQL legacy pagination checkbox
361
- let tmpMssqlLegacyPaginationEl = document.getElementById('mssqlLegacyPagination');
362
- if (tmpMssqlLegacyPaginationEl)
363
- {
364
- tmpMssqlLegacyPaginationEl.addEventListener('change', function()
365
- {
366
- localStorage.setItem('dataCloner_mssqlLegacyPagination', this.checked);
367
- });
368
- }
325
+ // (Connection-section checkboxes solrSecure, mssqlLegacyPagination —
326
+ // are handled by bootstrapConnectionSchemas() which hooks change
327
+ // listeners after the schema-driven form renders.)
369
328
 
370
329
  // Persist advanced ID pagination checkbox
371
330
  let tmpAdvancedIDPaginationEl = document.getElementById('syncAdvancedIDPagination');
@@ -397,6 +356,136 @@ class DataClonerProvider extends libPictProvider
397
356
  }
398
357
  }
399
358
 
359
+ // ================================================================
360
+ // Connection Schemas Bootstrap
361
+ //
362
+ // Fetches the host's aggregated connection-form schemas and re-
363
+ // renders the Connection view so it shows the real provider list +
364
+ // per-provider field blocks. Then restores localStorage values
365
+ // for the new (schema-driven) DOM ids and hooks save listeners.
366
+ // ================================================================
367
+
368
+ bootstrapConnectionSchemas(fCallback)
369
+ {
370
+ let tmpSelf = this;
371
+ let tmpDone = (typeof(fCallback) === 'function') ? fCallback : function () {};
372
+
373
+ this.api('GET', '/clone/connection/schemas')
374
+ .then(function (pData)
375
+ {
376
+ let tmpSchemas = (pData && Array.isArray(pData.Schemas)) ? pData.Schemas : [];
377
+ tmpSelf._applyConnectionSchemas(tmpSchemas);
378
+ return tmpDone(null, tmpSchemas);
379
+ })
380
+ .catch(function (pError)
381
+ {
382
+ if (tmpSelf.fable && tmpSelf.fable.log && tmpSelf.fable.log.error)
383
+ {
384
+ tmpSelf.fable.log.error(`DataCloner: failed to fetch connection schemas: ${pError && pError.message}`);
385
+ }
386
+ // On failure, leave the empty-schema state in place — the
387
+ // shared view's "no schemas detected" notice will surface.
388
+ tmpSelf._applyConnectionSchemas([]);
389
+ return tmpDone(pError);
390
+ });
391
+ }
392
+
393
+ _applyConnectionSchemas(pSchemas)
394
+ {
395
+ let tmpAppData = this.pict.AppData.DataCloner;
396
+ if (!tmpAppData.Connection) { tmpAppData.Connection = { Schemas: [], ActiveProvider: '', PreviewText: '' }; }
397
+ tmpAppData.Connection.Schemas = pSchemas;
398
+
399
+ // Pick an initial ActiveProvider — restore from localStorage
400
+ // if the saved value matches one of the available providers,
401
+ // otherwise default to the first schema (or stay empty).
402
+ let tmpAvailable = pSchemas.map(function (pS) { return pS.Provider; });
403
+ let tmpSavedProvider = localStorage.getItem('dataCloner_activeProvider');
404
+ if (tmpSavedProvider && tmpAvailable.indexOf(tmpSavedProvider) >= 0)
405
+ {
406
+ tmpAppData.Connection.ActiveProvider = tmpSavedProvider;
407
+ }
408
+ else if (pSchemas.length > 0)
409
+ {
410
+ tmpAppData.Connection.ActiveProvider = pSchemas[0].Provider;
411
+ }
412
+
413
+ // Hand the schemas to the shared view, which renders the form
414
+ // into #DataCloner-Connection-FormSlot.
415
+ let tmpForm = this.pict.views['PictSection-ConnectionForm'];
416
+ if (tmpForm)
417
+ {
418
+ let tmpSelf = this;
419
+ tmpForm.options.OnProviderChange = function (pProvider)
420
+ {
421
+ tmpAppData.Connection.ActiveProvider = pProvider;
422
+ localStorage.setItem('dataCloner_activeProvider', pProvider);
423
+ // Re-hook persistence on the new active form's inputs
424
+ // (the shared view re-renders on provider change, so the
425
+ // previous input listeners are gone).
426
+ tmpSelf._persistConnectionFields(pSchemas);
427
+ tmpSelf.updateAllPreviews();
428
+ };
429
+ if (tmpAppData.Connection.ActiveProvider)
430
+ {
431
+ tmpForm._ActiveProvider = tmpAppData.Connection.ActiveProvider;
432
+ }
433
+ tmpForm.setSchemas(pSchemas);
434
+ }
435
+
436
+ // Re-render the DataCloner accordion shell so the preview text
437
+ // reflects the active provider.
438
+ let tmpAccordion = this.pict.views['DataCloner-Connection'];
439
+ if (tmpAccordion) { tmpAccordion.render(); }
440
+
441
+ // Restore values + hook input listeners on the freshly-rendered
442
+ // shared-view inputs.
443
+ this._persistConnectionFields(pSchemas);
444
+ this.updateAllPreviews();
445
+ }
446
+
447
+ _persistConnectionFields(pSchemas)
448
+ {
449
+ let tmpForm = this.pict.views['PictSection-ConnectionForm'];
450
+ if (!tmpForm) { return; }
451
+
452
+ let tmpSelf = this;
453
+
454
+ // Restore + hook every per-provider field. saveField()
455
+ // dispatches on element type internally so checkboxes and
456
+ // text inputs share the same path.
457
+ (pSchemas || []).forEach(function (pSchema)
458
+ {
459
+ (pSchema.Fields || []).forEach(function (pField)
460
+ {
461
+ let tmpId = tmpForm.fieldDOMId(pSchema.Provider, pField.Name);
462
+ let tmpEl = document.getElementById(tmpId);
463
+ if (!tmpEl) { return; }
464
+
465
+ let tmpSaved = localStorage.getItem('dataCloner_' + tmpId);
466
+ if (tmpSaved !== null)
467
+ {
468
+ if (tmpEl.type === 'checkbox')
469
+ {
470
+ tmpEl.checked = (tmpSaved === 'true');
471
+ }
472
+ else
473
+ {
474
+ tmpEl.value = tmpSaved;
475
+ }
476
+ }
477
+
478
+ // Avoid double-binding when the shared view re-renders
479
+ // on provider change. We tag the element so subsequent
480
+ // runs of this method are no-ops.
481
+ if (tmpEl.dataset && tmpEl.dataset.dataclonerHooked === '1') { return; }
482
+ if (tmpEl.dataset) { tmpEl.dataset.dataclonerHooked = '1'; }
483
+ tmpEl.addEventListener('input', function () { tmpSelf.saveField(tmpId); tmpSelf.updateAllPreviews(); });
484
+ tmpEl.addEventListener('change', function () { tmpSelf.saveField(tmpId); tmpSelf.updateAllPreviews(); });
485
+ });
486
+ });
487
+ }
488
+
400
489
  // ================================================================
401
490
  // Live Status Indicator
402
491
  // ================================================================