@qooxdoo/framework 7.7.2 → 7.9.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.
Files changed (93) hide show
  1. package/Manifest.json +2 -2
  2. package/lib/compiler/compile-info.json +91 -89
  3. package/lib/compiler/index.js +2517 -1488
  4. package/lib/resource/qx/tool/schema/compile-1-0-0.json +13 -0
  5. package/lib/resource/qx/tool/website/build/404.html +3 -25
  6. package/lib/resource/qx/tool/website/build/about.html +3 -25
  7. package/lib/resource/qx/tool/website/build/assets/common.js +20 -0
  8. package/lib/resource/qx/tool/website/build/diagnostics/dependson.html +3 -25
  9. package/lib/resource/qx/tool/website/build/diagnostics/requiredby.html +3 -22
  10. package/lib/resource/qx/tool/website/build/index.html +3 -25
  11. package/lib/resource/qx/tool/website/partials/footer.html +3 -21
  12. package/lib/resource/qx/tool/website/partials/head.html +0 -1
  13. package/package.json +2 -2
  14. package/source/class/qx/Bootstrap.js +6 -3
  15. package/source/class/qx/Promise.js +93 -6964
  16. package/source/class/qx/bom/Label.js +82 -2
  17. package/source/class/qx/bom/webfonts/WebFont.js +1 -0
  18. package/source/class/qx/core/Environment.js +83 -1
  19. package/source/class/qx/data/controller/List.js +50 -21
  20. package/source/class/qx/data/controller/MSelection.js +45 -12
  21. package/source/class/qx/data/marshal/Json.js +64 -11
  22. package/source/class/qx/dev/unit/AsyncWrapper.js +8 -0
  23. package/source/class/qx/event/Manager.js +163 -124
  24. package/source/class/qx/io/ImageLoader.js +6 -3
  25. package/source/class/qx/io/exception/Transport.js +1 -0
  26. package/source/class/qx/io/jsonrpc/Client.js +64 -8
  27. package/source/class/qx/io/jsonrpc/protocol/Request.js +10 -6
  28. package/source/class/qx/lang/Type.js +36 -3
  29. package/source/class/qx/promise/BluebirdImpl.js +6918 -0
  30. package/source/class/qx/promise/NativeWrapper.js +738 -0
  31. package/source/class/qx/test/Promise.js +1145 -22
  32. package/source/class/qx/test/bom/client/Pdfjs.js +4 -0
  33. package/source/class/qx/test/bom/element/AnimationJs.js +3 -0
  34. package/source/class/qx/test/bom/element/Style.js +1 -0
  35. package/source/class/qx/test/bom/media/MediaTestCase.js +6 -0
  36. package/source/class/qx/test/core/Environment.js +44 -0
  37. package/source/class/qx/test/data/controller/List.js +6 -0
  38. package/source/class/qx/test/data/marshal/Json.js +29 -0
  39. package/source/class/qx/test/io/MAssert.js +94 -0
  40. package/source/class/qx/test/io/TestMAssert.js +47 -0
  41. package/source/class/qx/test/io/jsonrpc/Client.js +79 -19
  42. package/source/class/qx/test/io/jsonrpc/PostMessageClient.js +152 -0
  43. package/source/class/qx/test/io/jsonrpc/Protocol.js +1 -5
  44. package/source/class/qx/test/io/request/Xhr.js +16 -0
  45. package/source/class/qx/test/lang/Type.js +151 -0
  46. package/source/class/qx/test/ui/embed/Iframe.js +1 -1
  47. package/source/class/qx/test/ui/form/TextArea.js +4 -0
  48. package/source/class/qx/test/util/DeferredCall.js +6 -0
  49. package/source/class/qx/test/util/Function.js +2 -2
  50. package/source/class/qx/theme/indigo/ColorDark.js +1 -1
  51. package/source/class/qx/tool/cli/api/Test.js +22 -0
  52. package/source/class/qx/tool/cli/commands/Compile.js +17 -4
  53. package/source/class/qx/tool/cli/commands/Test.js +7 -1
  54. package/source/class/qx/tool/compiler/Analyser.js +7 -0
  55. package/source/class/qx/tool/compiler/ClassFile.js +3 -2
  56. package/source/class/qx/tool/compiler/MetaExtraction.js +0 -5
  57. package/source/class/qx/tool/compiler/targets/meta/Browserify.js +8 -2
  58. package/source/class/qx/ui/basic/Image.js +72 -8
  59. package/source/class/qx/ui/basic/Label.js +4 -6
  60. package/source/class/qx/ui/control/DateChooser.js +4 -6
  61. package/source/class/qx/ui/core/Blocker.js +4 -6
  62. package/source/class/qx/ui/core/LayoutItem.js +4 -6
  63. package/source/class/qx/ui/core/MPlacement.js +23 -1
  64. package/source/class/qx/ui/core/Widget.js +7 -5
  65. package/source/class/qx/ui/form/AbstractField.js +4 -6
  66. package/source/class/qx/ui/form/MForm.js +4 -6
  67. package/source/class/qx/ui/form/Spinner.js +4 -6
  68. package/source/class/qx/ui/form/renderer/AbstractRenderer.js +4 -6
  69. package/source/class/qx/ui/menu/AbstractButton.js +7 -11
  70. package/source/class/qx/ui/mobile/basic/Label.js +4 -6
  71. package/source/class/qx/ui/mobile/form/Label.js +4 -6
  72. package/source/class/qx/ui/mobile/list/List.js +4 -6
  73. package/source/class/qx/ui/progressive/renderer/table/Row.js +35 -7
  74. package/source/class/qx/ui/progressive/renderer/table/cell/Boolean.js +4 -6
  75. package/source/class/qx/ui/table/Table.js +4 -6
  76. package/source/class/qx/ui/table/cellrenderer/Abstract.js +4 -6
  77. package/source/class/qx/ui/table/cellrenderer/Boolean.js +4 -6
  78. package/source/class/qx/ui/table/pane/Scroller.js +1 -1
  79. package/source/class/qx/ui/table/rowrenderer/Default.js +4 -6
  80. package/source/class/qx/util/ConcurrencyLimiter.js +78 -0
  81. package/source/resource/qx/tool/schema/compile-1-0-0.json +13 -0
  82. package/source/resource/qx/tool/website/build/404.html +3 -25
  83. package/source/resource/qx/tool/website/build/about.html +3 -25
  84. package/source/resource/qx/tool/website/build/assets/common.js +20 -0
  85. package/source/resource/qx/tool/website/build/diagnostics/dependson.html +3 -25
  86. package/source/resource/qx/tool/website/build/diagnostics/requiredby.html +3 -22
  87. package/source/resource/qx/tool/website/build/index.html +3 -25
  88. package/source/resource/qx/tool/website/partials/footer.html +3 -21
  89. package/source/resource/qx/tool/website/partials/head.html +0 -1
  90. package/lib/resource/qx/tool/website/build/assets/bluebird.min.js +0 -4615
  91. package/lib/resource/qx/tool/website/src/assets/bluebird.min.js +0 -4615
  92. package/source/resource/qx/tool/website/build/assets/bluebird.min.js +0 -4615
  93. package/source/resource/qx/tool/website/src/assets/bluebird.min.js +0 -4615
@@ -39,6 +39,9 @@ qx.Bootstrap.define("qx.bom.Label", {
39
39
  letterSpacing: 1
40
40
  },
41
41
 
42
+ /** @type{Object<String,Object>} cache of label sizes */
43
+ __sizeCache: {},
44
+
42
45
  /**
43
46
  * Generates the helper DOM element for text measuring
44
47
  *
@@ -286,6 +289,12 @@ qx.Bootstrap.define("qx.bom.Label", {
286
289
  * @return {Map} A map with preferred <code>width</code> and <code>height</code>.
287
290
  */
288
291
  getHtmlSize(content, styles, width) {
292
+ let cacheKey = this.__getCacheKey(styles, width);
293
+ let size = this.__getCachedSize(cacheKey, content);
294
+ if (size !== undefined) {
295
+ return size;
296
+ }
297
+
289
298
  var element = this._htmlElement || this.__prepareHtml();
290
299
 
291
300
  // apply width
@@ -293,7 +302,9 @@ qx.Bootstrap.define("qx.bom.Label", {
293
302
  // insert content
294
303
  element.innerHTML = content;
295
304
 
296
- return this.__measureSize(element, styles);
305
+ size = this.__measureSize(element, styles);
306
+ this.__storeCacheSize(cacheKey, content, size);
307
+ return size;
297
308
  },
298
309
 
299
310
  /**
@@ -304,6 +315,12 @@ qx.Bootstrap.define("qx.bom.Label", {
304
315
  * @return {Map} A map with preferred <code>width</code> and <code>height</code>.
305
316
  */
306
317
  getTextSize(text, styles) {
318
+ let cacheKey = this.__getCacheKey(styles);
319
+ let size = this.__getCachedSize(cacheKey, text);
320
+ if (size !== undefined) {
321
+ return size;
322
+ }
323
+
307
324
  var element = this._textElement || this.__prepareText();
308
325
 
309
326
  if (
@@ -315,7 +332,70 @@ qx.Bootstrap.define("qx.bom.Label", {
315
332
  qx.bom.element.Attribute.set(element, "text", text);
316
333
  }
317
334
 
318
- return this.__measureSize(element, styles);
335
+ size = this.__measureSize(element, styles);
336
+ this.__storeCacheSize(cacheKey, text, size);
337
+ return size;
338
+ },
339
+
340
+ /**
341
+ * Returns a key for a specific set of styles, used in the caching of size calculations
342
+ *
343
+ * @param {*} styles
344
+ * @param {Integer?} width optional width
345
+ * @returns
346
+ */
347
+ __getCacheKey(styles, width) {
348
+ let cacheKey = [];
349
+ for (let key in styles) {
350
+ let value = styles[key];
351
+ if (value !== null) {
352
+ cacheKey.push(key + ":" + value);
353
+ }
354
+ }
355
+ if (width !== undefined) {
356
+ cacheKey.push("width:" + width);
357
+ }
358
+ return cacheKey.join(",");
359
+ },
360
+
361
+ /**
362
+ * Returns the cached size of the given text
363
+ *
364
+ * @param {String} cacheKey
365
+ * @param {String} text
366
+ * @returns {*} size object
367
+ */
368
+ __getCachedSize(cacheKey, text) {
369
+ let cache = qx.bom.Label.__sizeCache[cacheKey];
370
+ return cache?.sizes[text];
371
+ },
372
+
373
+ /**
374
+ * Stores the size of the given text in the cache
375
+ *
376
+ * @param {String} cacheKey
377
+ * @param {String} text
378
+ * @param {*} size
379
+ */
380
+ __storeCacheSize(cacheKey, text, size) {
381
+ if (!size) {
382
+ return;
383
+ }
384
+
385
+ let cache = qx.bom.Label.__sizeCache[cacheKey];
386
+ if (cache === undefined) {
387
+ cache = qx.bom.Label.__sizeCache[cacheKey] = {
388
+ sizes: {}
389
+ };
390
+ }
391
+ cache.sizes[text] = size;
392
+ },
393
+
394
+ /**
395
+ * Flushes the size cache - use this when something changes, for example webfonts have been loaded
396
+ */
397
+ flushSizeCache() {
398
+ qx.bom.Label.__sizeCache = {};
319
399
  },
320
400
 
321
401
  /**
@@ -112,6 +112,7 @@ qx.Class.define("qx.bom.webfonts.WebFont", {
112
112
  },
113
113
 
114
114
  __applyValid(value) {
115
+ qx.bom.Label.flushSizeCache();
115
116
  this.fireDataEvent("changeStatus", {
116
117
  family: this.getFamily(),
117
118
  valid: value
@@ -891,6 +891,7 @@ qx.Bootstrap.define("qx.core.Environment", {
891
891
  "qx.dynlocale": true,
892
892
  "qx.dyntheme": true,
893
893
  "qx.blankpage": "qx/static/blank.html",
894
+ "qx.data.marshal.Json.breakOnNonPojos": false,
894
895
  "qx.debug.databinding": false,
895
896
  "qx.debug.dispose": false,
896
897
  "qx.debug.startupTimings": false,
@@ -913,7 +914,8 @@ qx.Bootstrap.define("qx.core.Environment", {
913
914
  "qx.promise.warnings": true,
914
915
  "qx.promise.longStackTraces": true,
915
916
  "qx.command.bindEnabled": false,
916
- "qx.headless": false
917
+ "qx.headless": false,
918
+ "qx.environment.allowRuntimeMutations": false
917
919
  },
918
920
 
919
921
  /**
@@ -1210,6 +1212,74 @@ qx.Bootstrap.define("qx.core.Environment", {
1210
1212
  return this._asyncChecks;
1211
1213
  },
1212
1214
 
1215
+ // mutation of environment settings at runtime
1216
+
1217
+ /**
1218
+ * Sets the environment setting for the given key to the given value. This deletes any check function
1219
+ * associated with this key.
1220
+ *
1221
+ * This method is only available if "qx.environment.allowRuntimeMutations" is set to true.
1222
+ *
1223
+ * @param key {String} The key of the environment setting to set.
1224
+ * @param value {var} The value to set the environment setting to. Must be a scalar value.
1225
+ */
1226
+ set: qx.Bootstrap.getEnvironmentSetting("qx.environment.allowRuntimeMutations") ? function(key, value) {
1227
+ if (key === undefined) {
1228
+ throw new TypeError("Key must be provided to set an environment setting.");
1229
+ }
1230
+ if (this.__environmentBackup === undefined) {
1231
+ qx.Bootstrap.warn("Modifying environment settings at runtime is enabled. This is a security risk and should not be done in production code.");
1232
+ this.__environmentBackup = Object.assign({}, this.__cache);
1233
+ this.__checksBackup = Object.assign({}, this._checks);
1234
+ }
1235
+ this.__cache[key] = value;
1236
+ delete this._checks[key];
1237
+ delete this._checksMap[key];
1238
+ }: undefined,
1239
+
1240
+ /**
1241
+ * Removes the environment setting for the given key, and any check function associated with it.
1242
+ * This method is only available if "qx.environment.allowRuntimeMutations" is set to true.
1243
+ *
1244
+ * @param key {String} The key of the environment setting to remove.
1245
+ */
1246
+ remove: qx.Bootstrap.getEnvironmentSetting("qx.environment.allowRuntimeMutations") ? function(key) {
1247
+ if (key === undefined) {
1248
+ throw new TypeError("Key must be provided to remove an environment setting.");
1249
+ }
1250
+ if (this.__environmentBackup === undefined || this.__environmentBackup[key] === undefined) {
1251
+ throw new TypeError(`Environment setting "${key}" does not exist.`);
1252
+ }
1253
+ delete this.__cache[key];
1254
+ delete this._checks[key];
1255
+ delete this._checksMap[key];
1256
+ }: undefined,
1257
+
1258
+ /**
1259
+ * Resets the environment settings to their original values. If a key is provided, only that key is reset.
1260
+ * This method is only available if "qx.environment.allowRuntimeMutations" is set to true.
1261
+ *
1262
+ * @param key {String?} The key of the environment setting to reset. If not provided, all settings are reset.
1263
+ */
1264
+ reset: qx.Bootstrap.getEnvironmentSetting("qx.environment.allowRuntimeMutations") ? function(key) {
1265
+ if (this.__environmentBackup === undefined) {
1266
+ // no backup available, nothing to reset
1267
+ return;
1268
+ }
1269
+ if (key !== undefined && this.__environmentBackup[key] === undefined) {
1270
+ throw new TypeError(`Environment setting "${key}" does not exist.`);
1271
+ }
1272
+ if (key === undefined) {
1273
+ // reset all keys
1274
+ this.__cache = Object.assign({}, this.__environmentBackup);
1275
+ this._checks = Object.assign({}, this.__checksBackup);
1276
+ return;
1277
+ }
1278
+ // only reset the particular key
1279
+ this.__cache[key] = this.__environmentBackup[key];
1280
+ this._checks[key] = this.__checksBackup[key];
1281
+ }: undefined,
1282
+
1213
1283
  /**
1214
1284
  * Initializer for the default values of the framework settings.
1215
1285
  */
@@ -1296,5 +1366,17 @@ qx.Bootstrap.define("qx.core.Environment", {
1296
1366
  if (statics.get("qx.allowUrlSettings") === true) {
1297
1367
  statics.__importFromUrl();
1298
1368
  }
1369
+ },
1370
+
1371
+ environment: {
1372
+ /**
1373
+ * By setting this key to true, the environment variables can be mutated at runtime, using the {@link #set} method,
1374
+ * which is otherwise not available. This is only useful for testing and debugging purposes. We strongly advise
1375
+ * against enabling this in production code. You have been warned.
1376
+ * Note: This enviroment key declaration is only for documentation purposes and is not used during bootstrapping, so
1377
+ * changing the value here will have no effect.
1378
+ */
1379
+ "qx.environment.allowRuntimeMutations": false,
1299
1380
  }
1381
+
1300
1382
  });
@@ -207,14 +207,10 @@ qx.Class.define("qx.data.controller.List", {
207
207
  }
208
208
  },
209
209
 
210
- /*
211
- *****************************************************************************
212
- MEMBERS
213
- *****************************************************************************
214
- */
215
-
216
210
  members: {
217
- // private members
211
+ /**
212
+ * Listener ID for the 'change' event of property `model`
213
+ */
218
214
  __changeModelListenerId: null,
219
215
  __lookupTable: null,
220
216
  __onUpdate: null,
@@ -376,12 +372,16 @@ qx.Class.define("qx.data.controller.List", {
376
372
  }
377
373
  }
378
374
 
379
- // erase the selection if there is something selected
380
- if (this.getSelection() != undefined && this.getSelection().length > 0) {
381
- this.getSelection().splice(0, this.getSelection().length).dispose();
375
+ if (!this.getAllowSelectionNotInModel()) {
376
+ //erase the selection if there is something selected
377
+ if (
378
+ this.getSelection() != undefined &&
379
+ this.getSelection().length > 0
380
+ ) {
381
+ this.getSelection().splice(0, this.getSelection().length).dispose();
382
+ }
382
383
  }
383
384
 
384
- // if a model is set
385
385
  if (value != null) {
386
386
  // add a new listener
387
387
  this.__changeModelListenerId = value.addListener(
@@ -392,28 +392,40 @@ qx.Class.define("qx.data.controller.List", {
392
392
 
393
393
  // renew the index lookup table
394
394
  this.__buildUpLookupTable();
395
+
395
396
  // check for the new length
396
397
  this.__changeModelLength();
397
398
 
398
- // as we only change the labels of the items, the selection change event
399
- // may be missing so we invoke it here
400
- if (old == null) {
401
- this._changeTargetSelection();
399
+ if (!this.getAllowSelectionNotInModel()) {
400
+ // as we only change the labels of the items, the selection change event
401
+ // may be missing so we invoke it here
402
+ if (old == null) {
403
+ this._changeTargetSelection();
404
+ } else {
405
+ // update the selection asynchronously
406
+ this.__syncTargetSelection = true;
407
+ qx.ui.core.queue.Widget.add(this);
408
+ }
402
409
  } else {
403
- // update the selection asynchronously
404
- this.__syncTargetSelection = true;
410
+ this.__syncModelSelection = true;
405
411
  qx.ui.core.queue.Widget.add(this);
406
412
  }
407
413
  } else {
408
414
  var target = this.getTarget();
409
415
  // if the model is set to null, we should remove all items in the target
410
416
  if (target != null) {
417
+ if (this.getAllowSelectionNotInModel()) {
418
+ this._startSelectionModification();
419
+ }
411
420
  // we need to remove the bindings too so use the controller method
412
421
  // for removing items
413
422
  var length = target.getChildren().length;
414
423
  for (var i = 0; i < length; i++) {
415
424
  this.__removeItem();
416
425
  }
426
+ if (this.getAllowSelectionNotInModel()) {
427
+ this._endSelectionModification();
428
+ }
417
429
  }
418
430
  }
419
431
  },
@@ -431,6 +443,10 @@ qx.Class.define("qx.data.controller.List", {
431
443
  // add a listener for the target change
432
444
  this._addChangeTargetListener(value, old);
433
445
 
446
+ if (this.getAllowSelectionNotInModel()) {
447
+ this._startSelectionModification();
448
+ }
449
+
434
450
  // if there was an old target
435
451
  if (old != undefined) {
436
452
  // remove all element of the old target
@@ -450,6 +466,10 @@ qx.Class.define("qx.data.controller.List", {
450
466
  }
451
467
  }
452
468
  }
469
+
470
+ if (this.getAllowSelectionNotInModel()) {
471
+ this._endSelectionModification();
472
+ }
453
473
  },
454
474
 
455
475
  /*
@@ -518,6 +538,9 @@ qx.Class.define("qx.data.controller.List", {
518
538
  var newLength = this.__lookupTable.length;
519
539
  var currentLength = this.getTarget().getChildren().length;
520
540
 
541
+ if (this.getAllowSelectionNotInModel()) {
542
+ this._startSelectionModification();
543
+ }
521
544
  // if there are more item
522
545
  if (newLength > currentLength) {
523
546
  // add the new elements
@@ -532,13 +555,19 @@ qx.Class.define("qx.data.controller.List", {
532
555
  }
533
556
  }
534
557
 
558
+ if (this.getAllowSelectionNotInModel()) {
559
+ this._endSelectionModification();
560
+ }
561
+
535
562
  // build up the look up table
536
563
  this.__buildUpLookupTable();
537
564
 
538
- // sync the target selection in case someone deleted a item in
539
- // selection mode "one" [BUG #4839]
540
- this.__syncTargetSelection = true;
541
- qx.ui.core.queue.Widget.add(this);
565
+ if (!this.getAllowSelectionNotInModel()) {
566
+ // sync the target selection in case someone deleted a item in
567
+ // selection mode "one" [BUG #4839]
568
+ this.__syncTargetSelection = true;
569
+ qx.ui.core.queue.Widget.add(this);
570
+ }
542
571
  },
543
572
 
544
573
  /**
@@ -61,6 +61,24 @@ qx.Mixin.define("qx.data.controller.MSelection", {
61
61
  event: "changeSelection",
62
62
  apply: "_applySelection",
63
63
  init: null
64
+ },
65
+
66
+ /**
67
+ * If set to true, the selection property will be allowed to have items which are not in the model property.
68
+ * If the model property changes, this will not cause the selection property to be reset.
69
+ *
70
+ * If the selection property contains items which are not in the model property,
71
+ * the selection of the target widget (e.g. qx.ui.form.SelectBox) will only be the items that are both in this controller's model property
72
+ * and in this controller's selection property.
73
+ *
74
+ * This is useful when it is undesirable to reset the selection when the model changes,
75
+ * because if the selection is bound to a business object,
76
+ * data in the business object may be overwritten.
77
+ */
78
+ allowSelectionNotInModel: {
79
+ check: "Boolean",
80
+ init: false,
81
+ nullable: false
64
82
  }
65
83
  },
66
84
 
@@ -125,6 +143,16 @@ qx.Mixin.define("qx.data.controller.MSelection", {
125
143
  this.resetSelection();
126
144
  },
127
145
 
146
+ /**
147
+ * Removes any items from the selection property that are not in the model property.
148
+ */
149
+ syncSelectionWithModel() {
150
+ //We will create a copy of my selection array and work on that to ensure there is only one change event being fired.
151
+ let mySelection = this.getSelection().copy();
152
+ mySelection = mySelection.filter(item => this.getModel().includes(item));
153
+ this.getSelection().replace(mySelection);
154
+ },
155
+
128
156
  /*
129
157
  ---------------------------------------------------------------------------
130
158
  APPLY METHODS
@@ -226,6 +254,8 @@ qx.Mixin.define("qx.data.controller.MSelection", {
226
254
  /**
227
255
  * Helper method which should be called by the classes including this
228
256
  * Mixin when the target changes.
257
+ * Removes the 'changeSelection' listener from the old target
258
+ * and add a new one to the new target;
229
259
  *
230
260
  * @param value {qx.ui.core.Widget|null} The new target.
231
261
  * @param old {qx.ui.core.Widget|null} The old target.
@@ -268,9 +298,9 @@ qx.Mixin.define("qx.data.controller.MSelection", {
268
298
  // if its a multi selection target
269
299
  if (this.__targetSupportsMultiSelection()) {
270
300
  var targetSelection = [];
271
- // go through the selection array
301
+ // go through my selection array
272
302
  for (var i = 0; i < this.getSelection().length; i++) {
273
- // store each item
303
+ // store each selectable item from target widget
274
304
  var model = this.getSelection().getItem(i);
275
305
  var selectable = this.__getSelectableForModel(model);
276
306
  if (selectable != null) {
@@ -281,21 +311,24 @@ qx.Mixin.define("qx.data.controller.MSelection", {
281
311
 
282
312
  // get the selection of the target
283
313
  targetSelection = this.getTarget().getSelection();
284
- // get all items selected in the list
285
- var targetSelectionItems = [];
314
+ // get all models of items selected in the target
315
+ var targetSelectionModels = [];
286
316
  for (var i = 0; i < targetSelection.length; i++) {
287
- targetSelectionItems[i] = targetSelection[i].getModel();
317
+ targetSelectionModels[i] = targetSelection[i].getModel();
288
318
  }
289
319
 
290
- // go through the controller selection
291
- for (var i = this.getSelection().length - 1; i >= 0; i--) {
292
- // if the item in the controller selection is not selected in the list
293
- if (!targetSelectionItems.includes(this.getSelection().getItem(i))) {
294
- // remove the current element and get rid of the return array
295
- this.getSelection().splice(i, 1).dispose();
320
+ if (!this.getAllowSelectionNotInModel()) {
321
+ // go through the controller selection
322
+ for (var i = this.getSelection().length - 1; i >= 0; i--) {
323
+ // if the item in the controller selection is not selected in the list
324
+ if (
325
+ !targetSelectionModels.includes(this.getSelection().getItem(i))
326
+ ) {
327
+ // remove the current element and get rid of the return array
328
+ this.getSelection().splice(i, 1).dispose();
329
+ }
296
330
  }
297
331
  }
298
-
299
332
  // if its a single selection target
300
333
  } else if (this.__targetSupportsSingleSelection()) {
301
334
  // get the model which should be selected
@@ -37,6 +37,10 @@ qx.Class.define("qx.data.marshal.Json", {
37
37
  },
38
38
 
39
39
  statics: {
40
+ /**
41
+ * Set to true when a warning has been shown that we are using the deprecated behaviour of trying to marshall non-POJO objects into Qooxdoo objects.
42
+ */
43
+ __shownNotBreakingOnNonPojosWarning: false,
40
44
  $$instance: null,
41
45
 
42
46
  /**
@@ -76,6 +80,25 @@ qx.Class.define("qx.data.marshal.Json", {
76
80
  Object.keys(data).sort().join('"') +
77
81
  (includeBubbleEvents === true ? "♥" : "")
78
82
  );
83
+ },
84
+
85
+ /**
86
+ * If the environment setting "qx.data.marshal.Json.breakOnNonPojos" is not set,
87
+ * it means we are using the old behaviour of marshalling non-POJO objects.
88
+ * A warning is shown if it hasn't been shown already.
89
+ */
90
+ checkIfWarnAboutNotBreakingOnNonPojos() {
91
+ if (
92
+ !qx.core.Environment.get("qx.data.marshal.Json.breakOnNonPojos") &&
93
+ !this.__shownNotBreakingOnNonPojosWarning
94
+ ) {
95
+ console.warn(
96
+ `Using deprecated behaviour of not breaking on non-POJOs when marshalling.
97
+ Please set the environment setting "qx.data.marshal.Json.breakOnNonPojos" to enable the new behaviour.
98
+ The old behaviour will be removed in the next major release of Qooxdoo.`
99
+ );
100
+ this.__shownNotBreakingOnNonPojosWarning = true;
101
+ }
79
102
  }
80
103
  },
81
104
 
@@ -170,12 +193,26 @@ qx.Class.define("qx.data.marshal.Json", {
170
193
  * @param depth {Number} The depth of the data relative to the data's root.
171
194
  */
172
195
  __toClass(data, includeBubbleEvents, parentProperty, depth) {
196
+ const breakOnNonPojos = qx.core.Environment.get(
197
+ "qx.data.marshal.Json.breakOnNonPojos"
198
+ );
199
+ this.constructor.checkIfWarnAboutNotBreakingOnNonPojos();
200
+
173
201
  // break on all primitive json types and qooxdoo objects
174
- if (
175
- !qx.lang.Type.isObject(data) ||
176
- !!data.$$isString || // check for localized strings
177
- data instanceof qx.core.Object
178
- ) {
202
+ let shouldBreak;
203
+ if (breakOnNonPojos) {
204
+ shouldBreak =
205
+ !qx.lang.Type.isPojo(data) ||
206
+ !!data.$$isString || // check for localized strings
207
+ data instanceof qx.core.Object;
208
+ } else {
209
+ shouldBreak =
210
+ !qx.lang.Type.isObject(data) ||
211
+ !!data.$$isString || // check for localized strings
212
+ data instanceof qx.core.Object;
213
+ }
214
+
215
+ if (shouldBreak) {
179
216
  // check for arrays
180
217
  if (data instanceof Array || qx.Bootstrap.getClass(data) == "Array") {
181
218
  for (var i = 0; i < data.length; i++) {
@@ -405,15 +442,31 @@ qx.Class.define("qx.data.marshal.Json", {
405
442
  * @return {qx.core.Object} The created model object.
406
443
  */
407
444
  __toModel(data, includeBubbleEvents, parentProperty, depth) {
445
+ const breakOnNonPojos = qx.core.Environment.get(
446
+ "qx.data.marshal.Json.breakOnNonPojos"
447
+ );
448
+ this.constructor.checkIfWarnAboutNotBreakingOnNonPojos();
449
+
408
450
  var isObject = qx.lang.Type.isObject(data);
451
+ var isPojo = qx.lang.Type.isPojo(data);
452
+
409
453
  var isArray =
410
454
  data instanceof Array || qx.Bootstrap.getClass(data) == "Array";
411
455
 
412
- if (
413
- (!isObject && !isArray) ||
414
- !!data.$$isString || // check for localized strings
415
- data instanceof qx.core.Object
416
- ) {
456
+ let shouldBreak;
457
+ if (breakOnNonPojos) {
458
+ shouldBreak =
459
+ (!isPojo && !isArray) ||
460
+ !!data.$$isString || // check for localized strings
461
+ data instanceof qx.core.Object;
462
+ } else {
463
+ shouldBreak =
464
+ (!isObject && !isArray) ||
465
+ !!data.$$isString || // check for localized strings
466
+ data instanceof qx.core.Object;
467
+ }
468
+
469
+ if (shouldBreak) {
417
470
  return data;
418
471
 
419
472
  // ignore rules
@@ -451,7 +504,7 @@ qx.Class.define("qx.data.marshal.Json", {
451
504
  );
452
505
  }
453
506
  return array;
454
- } else if (isObject) {
507
+ } else if (breakOnNonPojos ? isPojo : isObject) {
455
508
  // create an instance for the object
456
509
  var hash = this.__jsonToBestHash(data, includeBubbleEvents);
457
510
  var model = this.__createInstance(hash, data, parentProperty, depth);
@@ -85,5 +85,13 @@ qx.Class.define("qx.dev.unit.AsyncWrapper", {
85
85
  nullable: false,
86
86
  init: 10000
87
87
  }
88
+ },
89
+
90
+ environment: {
91
+ /**
92
+ * The factor by which any delay is multiplied or false or zero if no such
93
+ * factor should be applied.
94
+ */
95
+ "qx.test.delay.scale": false
88
96
  }
89
97
  });