fit-ui 2.8.2 → 2.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 (107) hide show
  1. package/dist/Controls/Input/maximize-highres.png +0 -0
  2. package/dist/Controls/Input/maximize.png +0 -0
  3. package/dist/Documentation.html +3 -3
  4. package/dist/Fit.UI.css +71 -6
  5. package/dist/Fit.UI.js +1017 -114
  6. package/dist/Fit.UI.min.css +2 -2
  7. package/dist/Fit.UI.min.js +1 -1
  8. package/dist/Resources/CKEditor/plugins/custombuttons/plugin.js +34 -0
  9. package/dist/Resources/CKEditor-with-maximize-button/CHANGES.md +2148 -0
  10. package/dist/Resources/CKEditor-with-maximize-button/LICENSE.md +1436 -0
  11. package/dist/Resources/CKEditor-with-maximize-button/README-FitUiChanges.txt +8 -0
  12. package/dist/Resources/CKEditor-with-maximize-button/README.md +39 -0
  13. package/dist/Resources/CKEditor-with-maximize-button/adapters/jquery.js +10 -0
  14. package/dist/Resources/CKEditor-with-maximize-button/build-config.js +79 -0
  15. package/dist/Resources/CKEditor-with-maximize-button/ckeditor.js +987 -0
  16. package/dist/Resources/CKEditor-with-maximize-button/config.js +34 -0
  17. package/dist/Resources/CKEditor-with-maximize-button/contents.css +208 -0
  18. package/dist/Resources/CKEditor-with-maximize-button/lang/da.js +5 -0
  19. package/dist/Resources/CKEditor-with-maximize-button/lang/de.js +5 -0
  20. package/dist/Resources/CKEditor-with-maximize-button/lang/en.js +5 -0
  21. package/dist/Resources/CKEditor-with-maximize-button/plugins/autocomplete/skins/default.css +38 -0
  22. package/dist/Resources/CKEditor-with-maximize-button/plugins/base64image/LICENSE.md +1244 -0
  23. package/dist/Resources/CKEditor-with-maximize-button/plugins/base64image/README.md +21 -0
  24. package/dist/Resources/CKEditor-with-maximize-button/plugins/base64image/dialogs/base64image.js +766 -0
  25. package/dist/Resources/CKEditor-with-maximize-button/plugins/base64image/dialogs/base64image.original.js +503 -0
  26. package/dist/Resources/CKEditor-with-maximize-button/plugins/base64image/icons/base64image.png +0 -0
  27. package/dist/Resources/CKEditor-with-maximize-button/plugins/base64image/icons/hidpi/base64image.png +0 -0
  28. package/dist/Resources/CKEditor-with-maximize-button/plugins/base64image/lang/da.js +12 -0
  29. package/dist/Resources/CKEditor-with-maximize-button/plugins/base64image/lang/de.js +12 -0
  30. package/dist/Resources/CKEditor-with-maximize-button/plugins/base64image/lang/en.js +12 -0
  31. package/dist/Resources/CKEditor-with-maximize-button/plugins/base64image/plugin.js +58 -0
  32. package/dist/Resources/CKEditor-with-maximize-button/plugins/base64imagepaste/plugin.js +91 -0
  33. package/dist/Resources/CKEditor-with-maximize-button/plugins/clipboard/dialogs/paste.js +11 -0
  34. package/dist/Resources/CKEditor-with-maximize-button/plugins/custombuttons/icons/custombuttons.png +0 -0
  35. package/dist/Resources/CKEditor-with-maximize-button/plugins/custombuttons/plugin.js +99 -0
  36. package/dist/Resources/CKEditor-with-maximize-button/plugins/dialog/dialogDefinition.js +4 -0
  37. package/dist/Resources/CKEditor-with-maximize-button/plugins/dialog/styles/dialog.css +18 -0
  38. package/dist/Resources/CKEditor-with-maximize-button/plugins/dragresize/LICENSE +19 -0
  39. package/dist/Resources/CKEditor-with-maximize-button/plugins/dragresize/_source.css +85 -0
  40. package/dist/Resources/CKEditor-with-maximize-button/plugins/dragresize/package.json +19 -0
  41. package/dist/Resources/CKEditor-with-maximize-button/plugins/dragresize/plugin.js +395 -0
  42. package/dist/Resources/CKEditor-with-maximize-button/plugins/dragresize/readme.md +35 -0
  43. package/dist/Resources/CKEditor-with-maximize-button/plugins/emoji/assets/iconsall.png +0 -0
  44. package/dist/Resources/CKEditor-with-maximize-button/plugins/emoji/assets/iconsall.svg +58 -0
  45. package/dist/Resources/CKEditor-with-maximize-button/plugins/emoji/emoji.json +1 -0
  46. package/dist/Resources/CKEditor-with-maximize-button/plugins/emoji/skins/default.css +237 -0
  47. package/dist/Resources/CKEditor-with-maximize-button/plugins/icons.png +0 -0
  48. package/dist/Resources/CKEditor-with-maximize-button/plugins/icons_hidpi.png +0 -0
  49. package/dist/Resources/CKEditor-with-maximize-button/plugins/link/dialogs/anchor.js +8 -0
  50. package/dist/Resources/CKEditor-with-maximize-button/plugins/link/dialogs/link.js +30 -0
  51. package/dist/Resources/CKEditor-with-maximize-button/plugins/link/images/anchor.png +0 -0
  52. package/dist/Resources/CKEditor-with-maximize-button/plugins/link/images/hidpi/anchor.png +0 -0
  53. package/dist/Resources/CKEditor-with-maximize-button/plugins/pastefromword/filter/default.js +42 -0
  54. package/dist/Resources/CKEditor-with-maximize-button/plugins/pastetools/filter/common.js +24 -0
  55. package/dist/Resources/CKEditor-with-maximize-button/plugins/pastetools/filter/image.js +12 -0
  56. package/dist/Resources/CKEditor-with-maximize-button/skins/bootstrapck/dialog.css +1 -0
  57. package/dist/Resources/CKEditor-with-maximize-button/skins/bootstrapck/dialog_ie.css +1 -0
  58. package/dist/Resources/CKEditor-with-maximize-button/skins/bootstrapck/dialog_ie7.css +1 -0
  59. package/dist/Resources/CKEditor-with-maximize-button/skins/bootstrapck/dialog_ie8.css +1 -0
  60. package/dist/Resources/CKEditor-with-maximize-button/skins/bootstrapck/dialog_iequirks.css +1 -0
  61. package/dist/Resources/CKEditor-with-maximize-button/skins/bootstrapck/dialog_opera.css +1 -0
  62. package/dist/Resources/CKEditor-with-maximize-button/skins/bootstrapck/editor.css +1 -0
  63. package/dist/Resources/CKEditor-with-maximize-button/skins/bootstrapck/editor_gecko.css +1 -0
  64. package/dist/Resources/CKEditor-with-maximize-button/skins/bootstrapck/editor_ie.css +1 -0
  65. package/dist/Resources/CKEditor-with-maximize-button/skins/bootstrapck/editor_ie7.css +1 -0
  66. package/dist/Resources/CKEditor-with-maximize-button/skins/bootstrapck/editor_ie8.css +1 -0
  67. package/dist/Resources/CKEditor-with-maximize-button/skins/bootstrapck/editor_iequirks.css +1 -0
  68. package/dist/Resources/CKEditor-with-maximize-button/skins/bootstrapck/icons.png +0 -0
  69. package/dist/Resources/CKEditor-with-maximize-button/skins/bootstrapck/icons_hidpi.png +0 -0
  70. package/dist/Resources/CKEditor-with-maximize-button/skins/bootstrapck/images/arrow.png +0 -0
  71. package/dist/Resources/CKEditor-with-maximize-button/skins/bootstrapck/images/close.png +0 -0
  72. package/dist/Resources/CKEditor-with-maximize-button/skins/bootstrapck/images/hidpi/close.png +0 -0
  73. package/dist/Resources/CKEditor-with-maximize-button/skins/bootstrapck/images/hidpi/lock-open.png +0 -0
  74. package/dist/Resources/CKEditor-with-maximize-button/skins/bootstrapck/images/hidpi/lock.png +0 -0
  75. package/dist/Resources/CKEditor-with-maximize-button/skins/bootstrapck/images/hidpi/refresh.png +0 -0
  76. package/dist/Resources/CKEditor-with-maximize-button/skins/bootstrapck/images/lock-open.png +0 -0
  77. package/dist/Resources/CKEditor-with-maximize-button/skins/bootstrapck/images/lock.png +0 -0
  78. package/dist/Resources/CKEditor-with-maximize-button/skins/bootstrapck/images/refresh.png +0 -0
  79. package/dist/Resources/CKEditor-with-maximize-button/skins/bootstrapck/readme.md +35 -0
  80. package/dist/Resources/CKEditor-with-maximize-button/skins/bootstrapck/skin.js +10 -0
  81. package/dist/Resources/CKEditor-with-maximize-button/skins/moono-lisa/dialog.css +5 -0
  82. package/dist/Resources/CKEditor-with-maximize-button/skins/moono-lisa/dialog_ie.css +5 -0
  83. package/dist/Resources/CKEditor-with-maximize-button/skins/moono-lisa/dialog_ie8.css +5 -0
  84. package/dist/Resources/CKEditor-with-maximize-button/skins/moono-lisa/dialog_iequirks.css +5 -0
  85. package/dist/Resources/CKEditor-with-maximize-button/skins/moono-lisa/editor.css +5 -0
  86. package/dist/Resources/CKEditor-with-maximize-button/skins/moono-lisa/editor_gecko.css +5 -0
  87. package/dist/Resources/CKEditor-with-maximize-button/skins/moono-lisa/editor_ie.css +5 -0
  88. package/dist/Resources/CKEditor-with-maximize-button/skins/moono-lisa/editor_ie8.css +5 -0
  89. package/dist/Resources/CKEditor-with-maximize-button/skins/moono-lisa/editor_iequirks.css +5 -0
  90. package/dist/Resources/CKEditor-with-maximize-button/skins/moono-lisa/icons.png +0 -0
  91. package/dist/Resources/CKEditor-with-maximize-button/skins/moono-lisa/icons_hidpi.png +0 -0
  92. package/dist/Resources/CKEditor-with-maximize-button/skins/moono-lisa/images/arrow.png +0 -0
  93. package/dist/Resources/CKEditor-with-maximize-button/skins/moono-lisa/images/close.png +0 -0
  94. package/dist/Resources/CKEditor-with-maximize-button/skins/moono-lisa/images/hidpi/close.png +0 -0
  95. package/dist/Resources/CKEditor-with-maximize-button/skins/moono-lisa/images/hidpi/lock-open.png +0 -0
  96. package/dist/Resources/CKEditor-with-maximize-button/skins/moono-lisa/images/hidpi/lock.png +0 -0
  97. package/dist/Resources/CKEditor-with-maximize-button/skins/moono-lisa/images/hidpi/refresh.png +0 -0
  98. package/dist/Resources/CKEditor-with-maximize-button/skins/moono-lisa/images/lock-open.png +0 -0
  99. package/dist/Resources/CKEditor-with-maximize-button/skins/moono-lisa/images/lock.png +0 -0
  100. package/dist/Resources/CKEditor-with-maximize-button/skins/moono-lisa/images/refresh.png +0 -0
  101. package/dist/Resources/CKEditor-with-maximize-button/skins/moono-lisa/images/spinner.gif +0 -0
  102. package/dist/Resources/CKEditor-with-maximize-button/skins/moono-lisa/readme.md +46 -0
  103. package/dist/Resources/CKEditor-with-maximize-button/styles.js +137 -0
  104. package/dist/Resources/CKEditor-with-maximize-button/vendor/promise.js +13 -0
  105. package/package.json +1 -1
  106. package/types/index.d.ts +266 -113
  107. package/dist/Resources/ckeditor_4.17.2_6f06412961d8.zip +0 -0
package/dist/Fit.UI.js CHANGED
@@ -451,6 +451,29 @@ Fit.Core.IsEqual = function(jsObj1, jsObj2)
451
451
  return false;
452
452
  }
453
453
 
454
+ /// <container name="Fit.Core.MergeOverwriteBehaviour">
455
+ /// Merge behaviour
456
+ /// </container>
457
+ Fit.Core.MergeOverwriteBehaviour = // Enums must exist runtime
458
+ {
459
+ /// <member container="Fit.Core.MergeOverwriteBehaviour" name="Always" access="public" static="true" type="string" default="Always">
460
+ /// <description> Always overwrite property values from target object with property values from merge object (default behaviour) </description>
461
+ /// </member>
462
+ Always: "Always",
463
+
464
+ /// <member container="Fit.Core.MergeOverwriteBehaviour" name="SkipNullAndUndefined" access="public" static="true" type="string" default="SkipNullAndUndefined">
465
+ /// <description> Always overwrite property values from target object with property values from merge object, except values from merge object that are Null or Undefined </description>
466
+ /// </member>
467
+ SkipNullAndUndefined: "SkipNullAndUndefined",
468
+
469
+ /// <member container="Fit.Core.MergeOverwriteBehaviour" name="Never" access="public" static="true" type="string" default="Never">
470
+ /// <description>
471
+ /// Never overwrite property values from target object - only add missing property values from merge object
472
+ /// </description>
473
+ /// </member>
474
+ Never: "Never"
475
+ };
476
+
454
477
  /// <function container="Fit.Core" name="Merge" access="public" static="true" returns="$ObjectTypeA + $ObjectTypeB">
455
478
  /// <description>
456
479
  /// Deep merges two objects and returns the resulting object.
@@ -462,11 +485,13 @@ Fit.Core.IsEqual = function(jsObj1, jsObj2)
462
485
  /// </description>
463
486
  /// <param name="targetObject" type="$ObjectTypeA"> Target object </param>
464
487
  /// <param name="mergeObject" type="$ObjectTypeB"> Merge object </param>
488
+ /// <param name="mergeObjectOverwriteBehaviour" type="Fit.Core.MergeOverwriteBehaviour" default="undefined"> Overwrite behaviour for merge object </param>
465
489
  /// </function>
466
- Fit.Core.Merge = function(targetObject, mergeObject)
490
+ Fit.Core.Merge = function(targetObject, mergeObject, mergeObjectOverwriteBehaviour)
467
491
  {
468
492
  Fit.Validation.ExpectObject(targetObject);
469
493
  Fit.Validation.ExpectObject(mergeObject);
494
+ Fit.Validation.ExpectStringValue(mergeObjectOverwriteBehaviour, true);
470
495
 
471
496
  /* // Test data
472
497
  f1 = function() { alert("Hello"); };
@@ -524,10 +549,19 @@ Fit.Core.Merge = function(targetObject, mergeObject)
524
549
  {
525
550
  if (isObject(newObject[prop]) && isObject(mergeObject[prop]))
526
551
  {
527
- newObject[prop] = Fit.Core.Merge(newObject[prop], mergeObject[prop]);
552
+ newObject[prop] = Fit.Core.Merge(newObject[prop], mergeObject[prop], mergeObjectOverwriteBehaviour);
528
553
  }
529
554
  else
530
555
  {
556
+ if (mergeObjectOverwriteBehaviour === Fit.Core.MergeOverwriteBehaviour.SkipNullAndUndefined && (mergeObject[prop] === null || mergeObject[prop] === undefined))
557
+ {
558
+ return; // Skip - ignore values of Null and Undefined from merge object
559
+ }
560
+ else if (mergeObjectOverwriteBehaviour === Fit.Core.MergeOverwriteBehaviour.Never && prop in newObject)
561
+ {
562
+ return; // Skip - never update values from target object, only add missing values
563
+ }
564
+
531
565
  newObject[prop] = mergeObject[prop];
532
566
  }
533
567
  });
@@ -648,7 +682,7 @@ Fit._internal =
648
682
  {
649
683
  Core:
650
684
  {
651
- VersionInfo: { Major: 2, Minor: 8, Patch: 2 } // Do NOT modify format - version numbers are programmatically changed when releasing new versions - MUST be on a separate line!
685
+ VersionInfo: { Major: 2, Minor: 9, Patch: 0 } // Do NOT modify format - version numbers are programmatically changed when releasing new versions - MUST be on a separate line!
652
686
  }
653
687
  };
654
688
 
@@ -2875,7 +2909,7 @@ Fit.Browser.GetScreenDimensions = function(onlyAvailable)
2875
2909
  }
2876
2910
 
2877
2911
  /// <function container="Fit.Browser" name="IsMobile" access="public" static="true" returns="boolean">
2878
- /// <description> Returns value indicating whether devices currently being used is a mobile device or not </description>
2912
+ /// <description> Returns value indicating whether device is a mobile device or not </description>
2879
2913
  /// <param name="includeTablets" type="boolean" default="true"> Value indicating whether tablets are considered mobile devices or not </param>
2880
2914
  /// </function>
2881
2915
  Fit.Browser.IsMobile = function(includeTablets)
@@ -2893,6 +2927,14 @@ Fit.Browser.IsMobile = function(includeTablets)
2893
2927
  return (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(nav) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(nav.substr(0,4)));
2894
2928
  }
2895
2929
 
2930
+ /// <function container="Fit.Browser" name="IsTouchEnabled" access="public" static="true" returns="boolean">
2931
+ /// <description> Returns value indicating whether device is touch enabled or not </description>
2932
+ /// </function>
2933
+ Fit.Browser.IsTouchEnabled = function()
2934
+ {
2935
+ return ("ontouchstart" in window);
2936
+ }
2937
+
2896
2938
 
2897
2939
  /// <function container="Fit.Browser" name="Log" access="public" static="true">
2898
2940
  /// <description> Log message or object </description>
@@ -2934,6 +2976,7 @@ Fit.Browser.LogDeprecated = function(msg)
2934
2976
  /// <member name="IsMobile" type="boolean"> Boolean indicating whether this is a mobile device (tablet or phone) </member>
2935
2977
  /// <member name="IsPhone" type="boolean"> Boolean indicating whether this is a phone </member>
2936
2978
  /// <member name="IsTablet" type="boolean"> Boolean indicating whether this is a tablet device </member>
2979
+ /// <member name="IsTouchEnabled" type="boolean"> Boolean indicating whether this is a touch enabled device </member>
2937
2980
  /// </container>
2938
2981
 
2939
2982
  /// <container name="Fit.BrowserTypeDefs.BrowserInfo" extends="Fit.BrowserTypeDefs.BrowserDetails">
@@ -2980,6 +3023,7 @@ Fit.Browser.GetInfo = function(returnAppInfo)
2980
3023
  Fit._internal.Browser.Info.IsMobile = Fit.Browser.IsMobile();
2981
3024
  Fit._internal.Browser.Info.IsPhone = Fit.Browser.IsMobile(false); // Phone only
2982
3025
  Fit._internal.Browser.Info.IsTablet = (Fit.Browser.IsMobile() === true && Fit.Browser.IsMobile(false) === false); // Tablet only
3026
+ Fit._internal.Browser.Info.IsTouchEnabled = Fit.Browser.IsTouchEnabled();
2983
3027
  }
2984
3028
 
2985
3029
  return Fit.Core.Clone(Fit._internal.Browser.Info); // Clone to ensure values are not shared and potentially changed
@@ -5153,6 +5197,28 @@ Fit.String.DecodeHtml = function(str)
5153
5197
  return str.replace(/&quot;/g, "\"").replace(/&#39;/g, "'").replace(/&#039;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
5154
5198
  }
5155
5199
 
5200
+ /// <function container="Fit.String" name="ParseImageBlobUrls" access="public" static="true" returns="string[]">
5201
+ /// <description> Parse and return image blob URLs from image tags </description>
5202
+ /// <param name="str" type="string"> String to parse </param>
5203
+ /// </function>
5204
+ Fit.String.ParseImageBlobUrls = function(str)
5205
+ {
5206
+ var imageBlobUrls = [];
5207
+ var blobImages = str.match(/<img .*?src=(["'])blob:.+?\1.*?>/gi) || [];
5208
+
5209
+ Fit.Array.ForEach(blobImages, function(img)
5210
+ {
5211
+ var blobUrl = img.match(/src=(["'])(blob:.*?)\1/i)[2]; // 0 = Full match, 1 = double quote or ping character, 2 = blob URL
5212
+
5213
+ if (Fit.Array.Contains(imageBlobUrls, blobUrl) === false)
5214
+ {
5215
+ Fit.Array.Add(imageBlobUrls, blobUrl);
5216
+ }
5217
+ });
5218
+
5219
+ return imageBlobUrls;
5220
+ }
5221
+
5156
5222
  /// <function container="Fit.String" name="Hash" access="public" static="true" returns="integer">
5157
5223
  /// <description> Get Java compatible Hash Code from string </description>
5158
5224
  /// <param name="str" type="string"> String to get hash code from </param>
@@ -6707,15 +6773,17 @@ Fit.DragDrop.Draggable = function(domElm, domTriggerElm)
6707
6773
 
6708
6774
  // Private properties
6709
6775
 
6776
+ var me = this;
6710
6777
  var elm = domElm;
6711
6778
  var posState = null; // { position: "", left: "", top: "" };
6712
6779
  var trgElm = (domTriggerElm ? domTriggerElm : null);
6713
- var me = this;
6780
+ var bringToFrontOnActivation = false;
6714
6781
 
6715
6782
  var onDragStart = null;
6716
6783
  var onDragging = null;
6717
6784
  var onDragStop = null;
6718
6785
 
6786
+ var activationEventId = -1;
6719
6787
  var mouseDownEventId = -1;
6720
6788
 
6721
6789
  // Construct
@@ -6727,15 +6795,33 @@ Fit.DragDrop.Draggable = function(domElm, domTriggerElm)
6727
6795
  Fit.Dom.AddClass(elm, "FitDragDropDraggable");
6728
6796
  Fit.Dom.AddClass((trgElm !== null ? trgElm : elm), "FitDragDropDraggableHandle");
6729
6797
 
6730
- // Mouse down
6798
+ // Bring to front on activation
6731
6799
 
6732
- mouseDownEventId = Fit.Events.AddHandler(((trgElm !== null) ? trgElm : elm), "mousedown", function(e)
6800
+ activationEventId = Fit.Events.AddHandler(elm, (Fit.Browser.IsTouchEnabled() === true ? "touchstart" : "mousedown"), function(e)
6733
6801
  {
6734
- var ev = e || window.event;
6802
+ if (bringToFrontOnActivation === true)
6803
+ {
6804
+ me.BringToFront();
6805
+ }
6806
+ });
6735
6807
 
6808
+ // Mouse down
6809
+
6810
+ mouseDownEventId = Fit.Events.AddHandler(((trgElm !== null) ? trgElm : elm), (Fit.Browser.IsTouchEnabled() === true ? "touchstart" : "mousedown"), function(e)
6811
+ {
6736
6812
  if (Fit.DragDrop.Draggable._internal.active !== null)
6737
6813
  return; // Skip - current element is a draggable parent to which event propagated
6738
6814
 
6815
+ var ev = Fit.Events.GetEvent(e);
6816
+
6817
+ if (onDragStart)
6818
+ {
6819
+ if (onDragStart(elm) === false)
6820
+ {
6821
+ return;
6822
+ }
6823
+ }
6824
+
6739
6825
  Fit.Dom.AddClass(elm, "FitDragDropDragging");
6740
6826
 
6741
6827
  // Initial positioning (used by Reset())
@@ -6758,12 +6844,9 @@ Fit.DragDrop.Draggable = function(domElm, domTriggerElm)
6758
6844
  }
6759
6845
 
6760
6846
  // Mouse position in viewport
6761
- var mouseXviewport = (ev.clientX || e.pageX);
6762
- var mouseYviewport = (ev.clientY || e.pageY);
6763
-
6764
- // Make sure element being dragged is on top of every other draggable element
6765
- Fit.DragDrop.Draggable._internal.zIndex++;
6766
- elm.style.zIndex = Fit.DragDrop.Draggable._internal.zIndex;
6847
+ var pp = Fit.Events.GetPointerState().Coordinates.ViewPort;
6848
+ var mouseXviewport = pp.X;
6849
+ var mouseYviewport = pp.Y;
6767
6850
 
6768
6851
  // Create state information object for draggable currently being dragged
6769
6852
  var state =
@@ -6772,8 +6855,8 @@ Fit.DragDrop.Draggable = function(domElm, domTriggerElm)
6772
6855
  Positioning: "relative",
6773
6856
  Mouse: {Viewport: {X: -1, Y: -1}, Document: {X: -1, Y: -1}},
6774
6857
  Position: {Viewport: {X: -1, Y: -1}, Document: {X: -1, Y: -1}, Offset: {X: -1, Y: -1}},
6775
- Events: { OnDragStart: onDragStart, OnDragging: onDragging, OnDragStop: onDragStop },
6776
- OnSelectStart : document.onselectstart
6858
+ Events: { OnDragStart: onDragStart, OnDragging: onDragging, OnDragStop: onDragStop }/*,
6859
+ OnSelectStart : document.onselectstart*/
6777
6860
  };
6778
6861
 
6779
6862
  var positioning = Fit.Dom.GetComputedStyle(elm, "position");
@@ -6782,8 +6865,9 @@ Fit.DragDrop.Draggable = function(domElm, domTriggerElm)
6782
6865
  state.Positioning = positioning;
6783
6866
  }
6784
6867
 
6785
- // Disable text selection for legacy browsers
6786
- document.onselectstart = function() { return false; }
6868
+ // Disable text selection for legacy browsers.
6869
+ // DISABLED: Now handled using Fit.Events.PreventDefault(ev).
6870
+ //document.onselectstart = function() { return false; }
6787
6871
 
6788
6872
  // Find mouse position in viewport
6789
6873
  state.Mouse.Viewport.X = mouseXviewport;
@@ -6833,12 +6917,10 @@ Fit.DragDrop.Draggable = function(domElm, domTriggerElm)
6833
6917
  Fit.Array.Add(Fit.DragDrop.DropZone._internal.dropzones, draggableDropZone);
6834
6918
  }
6835
6919
 
6836
- if (state.Events.OnDragStart)
6837
- state.Events.OnDragStart(elm);
6838
-
6839
- /*if (ev.preventDefault)
6840
- ev.preventDefault();
6841
- ev.cancelBubble = true;*/
6920
+ if (ev.type === "touchstart")
6921
+ {
6922
+ Fit.Events.PreventDefault(ev); // Stop page scrolling on iOS (required in both ontouchstart and ontouchmove handlers)
6923
+ }
6842
6924
  });
6843
6925
 
6844
6926
  // Mouse Up
@@ -6847,13 +6929,11 @@ Fit.DragDrop.Draggable = function(domElm, domTriggerElm)
6847
6929
  {
6848
6930
  Fit.DragDrop.Draggable._internal.mouseUpRegistered = true;
6849
6931
 
6850
- Fit.Events.AddHandler(document, "mouseup", function(e)
6932
+ Fit.Events.AddHandler(document, (Fit.Browser.IsTouchEnabled() === true ? "touchend" : "mouseup"), function(e)
6851
6933
  {
6852
6934
  if (Fit.DragDrop.Draggable._internal.active === null)
6853
6935
  return;
6854
6936
 
6855
- var ev = e || window.event;
6856
-
6857
6937
  var draggableState = Fit.DragDrop.Draggable._internal.active;
6858
6938
  var draggable = Fit.DragDrop.Draggable._internal.active.Draggable;
6859
6939
 
@@ -6886,7 +6966,7 @@ Fit.DragDrop.Draggable = function(domElm, domTriggerElm)
6886
6966
  draggableState.Events.OnDragStop(draggable);
6887
6967
 
6888
6968
  // Restore OnSelectStart event
6889
- document.onselectstart = draggableState.OnSelectStart;
6969
+ //document.onselectstart = draggableState.OnSelectStart;
6890
6970
  });
6891
6971
  }
6892
6972
 
@@ -6896,12 +6976,12 @@ Fit.DragDrop.Draggable = function(domElm, domTriggerElm)
6896
6976
  {
6897
6977
  Fit.DragDrop.Draggable._internal.mouseMoveRegistered = true;
6898
6978
 
6899
- Fit.Events.AddHandler(document, "mousemove", function(e)
6979
+ Fit.Events.AddHandler(document, (Fit.Browser.IsTouchEnabled() === true ? "touchmove" : "mousemove"), { passive: false /* https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener */ }, function(e)
6900
6980
  {
6901
6981
  if (Fit.DragDrop.Draggable._internal.active === null)
6902
6982
  return;
6903
6983
 
6904
- var ev = e || window.event;
6984
+ var ev = Fit.Events.GetEvent(e);
6905
6985
 
6906
6986
  // Handle draggable
6907
6987
 
@@ -6910,8 +6990,16 @@ Fit.DragDrop.Draggable = function(domElm, domTriggerElm)
6910
6990
  var elm = draggable.GetElement();
6911
6991
 
6912
6992
  // Mouse position in viewport
6913
- var mouseXviewport = (ev.clientX || e.pageX);
6914
- var mouseYviewport = (ev.clientY || e.pageY);
6993
+ var pp = Fit.Events.GetPointerState().Coordinates.ViewPort;
6994
+ var mouseXviewport = pp.X;
6995
+ var mouseYviewport = pp.Y;
6996
+
6997
+ // Prevent user from moving object out of viewport
6998
+ var vpDim = Fit.Browser.GetViewPortDimensions();
6999
+ mouseXviewport = mouseXviewport >= 0 ? mouseXviewport : 0;
7000
+ mouseXviewport = mouseXviewport <= vpDim.Width ? mouseXviewport : vpDim.Width;
7001
+ mouseYviewport = mouseYviewport >= 0 ? mouseYviewport : 0;
7002
+ mouseYviewport = mouseYviewport <= vpDim.Height ? mouseYviewport : vpDim.Height;
6915
7003
 
6916
7004
  // Positioning (fixed, absolute, or relative)
6917
7005
  var positioning = Fit.DragDrop.Draggable._internal.active.Positioning;
@@ -7011,12 +7099,55 @@ Fit.DragDrop.Draggable = function(domElm, domTriggerElm)
7011
7099
  if (dropzoneActive.OnEnter)
7012
7100
  dropzoneActive.OnEnter(dropzoneActive.DropZone);
7013
7101
  }
7102
+
7103
+ // Stop page scrolling on iOS (required in both ontouchstart and ontouchmove handlers).
7104
+ // Ontouchmove must be registered with { passive: false } for this to work in browsers
7105
+ // which defaults to passive:true. This also prevents text selection on older browsers
7106
+ // not supporting user-select:none.
7107
+
7108
+ Fit.Events.PreventDefault(ev);
7014
7109
  });
7015
7110
  }
7016
7111
  }
7017
7112
 
7018
7113
  // Public
7019
7114
 
7115
+ /// <function container="Fit.DragDrop.Draggable" name="BringToFrontOnActivation" access="public" returns="boolean">
7116
+ /// <description> Get/set flag indicating whether to bring draggable to front when activated </description>
7117
+ /// <param name="val" type="boolean" default="undefined"> If defined, a value of True enables functionality, False disables it (default) </param>
7118
+ /// </function>
7119
+ this.BringToFrontOnActivation = function(val)
7120
+ {
7121
+ Fit.Validation.ExpectBoolean(val, true);
7122
+
7123
+ if (Fit.Validation.IsSet(val) === true)
7124
+ {
7125
+ bringToFrontOnActivation = val;
7126
+
7127
+ // NOTICE: z-index is not reset when BringToFrontOnActivation is disabled.
7128
+ // It may have been enabled at some point, but we keep the z-index to make
7129
+ // sure the element remains below other elements that have BringToFrontOnActivation
7130
+ // enabled. Resetting the z-index style attribute will cause the z-index value of
7131
+ // 99999 from CSS to be applied when dragging the element, causing it to temporarily
7132
+ // be positioned above elements with BringToFrontOnActivation enabled while dragging it.
7133
+ // That becomes bad UX when the user stops dragging it and releases it, at which point
7134
+ // the temporary z-index value of 99999 from CSS is no longer in effect and z-index:1200
7135
+ // takes effect again, which causes the element to be hidden behind elements with
7136
+ // BringToFrontOnActivation enabled.
7137
+ }
7138
+
7139
+ return bringToFrontOnActivation;
7140
+ }
7141
+
7142
+ /// <function container="Fit.DragDrop.Draggable" name="BringToFront" access="public">
7143
+ /// <description> Bring draggable to front </description>
7144
+ /// </function>
7145
+ this.BringToFront = function()
7146
+ {
7147
+ Fit.DragDrop.Draggable._internal.zIndex++;
7148
+ elm.style.zIndex = Fit.DragDrop.Draggable._internal.zIndex;
7149
+ }
7150
+
7020
7151
  /// <function container="Fit.DragDrop.Draggable" name="Reset" access="public">
7021
7152
  /// <description> Reset draggable to initial position </description>
7022
7153
  /// </function>
@@ -7028,6 +7159,7 @@ Fit.DragDrop.Draggable = function(domElm, domTriggerElm)
7028
7159
  elm.style.position = posState.position;
7029
7160
  elm.style.left = posState.left;
7030
7161
  elm.style.top = posState.top;
7162
+ elm.style.zIndex = "";
7031
7163
 
7032
7164
  posState = null;
7033
7165
  }
@@ -7057,9 +7189,15 @@ Fit.DragDrop.Draggable = function(domElm, domTriggerElm)
7057
7189
  Fit.Dom.RemoveClass(elm, "FitDragDropDraggable");
7058
7190
  Fit.Dom.RemoveClass((trgElm !== null ? trgElm : elm), "FitDragDropDraggableHandle");
7059
7191
 
7192
+ Fit.Events.RemoveHandler(elm, activationEventId);
7060
7193
  Fit.Events.RemoveHandler(((trgElm !== null) ? trgElm : elm), mouseDownEventId);
7061
7194
 
7062
- me = elm = posState = trgElm = onDragStart = onDragging = onDragStop = mouseDownEventId = null;
7195
+ if (Fit.DragDrop.Draggable._internal.active !== null && Fit.DragDrop.Draggable._internal.active.Draggable === me)
7196
+ {
7197
+ Fit.DragDrop.Draggable._internal.active = null;
7198
+ }
7199
+
7200
+ me = elm = posState = trgElm = bringToFrontOnActivation = onDragStart = onDragging = onDragStop = activationEventId = mouseDownEventId = null;
7063
7201
  }
7064
7202
 
7065
7203
  // Event handling
@@ -14574,16 +14712,21 @@ Fit.Controls.Dialog = function(controlId)
14574
14712
  var dialog = me.GetDomElement();
14575
14713
  var focusTrapStart = null;
14576
14714
  var focusTrapEnd = null;
14577
- var title = null;
14715
+ var titleContainer = null;
14716
+ var titleText = null;
14578
14717
  var titleButtons = null;
14579
14718
  var cmdMaximize = null;
14580
14719
  var cmdDismiss = null;
14581
14720
  var content = null;
14582
14721
  var iframe = null;
14722
+ var resizer = null;
14583
14723
  var buttons = null;
14584
14724
  var modal = false;
14585
14725
  var layer = null;
14586
14726
 
14727
+ var draggable = null;
14728
+ var suppressPositioning = false;
14729
+
14587
14730
  var width = null;
14588
14731
  var minWidth = null;
14589
14732
  var maxWidth = null;
@@ -14608,6 +14751,7 @@ Fit.Controls.Dialog = function(controlId)
14608
14751
  Fit.Dom.AddClass(dialog, "FitUiControlDialog");
14609
14752
  Fit.Dom.Data(dialog, "framed", "false");
14610
14753
  Fit.Dom.Data(dialog, "maximized", "false");
14754
+ Fit.Dom.Data(dialog, "resizable", "false");
14611
14755
 
14612
14756
  focusTrapStart = document.createElement("div");
14613
14757
  focusTrapStart.tabIndex = 0;
@@ -14682,44 +14826,43 @@ Fit.Controls.Dialog = function(controlId)
14682
14826
 
14683
14827
  if (val !== undefined) // Allow null to remove title
14684
14828
  {
14685
- if (val === null && title !== null)
14829
+ if (val === null && titleContainer !== null) // Remove title
14686
14830
  {
14687
- if (titleButtons !== null)
14831
+ if (titleButtons !== null || draggable !== null) // Clear title but keep title bar used for buttons and drag support
14688
14832
  {
14689
- Fit.Dom.Text(title, "");
14690
- Fit.Dom.Add(title, titleButtons);
14833
+ Fit.Dom.Text(titleText, "");
14691
14834
  }
14692
- else
14835
+ else // Remove title bar
14693
14836
  {
14694
- Fit.Dom.Remove(title);
14695
- title = null;
14837
+ Fit.Dom.Remove(titleContainer);
14838
+ titleContainer = null;
14839
+ titleText = null;
14696
14840
 
14697
14841
  setContentHeight();
14698
14842
  updatePosition();
14699
14843
  }
14700
14844
  }
14701
- else
14845
+ else // Set title
14702
14846
  {
14703
- if (title === null)
14847
+ if (titleContainer === null)
14704
14848
  {
14705
- title = document.createElement("div");
14706
- Fit.Dom.AddClass(title, "FitUiControlDialogTitle");
14707
- Fit.Dom.InsertAfter(focusTrapStart, title);
14708
- }
14709
-
14710
- Fit.Dom.Text(title, val);
14849
+ titleContainer = document.createElement("div");
14850
+ Fit.Dom.AddClass(titleContainer, "FitUiControlDialogTitle");
14851
+ Fit.Dom.InsertAfter(focusTrapStart, titleContainer);
14711
14852
 
14712
- if (titleButtons !== null)
14713
- {
14714
- Fit.Dom.Add(title, titleButtons);
14853
+ titleText = document.createElement("div");
14854
+ Fit.Dom.AddClass(titleText, "FitUiControlDialogTitleText");
14855
+ Fit.Dom.Add(titleContainer, titleText);
14715
14856
  }
14716
14857
 
14858
+ Fit.Dom.Text(titleText, val);
14859
+
14717
14860
  setContentHeight();
14718
14861
  updatePosition();
14719
14862
  }
14720
14863
  }
14721
14864
 
14722
- return (title !== null ? Fit.Dom.Text(title) : null);
14865
+ return (titleText !== null ? Fit.Dom.Text(titleText) : null);
14723
14866
  }
14724
14867
 
14725
14868
  /// <function container="Fit.Controls.Dialog" name="Width" access="public" returns="Fit.TypeDefs.CssValue">
@@ -14741,12 +14884,17 @@ Fit.Controls.Dialog = function(controlId)
14741
14884
  width = { Value: val, Unit: ((Fit.Validation.IsSet(unit) === true) ? unit : "px") };
14742
14885
  dialog.style.width = width.Value + width.Unit;
14743
14886
 
14744
- if (minWidth === null)
14887
+ // Disable default min-width and default max-width from CSS when width alone is set, unless Resizable is
14888
+ // enabled, in which case we must prevent dialog from "collapsing" completely due to resizing, or become
14889
+ // larger than the viewport. When resizing is enabled, appropriate values for min-width and max-width are
14890
+ // set from CSS, but can be overridden using MinimumWidth(..) and MaximumWidth(..)
14891
+
14892
+ if (minWidth === null && me.Resizable() === false)
14745
14893
  {
14746
14894
  dialog.style.minWidth = "0";
14747
14895
  }
14748
14896
 
14749
- if (maxWidth === null)
14897
+ if (maxWidth === null && me.Resizable() === false)
14750
14898
  {
14751
14899
  dialog.style.maxWidth = "none";
14752
14900
  }
@@ -14785,6 +14933,7 @@ Fit.Controls.Dialog = function(controlId)
14785
14933
 
14786
14934
  // defaultValue must match min-width in Dialog.css
14787
14935
  var defaultValue = { Value: 280, Unit: "px" };
14936
+ var defaultValueResizable = { Value: 15, Unit: "em" };
14788
14937
 
14789
14938
  if (Fit.Validation.IsSet(val) === true)
14790
14939
  {
@@ -14796,12 +14945,17 @@ Fit.Controls.Dialog = function(controlId)
14796
14945
  else
14797
14946
  {
14798
14947
  minWidth = null;
14799
- dialog.style.minWidth = (width !== null ? "0" : ""); // Apply "0" (no min-width) if width is set
14948
+ dialog.style.minWidth = (width !== null && me.Resizable() === false ? "0" : ""); // Apply "0" (no min-width) if width is set, to override min-width from CSS - similar logic found in Width(..)
14800
14949
  }
14801
14950
 
14802
14951
  updatePosition();
14803
14952
  }
14804
14953
 
14954
+ if (minWidth === null && me.Resizable() === true)
14955
+ {
14956
+ return defaultValueResizable;
14957
+ }
14958
+
14805
14959
  return (minWidth !== null ? minWidth : (width !== null ? width : defaultValue));
14806
14960
  }
14807
14961
 
@@ -14817,6 +14971,7 @@ Fit.Controls.Dialog = function(controlId)
14817
14971
 
14818
14972
  // defaultValue must match max-width in Dialog.css
14819
14973
  var defaultValue = { Value: 800, Unit: "px" };
14974
+ var defaultValueResizable = { Value: 100, Unit: "%" };
14820
14975
 
14821
14976
  if (Fit.Validation.IsSet(val) === true)
14822
14977
  {
@@ -14828,12 +14983,17 @@ Fit.Controls.Dialog = function(controlId)
14828
14983
  else
14829
14984
  {
14830
14985
  maxWidth = null;
14831
- dialog.style.maxWidth = (width !== null ? "none" : ""); // Apply "none" (no max-width) if width is set
14986
+ dialog.style.maxWidth = (width !== null && me.Resizable() === false ? "none" : ""); // Apply "none" (no max-width) if width is set, to override max-width from CSS - similar logic found in Width(..)
14832
14987
  }
14833
14988
 
14834
14989
  updatePosition();
14835
14990
  }
14836
14991
 
14992
+ if (maxWidth === null && me.Resizable() === true)
14993
+ {
14994
+ return defaultValueResizable;
14995
+ }
14996
+
14837
14997
  return (maxWidth !== null ? maxWidth : (width !== null ? width : defaultValue));
14838
14998
  }
14839
14999
 
@@ -14882,6 +15042,7 @@ Fit.Controls.Dialog = function(controlId)
14882
15042
 
14883
15043
  // defaultValue must match min-height in Dialog.css (which is not defined)
14884
15044
  var defaultValue = { Value: -1, Unit: "px" };
15045
+ var defaultValueResizable = { Value: 10, Unit: "em" };
14885
15046
 
14886
15047
  if (Fit.Validation.IsSet(val) === true)
14887
15048
  {
@@ -14900,6 +15061,11 @@ Fit.Controls.Dialog = function(controlId)
14900
15061
  updatePosition();
14901
15062
  }
14902
15063
 
15064
+ if (minHeight === null && me.Resizable() === true)
15065
+ {
15066
+ return defaultValueResizable;
15067
+ }
15068
+
14903
15069
  return (minHeight !== null ? minHeight : defaultValue);
14904
15070
  }
14905
15071
 
@@ -14915,6 +15081,7 @@ Fit.Controls.Dialog = function(controlId)
14915
15081
 
14916
15082
  // defaultValue must match max-height in Dialog.css (which is not defined)
14917
15083
  var defaultValue = { Value: -1, Unit: "px" };
15084
+ var defaultValueResizable = { Value: 100, Unit: "%" };
14918
15085
 
14919
15086
  if (Fit.Validation.IsSet(val) === true)
14920
15087
  {
@@ -14933,6 +15100,11 @@ Fit.Controls.Dialog = function(controlId)
14933
15100
  updatePosition();
14934
15101
  }
14935
15102
 
15103
+ if (maxHeight === null && me.Resizable() === true)
15104
+ {
15105
+ return defaultValueResizable;
15106
+ }
15107
+
14936
15108
  return (maxHeight !== null ? maxHeight : defaultValue);
14937
15109
  }
14938
15110
 
@@ -14949,9 +15121,19 @@ Fit.Controls.Dialog = function(controlId)
14949
15121
  if (val !== modal && me.IsOpen() === true)
14950
15122
  {
14951
15123
  if (val === true)
15124
+ {
15125
+ layer.style.zIndex = dialog.style.zIndex; // Update in case dialog was/is draggable and it was brought to front (z-index set by Fit.DragDrop.Draggable when Draggable is enabled)
14952
15126
  Fit.Dom.InsertBefore(dialog, layer);
15127
+
15128
+ if (me.IsOpen() === true)
15129
+ {
15130
+ me.BringToFront();
15131
+ }
15132
+ }
14953
15133
  else
15134
+ {
14954
15135
  Fit.Dom.Remove(layer);
15136
+ }
14955
15137
  }
14956
15138
 
14957
15139
  modal = val;
@@ -15165,6 +15347,84 @@ Fit.Controls.Dialog = function(controlId)
15165
15347
  return (cmdDismiss !== null);
15166
15348
  }
15167
15349
 
15350
+ /// <function container="Fit.Controls.Dialog" name="Draggable" access="public" returns="boolean">
15351
+ /// <description> Get/set flag indicating whether dialog can be moved around on screen </description>
15352
+ /// <param name="val" type="boolean" default="undefined"> If defined, a value of True enables dragging, False disables it (default) </param>
15353
+ /// </function>
15354
+ this.Draggable = function(val)
15355
+ {
15356
+ Fit.Validation.ExpectBoolean(val, true);
15357
+
15358
+ if (Fit.Validation.IsSet(val) === true)
15359
+ {
15360
+ if (val === true && draggable === null)
15361
+ {
15362
+ if (me.Title() === null) // Ensure title element
15363
+ {
15364
+ me.Title("");
15365
+ }
15366
+
15367
+ draggable = new Fit.DragDrop.Draggable(me.GetDomElement(), titleText);
15368
+ draggable.BringToFrontOnActivation(true);
15369
+ draggable.OnDragStart(function()
15370
+ {
15371
+ if (me.Maximized() === true)
15372
+ {
15373
+ return false; // Prevent dragging while maximized
15374
+ }
15375
+
15376
+ suppressPositioning = true;
15377
+ });
15378
+ }
15379
+ else if (val === false && draggable !== null)
15380
+ {
15381
+ draggable.Dispose();
15382
+ draggable = null;
15383
+ suppressPositioning = false;
15384
+
15385
+ if (me.Title() === "")
15386
+ {
15387
+ me.Title(null); // Remove title element - will remain enabled if buttons (dismiss/maximize) are present though
15388
+ }
15389
+
15390
+ updatePosition();
15391
+ }
15392
+ }
15393
+
15394
+ return (draggable !== null);
15395
+ }
15396
+
15397
+ /// <function container="Fit.Controls.Dialog" name="Resizable" access="public" returns="boolean">
15398
+ /// <description> Get/set flag indicating whether dialog can be resized by the user </description>
15399
+ /// <param name="val" type="boolean" default="undefined"> If defined, a value of True enables resizing, False disables it (default) </param>
15400
+ /// </function>
15401
+ this.Resizable = function(val)
15402
+ {
15403
+ Fit.Validation.ExpectBoolean(val, true);
15404
+
15405
+ if (Fit.Validation.IsSet(val) === true)
15406
+ {
15407
+ if (val === true && resizer === null)
15408
+ {
15409
+ resizer = createResizerElement();
15410
+ Fit.Dom.InsertAfter(content, resizer);
15411
+ Fit.Dom.Data(dialog, "resizable", "true");
15412
+ }
15413
+ else if (val === false && resizer !== null)
15414
+ {
15415
+ Fit.Dom.Remove(resizer);
15416
+ resizer = null;
15417
+ Fit.Dom.Data(dialog, "resizable", "false");
15418
+
15419
+ // Undo width/height set by user
15420
+ me.Width(me.Width().Value, me.Width().Unit);
15421
+ me.Height(me.Height().Value, me.Height().Unit);
15422
+ }
15423
+ }
15424
+
15425
+ return (resizer !== null);
15426
+ }
15427
+
15168
15428
  /// <function container="Fit.Controls.Dialog" name="AddButton" access="public">
15169
15429
  /// <description> Add button to dialog </description>
15170
15430
  /// <param name="btn" type="Fit.Controls.Button"> Instance of Fit.Controls.Button </param>
@@ -15264,6 +15524,18 @@ Fit.Controls.Dialog = function(controlId)
15264
15524
  return Fit.Dom.IsRooted(dialog);
15265
15525
  }
15266
15526
 
15527
+ /// <function container="Fit.Controls.Dialog" name="BringToFront" access="public">
15528
+ /// <description> Bring draggable dialog to front </description>
15529
+ /// </function>
15530
+ this.BringToFront = function()
15531
+ {
15532
+ if (draggable !== null)
15533
+ {
15534
+ draggable.BringToFront();
15535
+ layer.style.zIndex = dialog.style.zIndex; // Ensure modal layer always remains exactly behind dialog with the same z-index value
15536
+ }
15537
+ }
15538
+
15267
15539
  /// <function container="Fit.Controls.Dialog" name="Open" access="public">
15268
15540
  /// <description> Open dialog </description>
15269
15541
  /// <param name="renderTarget" type="DOMElement" default="undefined">
@@ -15279,8 +15551,13 @@ Fit.Controls.Dialog = function(controlId)
15279
15551
  if (me.IsOpen() === true)
15280
15552
  return;
15281
15553
 
15554
+ me.BringToFront();
15555
+
15282
15556
  if (modal === true)
15557
+ {
15558
+ layer.style.zIndex = dialog.style.zIndex; // Ensure modal layer always remains exactly behind dialog with the same z-index value
15283
15559
  Fit.Dom.Add(renderTarget || document.body, layer);
15560
+ }
15284
15561
 
15285
15562
  Fit.Dom.Add(renderTarget || document.body, dialog);
15286
15563
 
@@ -15388,6 +15665,11 @@ Fit.Controls.Dialog = function(controlId)
15388
15665
  cmdDismiss.Dispose();
15389
15666
  }
15390
15667
 
15668
+ if (draggable !== null)
15669
+ {
15670
+ draggable.Dispose();
15671
+ }
15672
+
15391
15673
  if (buttons !== null)
15392
15674
  {
15393
15675
  Fit.Array.ForEach(Fit.Array.Copy(buttons.children), function(buttonElm) // Using Copy(..) since Dispose() modifies children collection
@@ -15406,7 +15688,7 @@ Fit.Controls.Dialog = function(controlId)
15406
15688
  Fit.Events.RemoveHandler(window, resizeHandlerId);
15407
15689
  }
15408
15690
 
15409
- me = dialog = title = titleButtons = cmdMaximize = cmdDismiss = content = buttons = modal = layer = width = minWidth = maxWidth = height = minHeight = maxHeight = mutationObserverId = resizeHandlerId = onDismissHandlers = onCloseHandlers = isClosing = null;
15691
+ me = dialog = focusTrapStart = focusTrapEnd = titleContainer = titleText = titleButtons = cmdMaximize = cmdDismiss = content = iframe = resizer = buttons = modal = layer = draggable = suppressPositioning = width = minWidth = maxWidth = height = minHeight = maxHeight = mutationObserverId = resizeHandlerId = onDismissHandlers = onCloseHandlers = isClosing = null;
15410
15692
 
15411
15693
  base();
15412
15694
  });
@@ -15454,6 +15736,17 @@ Fit.Controls.Dialog = function(controlId)
15454
15736
  Fit.Array.Add(onCloseHandlers, cb);
15455
15737
  }
15456
15738
 
15739
+ // ============================================
15740
+ // Protected
15741
+ // ============================================
15742
+
15743
+ this._internal = (this._internal ? this._internal : {});
15744
+
15745
+ this._internal.GetLayerElement = function()
15746
+ {
15747
+ return layer;
15748
+ }
15749
+
15457
15750
  // ============================================
15458
15751
  // Private
15459
15752
  // ============================================
@@ -15465,6 +15758,130 @@ Fit.Controls.Dialog = function(controlId)
15465
15758
  return div;
15466
15759
  }
15467
15760
 
15761
+ function createResizerElement()
15762
+ {
15763
+ // NOTICE: Resizing a dialog with an iframe:
15764
+ // Resizing might not work properly with iframes with content from a foreign domain,
15765
+ // since browsers won't dispatch mouse/touch events over foreign domains.
15766
+ // Some older browsers won't even dispatch the events over iframes with content
15767
+ // originating from the same domain as the one hosting the main page.
15768
+ // This becomes a problem if the user is able to quickly move the mouse over the
15769
+ // iframe while resizing, and the computer/browser is not fast enough to "catch up" with
15770
+ // the movement of the resize handle. Slowly resizing the dialog in this case works.
15771
+ // Possible solution:
15772
+ // If we need better support for resizing dialogs with iframes, consider using a timer
15773
+ // which on a regular basis (2-10 times per second) updates the dialog size based on the
15774
+ // pointer position. That way the missing invocation of OnTouchMove or OnMouseMove is mitigated.
15775
+ // It should also dispatch the logic found in OnTouchEnd, OnTouchCancel and OnMouseUp if the
15776
+ // timer detects that the pointer (mouse or finger) has been released. This can be determined
15777
+ // using: Fit.Events.GetPointerState().Buttons.Touch || Fit.Events.GetPointerState().Buttons.Primary
15778
+ // However, make sure this issue is resolved first: https://github.com/Jemt/Fit.UI/issues/153
15779
+ // The issue relates to the reliablity of Fit.Events.GetPointerState().Buttons.Primary since it
15780
+ // is unset OnMouseOut, which will be triggered if mouse/finger leaves resize handle.
15781
+
15782
+ var resizer = document.createElement("div");
15783
+ Fit.Dom.AddClass(resizer, "FitUiControlDialogResizer");
15784
+
15785
+ Fit.Events.AddHandler(resizer, (Fit.Browser.IsTouchEnabled() === true ? "touchstart" : "mousedown"), function(e)
15786
+ {
15787
+ if (me.Maximized() === true)
15788
+ {
15789
+ return; // Prevent resizing while maximized
15790
+ }
15791
+
15792
+ var ev = Fit.Events.GetEvent(e);
15793
+
15794
+ var initPos = Fit.Events.GetPointerState().Coordinates.ViewPort;
15795
+ var initDim = { Width: me.GetDomElement().offsetWidth, Height: me.GetDomElement().offsetHeight };
15796
+
15797
+ var moveHandler = null; // OnMouseMove or OnTouchMove event ID
15798
+ var releaseHandler = null; // OnMouseUp or OnTouchEnd event ID
15799
+ var cancelHandler = null; // OnTouchCancel event ID (only set on touch devices)
15800
+
15801
+ var cleanup = function()
15802
+ {
15803
+ Fit.Events.RemoveHandler(document, moveHandler);
15804
+ Fit.Events.RemoveHandler(document, releaseHandler);
15805
+
15806
+ if (cancelHandler !== null)
15807
+ {
15808
+ Fit.Events.RemoveHandler(document, cancelHandler);
15809
+ }
15810
+ };
15811
+
15812
+ moveHandler = Fit.Events.AddHandler(document, (Fit.Browser.IsTouchEnabled() === true ? "touchmove" : "mousemove"), { passive: false /* https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener */ }, function(e)
15813
+ {
15814
+ if (me === null) // Unlikely, but theoretically possible that dialog gets disposed while being resized
15815
+ {
15816
+ cleanup();
15817
+ return;
15818
+ }
15819
+
15820
+ var ev = Fit.Events.GetEvent(e);
15821
+
15822
+ // Determine mouse/finger travel
15823
+
15824
+ var pp = Fit.Events.GetPointerState().Coordinates.ViewPort;
15825
+ var left = pp.X - initPos.X; // Positive = moved right, negative = moved left
15826
+ var top = pp.Y - initPos.Y; // Positive = moved down, negative = moved up
15827
+
15828
+ if (suppressPositioning === false) // Dialog remains centered - adjust amount of resizing needed ("double up")
15829
+ {
15830
+ left = left * 2; // Double up when dialog is centered since both left and right side is resized
15831
+ top = top * 1.3; // Approximate value to adjust resizing vertically - not 100% accurate - resize handle will not follow pointer precisely, but close enough - reason: dialog is not centered 50/50 vertically but 25/75, so a static value such as 1.3 is not accurate - the actual adjustment needed for accurate positioning is variable
15832
+ }
15833
+
15834
+ // Calculate and apply new width and height
15835
+
15836
+ var newWidth = initDim.Width + left;
15837
+ var newHeight = initDim.Height + top;
15838
+
15839
+ if (newWidth > 0)
15840
+ {
15841
+ dialog.style.width = newWidth + "px";
15842
+ }
15843
+
15844
+ if (newHeight > 0)
15845
+ {
15846
+ dialog.style.height = newHeight + "px";
15847
+ setContentHeight();
15848
+ }
15849
+
15850
+ if (newWidth > 0 || newHeight > 0)
15851
+ {
15852
+ updatePosition();
15853
+ }
15854
+
15855
+ // Stop page scrolling on iOS (required in both ontouchstart and ontouchmove handlers).
15856
+ // Ontouchmove must be registered with { passive: false } for this to work in browsers
15857
+ // which defaults to passive:true. This also prevents text selection on older browsers
15858
+ // not supporting user-select:none.
15859
+
15860
+ Fit.Events.PreventDefault(ev);
15861
+ });
15862
+
15863
+ releaseHandler = Fit.Events.AddHandler(document, (Fit.Browser.IsTouchEnabled() === true ? "touchend" : "mouseup"), function(e)
15864
+ {
15865
+ cleanup();
15866
+ });
15867
+
15868
+ if (Fit.Browser.IsTouchEnabled() === true)
15869
+ {
15870
+ cancelHandler = Fit.Events.AddHandler(document, "touchcancel", function(e) // Can be triggered on iOS by swiping down notification panel or control center while resizing
15871
+ {
15872
+ cleanup();
15873
+ });
15874
+ }
15875
+
15876
+ if (ev.type === "touchstart")
15877
+ {
15878
+ Fit.Events.PreventDefault(ev); // Stop page scrolling on iOS (required in both ontouchstart and ontouchmove handlers)
15879
+ }
15880
+ });
15881
+
15882
+ return resizer;
15883
+ }
15884
+
15468
15885
  function setContentHeight()
15469
15886
  {
15470
15887
  if (me.IsOpen() === false)
@@ -15478,10 +15895,10 @@ Fit.Controls.Dialog = function(controlId)
15478
15895
 
15479
15896
  content.style.height = "";
15480
15897
 
15481
- if ((buttons !== null || title !== null) && (me.Maximized() === true || me.Height().Value !== -1 || me.MinimumHeight().Value !== -1 || me.MaximumHeight().Value !== -1))
15898
+ if ((buttons !== null || titleContainer !== null) && (me.Maximized() === true || me.Height().Value !== -1 || me.MinimumHeight().Value !== -1 || me.MaximumHeight().Value !== -1))
15482
15899
  {
15483
15900
  var dh = dialog.offsetHeight;
15484
- var th = (title !== null ? title.offsetHeight : 0);
15901
+ var th = (titleContainer !== null ? titleContainer.offsetHeight : 0);
15485
15902
  var bh = (buttons !== null ? buttons.offsetHeight : 0);
15486
15903
 
15487
15904
  content.style.height = (dh - th - bh) + "px";
@@ -15490,6 +15907,9 @@ Fit.Controls.Dialog = function(controlId)
15490
15907
 
15491
15908
  function updatePosition()
15492
15909
  {
15910
+ if (suppressPositioning === true)
15911
+ return;
15912
+
15493
15913
  if (me.IsOpen() === false)
15494
15914
  return;
15495
15915
 
@@ -15513,13 +15933,18 @@ Fit.Controls.Dialog = function(controlId)
15513
15933
  elm.style.top = "0px";
15514
15934
 
15515
15935
  var dim = Fit.Browser.GetViewPortDimensions();
15516
- var offsetLeft = Math.floor((dim.Width / 2) - (elm.offsetWidth / 2)); // Center horizontally - place center of dialog 1/2 (50%) from the left
15517
- var offsetTop = Math.floor((dim.Height / 3) - (elm.offsetHeight / 2)); // Place center of dialog 1/3 (33%) from the top
15936
+ var offsetTop = (dim.Height - elm.offsetHeight) * 0.25; // Place dialog vertically with 25% of available space above dialog and remaining 75% space below dialog
15937
+ var offsetLeft = (dim.Width - elm.offsetWidth) * 0.5; // Place dialog exactly in the middle horizontally
15518
15938
 
15519
- if (offsetTop < 0)
15939
+ if (offsetTop < 0) // Value becomes negative if dialog is higher than viewport
15940
+ {
15520
15941
  offsetTop = 0;
15521
- if (offsetLeft < 0)
15942
+ }
15943
+
15944
+ if (offsetLeft < 0) // Value becomes negative if dialog is wider than viewport
15945
+ {
15522
15946
  offsetLeft = 0;
15947
+ }
15523
15948
 
15524
15949
  elm.style.left = offsetLeft + "px";
15525
15950
  elm.style.top = offsetTop + "px";
@@ -15533,6 +15958,12 @@ Fit.Controls.Dialog = function(controlId)
15533
15958
  {
15534
15959
  Fit.Dom.Remove(titleButtons);
15535
15960
  titleButtons = null;
15961
+
15962
+ if (me.Title() === "")
15963
+ {
15964
+ me.Title(null); // Remove title element - will remain enabled if Draggable is enabled
15965
+ }
15966
+
15536
15967
  return;
15537
15968
  }
15538
15969
 
@@ -15545,7 +15976,7 @@ Fit.Controls.Dialog = function(controlId)
15545
15976
 
15546
15977
  titleButtons = document.createElement("div");
15547
15978
  Fit.Dom.AddClass(titleButtons, "FitUiControlDialogTitleButtons");
15548
- Fit.Dom.Add(title, titleButtons);
15979
+ Fit.Dom.Add(titleContainer, titleButtons);
15549
15980
  }
15550
15981
 
15551
15982
  // Add/re-add to ensure proper order
@@ -15590,7 +16021,9 @@ Fit.Controls.Dialog._internal.BaseDialog = function(content, showCancel, cb)
15590
16021
  var d = new Fit.Controls.Dialog();
15591
16022
  d.Content(content.replace(/\n/g, "<br>"));
15592
16023
  d.Modal(true);
16024
+
15593
16025
  Fit.Dom.AddClass(d.GetDomElement(), "FitUiControlDialogBase");
16026
+ Fit.Dom.AddClass(d._internal.GetLayerElement(), "FitUiControlDialogBaseModalLayer");
15594
16027
 
15595
16028
  // Declare buttons
15596
16029
 
@@ -21659,6 +22092,7 @@ Fit.Controls.Input = function(ctlId)
21659
22092
  var designEditorMustDisposeWhenReady = false;
21660
22093
  var designEditorUpdateSizeDebouncer = -1;
21661
22094
  var designEditorActiveToolbarPanel = null; // { DomElement: HTMLElement, UnlockFocusStateIfEmojiPanelIsClosed: function, CloseEmojiPanel: function }
22095
+ var designEditorDetached = null; // { GetValue: function, Reload: function, Dispose: function }
21662
22096
  //var htmlWrappedInParagraph = false;
21663
22097
  var wasAutoChangedToMultiLineMode = false; // Used to revert to single line if multi line was automatically enabled along with DesignMode(true), Maximizable(true), or Resizable(true)
21664
22098
  var minimizeHeight = -1;
@@ -21674,6 +22108,7 @@ Fit.Controls.Input = function(ctlId)
21674
22108
  var debounceOnChangeTimeout = -1;
21675
22109
  var debouncedOnChange = null;
21676
22110
  var imageBlobUrls = []; // Specific to DesignMode
22111
+ var locale = null;
21677
22112
 
21678
22113
  // ============================================
21679
22114
  // Init
@@ -21739,6 +22174,7 @@ Fit.Controls.Input = function(ctlId)
21739
22174
  me._internal.Data("designmode", "false");
21740
22175
 
21741
22176
  Fit.Internationalization.OnLocaleChanged(localize);
22177
+ localize();
21742
22178
 
21743
22179
  me.OnBlur(function(sender)
21744
22180
  {
@@ -21981,20 +22417,22 @@ Fit.Controls.Input = function(ctlId)
21981
22417
  input.value = val;
21982
22418
  }
21983
22419
 
22420
+ // Notice: Identical logic found in DesignMode(true, config)!
21984
22421
  if (designEditorConfig !== null && designEditorConfig.Plugins && designEditorConfig.Plugins.Images && designEditorConfig.Plugins.Images.RevokeExternalBlobUrlsOnDispose === true)
21985
22422
  {
21986
- // Keep track of image blobs added via Value(..) so we can dispose them automatically.
22423
+ // Keep track of image blobs added via Value(..) so we can dispose of them automatically.
21987
22424
  // When RevokeExternalBlobUrlsOnDispose is True it basically means that the Input control
21988
22425
  // is allowed (and expected) to take control over memory management for these blobs
21989
22426
  // based on the rule set in RevokeBlobUrlsOnDispose.
22427
+ // This code is also found in DesignMode(true, config) since images might be added before
22428
+ // editor is created, in which case we do not yet have the editor configuration used to determine
22429
+ // the desired behaviour.
21990
22430
 
21991
- var blobImages = val.match(/<img .*?src=(["'])blob:.+?\1.*?>/gi) || [];
22431
+ var blobUrls = Fit.String.ParseImageBlobUrls(val);
21992
22432
 
21993
- Fit.Array.ForEach(blobImages, function(img)
22433
+ Fit.Array.ForEach(blobUrls, function(blobUrl)
21994
22434
  {
21995
- var blobUrl = img.match(/src=(["'])(blob:.*?)\1/i)[2];
21996
-
21997
- if (Fit.Array.Contains(blobImages, blobUrl) === false)
22435
+ if (Fit.Array.Contains(imageBlobUrls, blobUrl) === false)
21998
22436
  {
21999
22437
  Fit.Array.Add(imageBlobUrls, blobUrl);
22000
22438
  }
@@ -22206,7 +22644,7 @@ Fit.Controls.Input = function(ctlId)
22206
22644
  });
22207
22645
  }
22208
22646
 
22209
- me = orgVal = preVal = input = cmdResize = designEditor = designEditorDom = designEditorDirty = designEditorDirtyPending = designEditorConfig = designEditorReloadConfig = designEditorRestoreButtonState = designEditorSuppressPaste = designEditorSuppressOnResize = designEditorMustReloadWhenReady = designEditorMustDisposeWhenReady = designEditorUpdateSizeDebouncer = designEditorActiveToolbarPanel /*= htmlWrappedInParagraph*/ = wasAutoChangedToMultiLineMode = minimizeHeight = maximizeHeight = minMaxUnit = maximizeHeightConfigured = resizable = nativeResizableAvailable = mutationObserverId = rootedEventId = createWhenReadyIntervalId = isIe8 = debounceOnChangeTimeout = debouncedOnChange = imageBlobUrls = null;
22647
+ me = orgVal = preVal = input = cmdResize = designEditor = designEditorDom = designEditorDirty = designEditorDirtyPending = designEditorConfig = designEditorReloadConfig = designEditorRestoreButtonState = designEditorSuppressPaste = designEditorSuppressOnResize = designEditorMustReloadWhenReady = designEditorMustDisposeWhenReady = designEditorUpdateSizeDebouncer = designEditorActiveToolbarPanel = designEditorDetached /*= htmlWrappedInParagraph*/ = wasAutoChangedToMultiLineMode = minimizeHeight = maximizeHeight = minMaxUnit = maximizeHeightConfigured = resizable = nativeResizableAvailable = mutationObserverId = rootedEventId = createWhenReadyIntervalId = isIe8 = debounceOnChangeTimeout = debouncedOnChange = imageBlobUrls = locale = null;
22210
22648
 
22211
22649
  base();
22212
22650
  });
@@ -22717,7 +23155,7 @@ Fit.Controls.Input = function(ctlId)
22717
23155
  /// <description> Configuration for image plugins </description>
22718
23156
  /// <member name="Enabled" type="boolean"> Flag indicating whether to enable image plugins or not (defaults to False) </member>
22719
23157
  /// <member name="EmbedType" type="'base64' | 'blob'" default="undefined">
22720
- /// How to store and embed images. Base64 is persistent while blob (default) is temporary
23158
+ /// How to store and embed images. Base64 (default) is persistent while blob is temporary
22721
23159
  /// and must be extracted from memory and uploaded/stored to be permanantly persisted.
22722
23160
  /// References to blobs can be parsed from the HTML value produced by the editor.
22723
23161
  /// </member>
@@ -22751,6 +23189,7 @@ Fit.Controls.Input = function(ctlId)
22751
23189
  /// <member name="Links" type="boolean" default="undefined"> Enable links (defaults to True) </member>
22752
23190
  /// <member name="Emojis" type="boolean" default="undefined"> Enable emoji button (defaults to False) </member>
22753
23191
  /// <member name="Images" type="boolean" default="undefined"> Enable image button (defaults to false) </member>
23192
+ /// <member name="Detach" type="boolean" default="undefined"> Enable detach button (defaults to false) </member>
22754
23193
  /// <member name="Position" type="'Top' | 'Bottom'" default="undefined"> Toolbar position (defaults to Top) </member>
22755
23194
  /// <member name="Sticky" type="boolean" default="undefined"> Make toolbar stick to edge of scroll container on supported browsers when scrolling (defaults to False) </member>
22756
23195
  /// <member name="HideInitially" type="boolean" default="undefined"> Hide toolbar until control gains focus (defaults to False) </member>
@@ -22781,6 +23220,7 @@ Fit.Controls.Input = function(ctlId)
22781
23220
  /// <member name="Icon" type="string" default="undefined"> Optional URL to icon/image </member>
22782
23221
  /// <member name="Url" type="string" default="undefined"> Optional URL to associate with tag </member>
22783
23222
  /// <member name="Data" type="string" default="undefined"> Optional data to associate with tag </member>
23223
+ /// <member name="Context" type="string" default="undefined"> Optional context information to associate with tag </member>
22784
23224
  /// </container>
22785
23225
  /// <container name="Fit.Controls.InputTypeDefs.DesignModeTagsOnResponseEventHandlerArgs">
22786
23226
  /// <description> Response handler event arguments </description>
@@ -22802,6 +23242,7 @@ Fit.Controls.Input = function(ctlId)
22802
23242
  /// <member name="Type" type="string"> Tag type (marker) </member>
22803
23243
  /// <member name="Url" type="string" default="undefined"> Optional tag URL </member>
22804
23244
  /// <member name="Data" type="string" default="undefined"> Optional tag data </member>
23245
+ /// <member name="Context" type="string" default="undefined"> Optional tag context </member>
22805
23246
  /// </container>
22806
23247
  /// <container name="Fit.Controls.InputTypeDefs.DesignModeTagsTagCreatorCallbackArgs">
22807
23248
  /// <description> TagCreator event arguments </description>
@@ -22838,8 +23279,8 @@ Fit.Controls.Input = function(ctlId)
22838
23279
  /// btoa(JSON.stringify({ creationDate: new Date(), active: true }))
22839
23280
  ///
22840
23281
  /// The data eventuelly results in a tag being added to the editor with the following format:
22841
- /// <a data-tag-type="@" data-tag-id="unique id 1" data-tag-data="..." href="show/1">Tag name 1</a>
22842
- /// The data-tag-data attribute is only declared if the corresponding Data property is defined in data.
23282
+ /// <a data-tag-type="@" data-tag-id="unique id 1" data-tag-data="..." data-tag-context="..." href="show/1">Tag name 1</a>
23283
+ /// The data-tag-data and data-tag-context attributes are only declared if the corresponding Data and Context properties are defined in data.
22843
23284
  /// </member>
22844
23285
  /// <member name="JsonpCallback" type="string" default="undefined"> Name of URL parameter receiving name of JSONP callback function (only for JSONP services) </member>
22845
23286
  /// <member name="JsonpTimeout" type="integer" default="undefined"> Number of milliseconds to allow JSONP request to wait for a response before aborting (only for JSONP services) </member>
@@ -22882,6 +23323,20 @@ Fit.Controls.Input = function(ctlId)
22882
23323
  /// <member name="PreventResizeBeyondMaximumHeight" type="boolean" default="undefined"> Prevent user from resizing editor beyond maximum height (see MaximumHeight property - defaults to False) </member>
22883
23324
  /// </container>
22884
23325
 
23326
+ /// <container name="Fit.Controls.InputTypeDefs.DesignModeDetachable">
23327
+ /// <description> Detachable configuration </description>
23328
+ /// <member name="Title" type="string" default="undefined"> Dialog title </member>
23329
+ /// <member name="Maximizable" type="boolean" default="undefined"> Flag indicating whether dialog is maximizable </member>
23330
+ /// <member name="Maximized" type="boolean" default="undefined"> Flag indicating whether dialog is initially maximized </member>
23331
+ /// <member name="Draggable" type="boolean" default="undefined"> Flag indicating whether dialog is draggable </member>
23332
+ /// <member name="Width" type="{ Value: number, Unit?: Fit.TypeDefs.CssUnit }" default="undefined"> Dialog width </member>
23333
+ /// <member name="MinimumWidth" type="{ Value: number, Unit?: Fit.TypeDefs.CssUnit }" default="undefined"> Minimum width of dialog </member>
23334
+ /// <member name="MaximumWidth" type="{ Value: number, Unit?: Fit.TypeDefs.CssUnit }" default="undefined"> Maximum Width of dialog </member>
23335
+ /// <member name="Height" type="{ Value: number, Unit?: Fit.TypeDefs.CssUnit }" default="undefined"> Dialog height </member>
23336
+ /// <member name="MinimumHeight" type="{ Value: number, Unit?: Fit.TypeDefs.CssUnit }" default="undefined"> Minimum height of dialog </member>
23337
+ /// <member name="MaximumHeight" type="{ Value: number, Unit?: Fit.TypeDefs.CssUnit }" default="undefined"> Maximum height of dialog </member>
23338
+ /// </container>
23339
+
22885
23340
  /// <container name="Fit.Controls.InputTypeDefs.DesignModeConfig">
22886
23341
  /// <description> Configuration for DesignMode </description>
22887
23342
  /// <member name="Plugins" type="Fit.Controls.InputTypeDefs.DesignModeConfigPlugins" default="undefined"> Plugins configuration </member>
@@ -22889,6 +23344,7 @@ Fit.Controls.Input = function(ctlId)
22889
23344
  /// <member name="InfoPanel" type="Fit.Controls.InputTypeDefs.DesignModeConfigInfoPanel" default="undefined"> Information panel configuration </member>
22890
23345
  /// <member name="Tags" type="Fit.Controls.InputTypeDefs.DesignModeConfigTags" default="undefined"> Tags configuration </member>
22891
23346
  /// <member name="AutoGrow" type="Fit.Controls.InputTypeDefs.DesignModeAutoGrow" default="undefined"> Auto grow configuration </member>
23347
+ /// <member name="Detachable" type="Fit.Controls.InputTypeDefs.DesignModeDetachable" default="undefined"> Detachable configuration </member>
22892
23348
  /// </container>
22893
23349
 
22894
23350
  /// <function container="Fit.Controls.Input" name="DesignMode" access="public" returns="boolean">
@@ -22922,6 +23378,7 @@ Fit.Controls.Input = function(ctlId)
22922
23378
  Fit.Validation.ExpectStringValue(((editorConfig || {}).Toolbar || {}).Position, true);
22923
23379
  Fit.Validation.ExpectBoolean(((editorConfig || {}).Toolbar || {}).Sticky, true);
22924
23380
  Fit.Validation.ExpectBoolean(((editorConfig || {}).Toolbar || {}).HideInitially, true);
23381
+ Fit.Validation.ExpectBoolean(((editorConfig || {}).Toolbar || {}).Detach, true);
22925
23382
  Fit.Validation.ExpectObject((editorConfig || {}).InfoPanel, true);
22926
23383
  Fit.Validation.ExpectString(((editorConfig || {}).InfoPanel || {}).Text, true);
22927
23384
  Fit.Validation.ExpectString(((editorConfig || {}).InfoPanel || {}).Alignment, true);
@@ -22935,6 +23392,29 @@ Fit.Controls.Input = function(ctlId)
22935
23392
  Fit.Validation.ExpectNumber((((editorConfig || {}).AutoGrow || {}).MaximumHeight || {}).Value, true);
22936
23393
  Fit.Validation.ExpectStringValue((((editorConfig || {}).AutoGrow || {}).MaximumHeight || {}).Unit, true);
22937
23394
  Fit.Validation.ExpectBoolean(((editorConfig || {}).AutoGrow || {}).PreventResizeBeyondMaximumHeight, true);
23395
+ Fit.Validation.ExpectObject((editorConfig || {}).Detachable, true);
23396
+ Fit.Validation.ExpectString(((editorConfig || {}).Detachable || {}).Title, true);
23397
+ Fit.Validation.ExpectBoolean(((editorConfig || {}).Detachable || {}).Maximizable, true);
23398
+ Fit.Validation.ExpectBoolean(((editorConfig || {}).Detachable || {}).Maximized, true);
23399
+ Fit.Validation.ExpectBoolean(((editorConfig || {}).Detachable || {}).Draggable, true);
23400
+ Fit.Validation.ExpectObject(((editorConfig || {}).Detachable || {}).Width, true);
23401
+ Fit.Validation.ExpectNumber((((editorConfig || {}).Detachable || {}).Width || {}).Value, true);
23402
+ Fit.Validation.ExpectStringValue((((editorConfig || {}).Detachable || {}).Width || {}).Unit, true);
23403
+ Fit.Validation.ExpectObject(((editorConfig || {}).Detachable || {}).MinimumWidth, true);
23404
+ Fit.Validation.ExpectNumber((((editorConfig || {}).Detachable || {}).MinimumWidth || {}).Value, true);
23405
+ Fit.Validation.ExpectStringValue((((editorConfig || {}).Detachable || {}).MinimumWidth || {}).Unit, true);
23406
+ Fit.Validation.ExpectObject(((editorConfig || {}).Detachable || {}).MaximumWidth, true);
23407
+ Fit.Validation.ExpectNumber((((editorConfig || {}).Detachable || {}).MaximumWidth || {}).Value, true);
23408
+ Fit.Validation.ExpectStringValue((((editorConfig || {}).Detachable || {}).MaximumWidth || {}).Unit, true);
23409
+ Fit.Validation.ExpectObject(((editorConfig || {}).Detachable || {}).Height, true);
23410
+ Fit.Validation.ExpectNumber((((editorConfig || {}).Detachable || {}).Height || {}).Value, true);
23411
+ Fit.Validation.ExpectStringValue((((editorConfig || {}).Detachable || {}).Height || {}).Unit, true);
23412
+ Fit.Validation.ExpectObject(((editorConfig || {}).Detachable || {}).MinimumHeight, true);
23413
+ Fit.Validation.ExpectNumber((((editorConfig || {}).Detachable || {}).MinimumHeight || {}).Value, true);
23414
+ Fit.Validation.ExpectStringValue((((editorConfig || {}).Detachable || {}).MinimumHeight || {}).Unit, true);
23415
+ Fit.Validation.ExpectObject(((editorConfig || {}).Detachable || {}).MaximumHeight, true);
23416
+ Fit.Validation.ExpectNumber((((editorConfig || {}).Detachable || {}).MaximumHeight || {}).Value, true);
23417
+ Fit.Validation.ExpectStringValue((((editorConfig || {}).Detachable || {}).MaximumHeight || {}).Unit, true);
22938
23418
 
22939
23419
  if (editorConfig && editorConfig.Tags)
22940
23420
  {
@@ -22987,6 +23467,26 @@ Fit.Controls.Input = function(ctlId)
22987
23467
  designEditorConfig = Fit.Core.Clone(editorConfig); // Clone to prevent external code from making changes later
22988
23468
  }
22989
23469
 
23470
+ // Notice: Identical logic found in Value(..)!
23471
+ if (designEditorConfig !== null && designEditorConfig.Plugins && designEditorConfig.Plugins.Images && designEditorConfig.Plugins.Images.RevokeExternalBlobUrlsOnDispose === true)
23472
+ {
23473
+ // Keep track of image blobs added via Value(..) so we can dispose of them automatically.
23474
+ // When RevokeExternalBlobUrlsOnDispose is True it basically means that the Input control
23475
+ // is allowed (and expected) to take control over memory management for these blobs
23476
+ // based on the rule set in RevokeBlobUrlsOnDispose.
23477
+ // This code is also found in Value(..) since images might be added after editor has been created.
23478
+
23479
+ var blobUrls = Fit.String.ParseImageBlobUrls(me.Value());
23480
+
23481
+ Fit.Array.ForEach(blobUrls, function(blobUrl)
23482
+ {
23483
+ if (Fit.Array.Contains(imageBlobUrls, blobUrl) === false)
23484
+ {
23485
+ Fit.Array.Add(imageBlobUrls, blobUrl);
23486
+ }
23487
+ });
23488
+ }
23489
+
22990
23490
  if (me.MultiLine() === false)
22991
23491
  {
22992
23492
  me.MultiLine(true);
@@ -23327,6 +23827,11 @@ Fit.Controls.Input = function(ctlId)
23327
23827
 
23328
23828
  var focused = me.Focused();
23329
23829
 
23830
+ if (focused === true) // Make sure focus is preserved when editor is destroyed
23831
+ {
23832
+ me.GetDomElement().focus();
23833
+ }
23834
+
23330
23835
  if (Fit._internal.Controls.Input.ActiveEditorForDialog === me)
23331
23836
  {
23332
23837
  if (Fit._internal.Controls.Input.ActiveDialogForEditor !== null)
@@ -23377,31 +23882,25 @@ Fit.Controls.Input = function(ctlId)
23377
23882
  me._internal.Data("toolbar-sticky", null);
23378
23883
 
23379
23884
  revertToSingleLineIfNecessary();
23380
-
23381
- // Remove tabindex used to prevent control from losing focus when clicking toolbar buttons
23382
- Fit.Dom.Attribute(me.GetDomElement(), "tabindex", null);
23383
-
23384
23885
  if (focused === true)
23385
23886
  {
23386
- if (Fit.Browser.GetBrowser() === "MSIE" && Fit.Browser.GetVersion() < 9 && me.MultiLine() === false)
23387
- {
23388
- // On IE8 input.focus() does not work if input field is switched to a single line control
23389
- // above (MultiLine(false)). Wrapping the code in setTimeout(..) solves the problem.
23390
-
23391
- setTimeout(function()
23392
- {
23393
- if (me === null)
23394
- return; // Control was disposed
23887
+ // On IE8 input.focus() does not work if input field is switched to a traditional input field,
23888
+ // or if input field is hidden/invisible. It's just not reliable and not worth it. Remove focus
23889
+ // from the control in IE8 when DesignMode is disabled and preserve focus in every other browser.
23395
23890
 
23396
- input.focus();
23397
- }, 0);
23891
+ if (isIe8 === false)
23892
+ {
23893
+ input.focus();
23398
23894
  }
23399
23895
  else
23400
23896
  {
23401
- input.focus();
23897
+ me.GetDomElement().blur(); // Control container was given focus further up - this will fire OnBlur as expected
23402
23898
  }
23403
23899
  }
23404
23900
 
23901
+ // Remove tabindex used to prevent control from losing focus when clicking toolbar buttons
23902
+ Fit.Dom.Attribute(me.GetDomElement(), "tabindex", null);
23903
+
23405
23904
  repaint();
23406
23905
  }
23407
23906
  }
@@ -23435,6 +23934,17 @@ Fit.Controls.Input = function(ctlId)
23435
23934
  return debounceOnChangeTimeout;
23436
23935
  }
23437
23936
 
23937
+ // ============================================
23938
+ // Protected
23939
+ // ============================================
23940
+
23941
+ this._internal = (this._internal ? this._internal : {});
23942
+
23943
+ this._internal.DesignModeEnabledAndReady = function()
23944
+ {
23945
+ return designModeEnabledAndReady();
23946
+ }
23947
+
23438
23948
  // ============================================
23439
23949
  // Private
23440
23950
  // ============================================
@@ -23497,8 +24007,8 @@ Fit.Controls.Input = function(ctlId)
23497
24007
  }
23498
24008
 
23499
24009
  var langSupport = ["da", "de", "en"];
23500
- var locale = Fit.Internationalization.Locale().length === 2 ? Fit.Internationalization.Locale() : Fit.Internationalization.Locale().substring(0, 2);
23501
- var lang = Fit.Array.Contains(langSupport, locale) === true ? locale : "en";
24010
+ var localeCode = Fit.Internationalization.Locale().length === 2 ? Fit.Internationalization.Locale() : Fit.Internationalization.Locale().substring(0, 2);
24011
+ var lang = Fit.Array.Contains(langSupport, localeCode) === true ? localeCode : "en";
23502
24012
  var plugins = [];
23503
24013
  var toolbar = [];
23504
24014
  var mentions = [];
@@ -23512,7 +24022,7 @@ Fit.Controls.Input = function(ctlId)
23512
24022
  Fit.Array.Add(plugins, "emoji");
23513
24023
  }
23514
24024
 
23515
- if ((config.Plugins && config.Plugins.Images && config.Plugins.Images.Enabled === true) || (config.Toolbar && config.Toolbar.Images === true))
24025
+ if (designModeEnableImagePlugin() === true)
23516
24026
  {
23517
24027
  if (config.Toolbar && config.Toolbar.Images === true)
23518
24028
  {
@@ -23522,6 +24032,8 @@ Fit.Controls.Input = function(ctlId)
23522
24032
  plugins = Fit.Array.Merge(plugins, ["base64imagepaste", "dragresize"]);
23523
24033
  }
23524
24034
 
24035
+ Fit.Array.Add(plugins, "custombuttons");
24036
+
23525
24037
  // Add toolbar buttons
23526
24038
 
23527
24039
  if (!config.Toolbar || config.Toolbar.Formatting !== false)
@@ -23582,6 +24094,43 @@ Fit.Controls.Input = function(ctlId)
23582
24094
  items: insert
23583
24095
  });
23584
24096
  }
24097
+
24098
+ var customButtons = [];
24099
+ var customToolbarGroups = [];
24100
+
24101
+ if (config.Toolbar.Detach === true)
24102
+ {
24103
+ Fit.Array.Add(customButtons,
24104
+ {
24105
+ Label: locale.Detach,
24106
+ Command: "Detach",
24107
+ Icon: Fit.GetUrl() + "/Controls/Input/" + (window.devicePixelRatio === 2 ? "maximize-highres.png" : "maximize.png"),
24108
+ Callback: function(args)
24109
+ {
24110
+ //console.log("Command " + args.Command.name + " executed", args);
24111
+ openDetachedDesignEditor();
24112
+ }
24113
+ });
24114
+
24115
+ /*Fit.Array.Add(customButtons,
24116
+ {
24117
+ Label: "Testing 1-2-3",
24118
+ Command: "TestButton",
24119
+ Icon: "/files/images/Bird.png",
24120
+ Callback: function(args)
24121
+ {
24122
+ alert("Hello world");
24123
+ }
24124
+ });*/
24125
+
24126
+ Fit.Array.Add(customToolbarGroups,
24127
+ {
24128
+ name: "DetachableEditor",
24129
+ items: ["Detach"/*, "TestButton"*/]
24130
+ });
24131
+ }
24132
+
24133
+ toolbar = Fit.Array.Merge(toolbar, customToolbarGroups);
23585
24134
  }
23586
24135
 
23587
24136
  // Configure tags/mentions plugin
@@ -23770,11 +24319,11 @@ Fit.Controls.Input = function(ctlId)
23770
24319
 
23771
24320
  if (alternativeItem !== null)
23772
24321
  {
23773
- return '<a data-tag-type="' + (alternativeItem.Type || trigger.Marker) + '" data-tag-id="' + (alternativeItem.Value || item.Value) + '"' + (alternativeItem.Data || item.Data ? ' data-tag-data="' + (alternativeItem.Data || item.Data) + '"' : '') + (alternativeItem.Url || item.Url ? ' href="' + (alternativeItem.Url || item.Url) + '"' : 'href=""') + '>' + (alternativeItem.Title || (trigger.Marker + item.Title)) + '</a>';
24322
+ return '<a data-tag-type="' + (alternativeItem.Type || trigger.Marker) + '" data-tag-id="' + (alternativeItem.Value || item.Value) + '"' + (alternativeItem.Data || item.Data ? ' data-tag-data="' + (alternativeItem.Data || item.Data) + '"' : '') + (alternativeItem.Context || item.Context ? ' data-tag-context="' + (alternativeItem.Context || item.Context) + '"' : '') + (alternativeItem.Url || item.Url ? ' href="' + (alternativeItem.Url || item.Url) + '"' : 'href=""') + '>' + (alternativeItem.Title || (trigger.Marker + item.Title)) + '</a>';
23774
24323
  }
23775
24324
  else
23776
24325
  {
23777
- return '<a data-tag-type="' + trigger.Marker + '" data-tag-id="' + item.Value + '"' + (item.Data ? ' data-tag-data="' + item.Data + '"' : '') + (item.Url ? ' href="' + item.Url + '"' : 'href=""') + '>' + trigger.Marker + item.Title + '</a>';
24326
+ return '<a data-tag-type="' + trigger.Marker + '" data-tag-id="' + item.Value + '"' + (item.Data ? ' data-tag-data="' + item.Data + '"' : '') + (item.Context ? ' data-tag-context="' + item.Context + '"' : '') + (item.Url ? ' href="' + item.Url + '"' : 'href=""') + '>' + trigger.Marker + item.Title + '</a>';
23778
24327
  }
23779
24328
  }
23780
24329
  };
@@ -23785,7 +24334,7 @@ Fit.Controls.Input = function(ctlId)
23785
24334
  mention.feed = Fit.Core.CreateDebouncer(mention.feed, trigger.DebounceQuery || 300).Invoke;
23786
24335
  }
23787
24336
 
23788
- Fit.Array.Add(mentions, mention)
24337
+ Fit.Array.Add(mentions, mention);
23789
24338
  });
23790
24339
  }
23791
24340
 
@@ -23830,7 +24379,7 @@ Fit.Controls.Input = function(ctlId)
23830
24379
  toolbarLocation: designEditorConfig !== null && designEditorConfig.Toolbar && designEditorConfig.Toolbar.Position === "Bottom" ? "bottom" : "top",
23831
24380
  uiColor: Fit._internal.Controls.Input.Editor.Skin === "moono-lisa" || Fit._internal.Controls.Input.Editor.Skin === null ? "#FFFFFF" : undefined,
23832
24381
  //allowedContent: true, // http://docs.ckeditor.com/#!/guide/dev_allowed_content_rules and http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-allowedContent
23833
- extraAllowedContent: "a[data-tag-type,data-tag-id,data-tag-data]", // https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-extraAllowedContent
24382
+ extraAllowedContent: "a[data-tag-type,data-tag-id,data-tag-data,data-tag-context]", // https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-extraAllowedContent
23834
24383
  language: lang,
23835
24384
  disableNativeSpellChecker: me.CheckSpelling() === false,
23836
24385
  readOnly: me.Enabled() === false,
@@ -23855,6 +24404,7 @@ Fit.Controls.Input = function(ctlId)
23855
24404
  resize_dir: resizable === Fit.Controls.InputResizing.Enabled ? "both" : resizable === Fit.Controls.InputResizing.Vertical ? "vertical" : resizable === Fit.Controls.InputResizing.Horizontal ? "horizontal" : "none", // Specific to resize plugin (horizontal | vertical | both - https://ckeditor.com/docs/ckeditor4/latest/features/resize.html)
23856
24405
  toolbar: toolbar,
23857
24406
  removeButtons: "", // Set to empty string to prevent CKEditor from removing buttons such as Underline
24407
+ customButtons: customButtons,
23858
24408
  mentions: mentions,
23859
24409
  emoji_minChars: 9999, // Impossible requirement to number of search characters to "disable" emoji auto complete menu - we cannot make it work properly with light dismissable panels/callouts since we have no event available for registering the data-disable-light-dismiss="true" attribute, and it's not very useful in any case
23860
24410
  on:
@@ -23953,17 +24503,21 @@ Fit.Controls.Input = function(ctlId)
23953
24503
  if (Fit.Dom.GetComputedStyle(toolbarContainer, "position") === "sticky") // False on non-supported browsers as position:sticky is applied via the @supports CSS rule
23954
24504
  {
23955
24505
  var scrollParent = Fit.Dom.GetScrollParent(me.GetDomElement());
23956
- var toolbarPosition = me._internal.Data("toolbar-position"); // top | bottom
23957
24506
 
23958
- if (toolbarPosition === "top")
23959
- {
23960
- var paddingOffset = Fit.Dom.GetComputedStyle(scrollParent, "padding-top"); // E.g. "28px"
23961
- toolbarContainer.style.top = paddingOffset !== "0px" ? "-" + paddingOffset : "";
23962
- }
23963
- else
24507
+ if (scrollParent !== null) // In case editor is hosted in a container with position:fixed with overflow:hidden, in which case Fit.Dom.GetScrollParent(..) returns null
23964
24508
  {
23965
- var paddingOffset = Fit.Dom.GetComputedStyle(scrollParent, "padding-bottom"); // E.g. "28px"
23966
- toolbarContainer.style.bottom = paddingOffset !== "0px" ? "-" + paddingOffset : "";
24509
+ var toolbarPosition = me._internal.Data("toolbar-position"); // top | bottom
24510
+
24511
+ if (toolbarPosition === "top")
24512
+ {
24513
+ var paddingOffset = Fit.Dom.GetComputedStyle(scrollParent, "padding-top"); // E.g. "28px"
24514
+ toolbarContainer.style.top = paddingOffset !== "0px" ? "-" + paddingOffset : "";
24515
+ }
24516
+ else
24517
+ {
24518
+ var paddingOffset = Fit.Dom.GetComputedStyle(scrollParent, "padding-bottom"); // E.g. "28px"
24519
+ toolbarContainer.style.bottom = paddingOffset !== "0px" ? "-" + paddingOffset : "";
24520
+ }
23967
24521
  }
23968
24522
  }
23969
24523
  }
@@ -24127,7 +24681,7 @@ Fit.Controls.Input = function(ctlId)
24127
24681
  // place focus at the end of the editor as expected.
24128
24682
  if (me.Focused() === true)
24129
24683
  {
24130
- designEditor.focus();
24684
+ designEditor.focus(); // Won't work on iOS as assigning focus must be the result of a direct user interaction, which this is not since it is postponed using setTimeout(..) and async. loading of the editor
24131
24685
  }
24132
24686
  }, 0);
24133
24687
  },
@@ -24193,11 +24747,18 @@ Fit.Controls.Input = function(ctlId)
24193
24747
 
24194
24748
  if (elm.tagName === "A" && Fit.Dom.Data(elm, "tag-id") !== null)
24195
24749
  {
24196
- designEditorSuppressPaste = true;
24197
- setTimeout(function() // Postpone - otherwise we won't be able to temporarily disable some of the buttons (https://jsfiddle.net/ymv56znq/14/)
24750
+ // Notice that selectionChange handler is invoked while editor is loading if control was given initial focus.
24751
+ // But at this point the toolbar buttons are not yet available to be disabled, so disableDesignEditorButtons()
24752
+ // won't work. However, as soon as the editor is done loading, focus is re-assigned to the editable area
24753
+ // which will trigger selectionChange handler once again, at which point designModeEnabledAndReady() returns true.
24754
+ if (designModeEnabledAndReady() === true)
24198
24755
  {
24199
- disableDesignEditorButtons();
24200
- }, 0);
24756
+ designEditorSuppressPaste = true;
24757
+ setTimeout(function() // Postpone - otherwise we won't be able to temporarily disable some of the buttons (https://jsfiddle.net/ymv56znq/14/)
24758
+ {
24759
+ disableDesignEditorButtons();
24760
+ }, 0);
24761
+ }
24201
24762
  }
24202
24763
  else
24203
24764
  {
@@ -24526,6 +25087,258 @@ Fit.Controls.Input = function(ctlId)
24526
25087
  }
24527
25088
  }
24528
25089
 
25090
+ function openDetachedDesignEditor()
25091
+ {
25092
+ me._internal.FocusStateLocked(true);
25093
+
25094
+ var deConfig = null;
25095
+
25096
+ var updateDetachedConfiguration = function()
25097
+ {
25098
+ deConfig = Fit.Core.Clone(designEditorConfig || {});
25099
+
25100
+ // Configure detached editor like original, but with a few required changes.
25101
+ // We need to make sure image blobs are handled properly, that detached editor
25102
+ // cannot created another detached editor, that the toolbar is initially visible
25103
+ // at the top of the dialog, and that auto grow is disabled.
25104
+
25105
+ if (designModeEnableImagePlugin() === true)
25106
+ {
25107
+ deConfig = Fit.Core.Merge(deConfig, // Override image plugin configuration
25108
+ {
25109
+ Plugins:
25110
+ {
25111
+ Images:
25112
+ {
25113
+ Enabled: true,
25114
+ EmbedType: deConfig.Plugins && deConfig.Plugins.Images && deConfig.Plugins.Images.EmbedType,
25115
+
25116
+ // Image blobs added in detached editor must always be disposed if no longer referenced.
25117
+ // Furthermore we make sure image blobs originating from main editor are never disposed.
25118
+ // When detached editor is closed, images transfered from detached editor to main editor
25119
+ // are added to the main editor's index over image blobs so that the main editor becomes
25120
+ // responsible for the memory management of these.
25121
+ // If detached editor is closed without transfering changes (canceled), all images found
25122
+ // in the detached editor, which are not referenced in the main editor, are disposed.
25123
+ // See OnClick handlers for OK and Cancel buttons.
25124
+
25125
+ RevokeBlobUrlsOnDispose: "UnreferencedOnly", // Make dialog editor preserve newly added (and still referenced) image blobs when disposed
25126
+ RevokeExternalBlobUrlsOnDispose: false // Make dialog editor preserve images blobs initially added from main editor
25127
+ }
25128
+ }
25129
+ });
25130
+ }
25131
+
25132
+ deConfig.Toolbar = deConfig.Toolbar || {};
25133
+ deConfig.Toolbar.Detach = false;
25134
+ deConfig.Toolbar.Position = "Top";
25135
+ deConfig.Toolbar.Sticky = false;
25136
+ deConfig.Toolbar.HideInitially = false;
25137
+
25138
+ delete deConfig.AutoGrow;
25139
+ };
25140
+
25141
+ updateDetachedConfiguration();
25142
+
25143
+ // Create dialog
25144
+
25145
+ var dia = new Fit.Controls.Dialog();
25146
+ Fit.Dom.AddClass(dia.GetDomElement(), "FitUiControlInputDetached");
25147
+
25148
+ // Create editor
25149
+
25150
+ var de = new Fit.Controls.Input();
25151
+
25152
+ var setDetachedEditorSettings = function()
25153
+ {
25154
+ de.Width(100, "%");
25155
+ de.Height(-1);
25156
+ de.CheckSpelling(me.CheckSpelling());
25157
+ de.DesignMode(true, deConfig);
25158
+ };
25159
+
25160
+ setDetachedEditorSettings();
25161
+ de.Value(me.Value());
25162
+
25163
+ // Make editor adjust to the dimensions of the dialog
25164
+
25165
+ // Adjust editor size to fit dialog using a timer.
25166
+ // Alternatively expose an OnResize event on Fit.Controls.Dialog, use it in
25167
+ // combination with window.onresize, and adjust editor when these events are triggered.
25168
+ // However, the process of monitoring the dimensions using a timer is practically free,
25169
+ // so even though the timer interupts browser events such as onmousemove, onscroll, etc.,
25170
+ // it creates no lack at all.
25171
+ var height = -1
25172
+ var dimMonitorId = setInterval(function()
25173
+ {
25174
+ if (height === -1 && de._internal.DesignModeEnabledAndReady() === false)
25175
+ {
25176
+ return;
25177
+ }
25178
+
25179
+ var newHeight = dia.GetDomElement().offsetHeight;
25180
+
25181
+ if (newHeight !== height)
25182
+ {
25183
+ height = newHeight
25184
+ de.Height(Fit.Dom.GetInnerDimensions(dia.GetContentDomElement()).Height);
25185
+ }
25186
+ }, 250);
25187
+
25188
+ // Configure dialog
25189
+
25190
+ var setDialogSettings = function(update)
25191
+ {
25192
+ var detachConfig = deConfig.Detachable || {};
25193
+ detachConfig = Fit.Core.Merge(detachConfig, // Apply default values - existing properties are preserved (Fit.Core.MergeOverwriteBehaviour.Never)
25194
+ {
25195
+ Title: "",
25196
+ Maximizable: true,
25197
+ Maximized: false,
25198
+ Draggable: true,
25199
+ Resizable: true,
25200
+ Width: detachConfig.Width ? detachConfig.Width : { Value: 850, Unit: "px" },
25201
+ MinimumWidth: detachConfig.MinimumWidth ? detachConfig.MinimumWidth : { Value: 20, Unit: "em" },
25202
+ MaximumWidth: detachConfig.MaximumWidth ? detachConfig.MaximumWidth : { Value: 100, Unit: "%" },
25203
+ Height: detachConfig.Height ? detachConfig.Height : { Value: 550, Unit: "px" },
25204
+ MinimumHeight: detachConfig.MinimumHeight ? detachConfig.MinimumHeight : { Value: 12, Unit: "em" },
25205
+ MaximumHeight: detachConfig.MaximumHeight ? detachConfig.MaximumHeight : { Value: 100, Unit: "%" }
25206
+ }, Fit.Core.MergeOverwriteBehaviour.Never);
25207
+
25208
+ var updateDimensions = (!detachConfig.Resizable || update !== true);
25209
+
25210
+ dia.Title(detachConfig.Title);
25211
+ dia.Modal(true);
25212
+ dia.Draggable(detachConfig.Draggable);
25213
+ dia.Resizable(detachConfig.Resizable);
25214
+ dia.Maximizable(detachConfig.Maximizable);
25215
+ dia.Maximized(detachConfig.Maximized);
25216
+ updateDimensions === true && dia.Width(detachConfig.Width.Value, detachConfig.Width.Unit || "px");
25217
+ updateDimensions === true && dia.Height(detachConfig.Height.Value, detachConfig.Height.Unit || "px");
25218
+ dia.MinimumWidth(detachConfig.MinimumWidth.Value, detachConfig.MinimumWidth.Unit || "px");
25219
+ dia.MinimumHeight(detachConfig.MinimumHeight.Value, detachConfig.MinimumHeight.Unit || "px");
25220
+ dia.MaximumWidth(detachConfig.MaximumWidth.Value, detachConfig.MaximumWidth.Unit || "px");
25221
+ dia.MaximumHeight(detachConfig.MaximumHeight.Value, detachConfig.MaximumHeight.Unit || "px");
25222
+ };
25223
+
25224
+ setDialogSettings();
25225
+
25226
+ // Localization support
25227
+
25228
+ var localizeDetachedEditor = function()
25229
+ {
25230
+ // Editor itself is already localized, so we just need to
25231
+ // localize the dialog. The locale variable will already have
25232
+ // been updated by the OnLocaleChanged handler registered in init().
25233
+
25234
+ cmdOk.Title(locale.Ok);
25235
+ cmdCancel.Title(locale.Cancel);
25236
+ };
25237
+ Fit.Internationalization.OnLocaleChanged(localizeDetachedEditor);
25238
+
25239
+ // Expose detached editor API
25240
+
25241
+ designEditorDetached =
25242
+ {
25243
+ GetValue: function()
25244
+ {
25245
+ return de.Value();
25246
+ },
25247
+
25248
+ Reload: function()
25249
+ {
25250
+ // Update configuration
25251
+ updateDetachedConfiguration();
25252
+
25253
+ // Update editor
25254
+ setDetachedEditorSettings();
25255
+
25256
+ // Update dialog
25257
+ setDialogSettings(true);
25258
+
25259
+ // Make dimension monitor ensure proper editor height (dialog height might have been changed)
25260
+ height = -1;
25261
+ },
25262
+
25263
+ Dispose: function()
25264
+ {
25265
+ clearInterval(dimMonitorId);
25266
+ Fit.Internationalization.RemoveOnLocaleChanged(localizeDetachedEditor);
25267
+ de.Dispose();
25268
+ dia.Dispose(); // Will also dispose associated buttons
25269
+ designEditorDetached = null;
25270
+ }
25271
+ };
25272
+
25273
+ var cmdOk = new Fit.Controls.Button();
25274
+ cmdOk.Title(locale.Ok);
25275
+ cmdOk.Icon("check");
25276
+ cmdOk.Type(Fit.Controls.ButtonType.Success);
25277
+ cmdOk.OnClick(function(sender)
25278
+ {
25279
+ var referencedBlobUrls = Fit.String.ParseImageBlobUrls(de.Value());
25280
+ Fit.Array.ForEach(referencedBlobUrls, function(blobUrl)
25281
+ {
25282
+ if (Fit.Array.Contains(imageBlobUrls, blobUrl) === false)
25283
+ {
25284
+ Fit.Array.Add(imageBlobUrls, blobUrl);
25285
+ }
25286
+ });
25287
+
25288
+ me.Value(de.Value());
25289
+
25290
+ designEditorDetached.Dispose();
25291
+
25292
+ me.Focused(true);
25293
+ me._internal.FocusStateLocked(false);
25294
+ });
25295
+ dia.AddButton(cmdOk);
25296
+
25297
+ var cmdCancel = new Fit.Controls.Button();
25298
+ cmdCancel.Title(locale.Cancel);
25299
+ cmdCancel.Icon("ban");
25300
+ cmdCancel.Type(Fit.Controls.ButtonType.Danger);
25301
+ cmdCancel.OnClick(function(sender)
25302
+ {
25303
+ var closeDialog = function()
25304
+ {
25305
+ var referencedBlobUrls = Fit.String.ParseImageBlobUrls(de.Value());
25306
+ Fit.Array.ForEach(referencedBlobUrls, function(blobUrl)
25307
+ {
25308
+ if (Fit.Array.Contains(imageBlobUrls, blobUrl) === false) // Only remove images added in dialog editor
25309
+ {
25310
+ URL.revokeObjectURL(blobUrl);
25311
+ }
25312
+ });
25313
+
25314
+ designEditorDetached.Dispose();
25315
+
25316
+ me.Focused(true);
25317
+ me._internal.FocusStateLocked(false);
25318
+ };
25319
+
25320
+ if (de.IsDirty() === true)
25321
+ {
25322
+ Fit.Controls.Dialog.Confirm(locale.CancelConfirmTitle + "<br><br>" + locale.CancelConfirmDescription, function(res)
25323
+ {
25324
+ if (res === true)
25325
+ {
25326
+ closeDialog();
25327
+ }
25328
+ });
25329
+ }
25330
+ else
25331
+ {
25332
+ closeDialog();
25333
+ }
25334
+ });
25335
+ dia.AddButton(cmdCancel);
25336
+
25337
+ dia.Open();
25338
+ de.Render(dia.GetContentDomElement());
25339
+ de.Focused(true);
25340
+ }
25341
+
24529
25342
  function revertToSingleLineIfNecessary()
24530
25343
  {
24531
25344
  if (wasAutoChangedToMultiLineMode === true && me.Maximizable() === false && me.Resizable() === Fit.Controls.InputResizing.Disabled && me.DesignMode() === false)
@@ -24589,10 +25402,25 @@ Fit.Controls.Input = function(ctlId)
24589
25402
  var height = me.Height();
24590
25403
  var currentWasAutoChangedToMultiLineMode = wasAutoChangedToMultiLineMode; // DesignMode(false) will result in wasAutoChangedToMultiLineMode being set to false if DesignMode(true) changed the control to MultiLine mode
24591
25404
 
25405
+ // Prevent detached editor from being closed when reloading, e.g. if CheckSpelling is changed.
25406
+ // Editor will also be reloaded if a different editor configuration is passed to DesignMode(true, updatedConfig).
25407
+ var detachedEditor = null;
25408
+ if (designEditorDetached !== null)
25409
+ {
25410
+ detachedEditor = designEditorDetached;
25411
+ designEditorDetached = null; // Prevent me.DesignMode(false), which in turn calls destroyDesignEditorInstance(), from closing detached editor dialog
25412
+ }
25413
+
24592
25414
  me.DesignMode(false);
24593
25415
  me.DesignMode(true, reloadConfig || designEditorReloadConfig || undefined); // Use reloadConfig if set (and if reload was not postponed) or use designEditorReloadConfig if reload was postponed with updated editor config
24594
25416
  designEditorReloadConfig = null;
24595
25417
 
25418
+ if (detachedEditor !== null)
25419
+ {
25420
+ designEditorDetached = detachedEditor;
25421
+ designEditorDetached.Reload(); // Reload detached editor to reflect any changes made to configuration
25422
+ }
25423
+
24596
25424
  me.Height(height.Value, height.Unit);
24597
25425
  wasAutoChangedToMultiLineMode = currentWasAutoChangedToMultiLineMode;
24598
25426
  }
@@ -24611,9 +25439,14 @@ Fit.Controls.Input = function(ctlId)
24611
25439
 
24612
25440
  designEditor.destroy();
24613
25441
 
25442
+ if (designEditorDetached !== null)
25443
+ {
25444
+ designEditorDetached.Dispose();
25445
+ }
25446
+
24614
25447
  designEditor = null;
24615
25448
  designEditorDom = null;
24616
- designEditorDirty = false;
25449
+ //designEditorDirty = false; // Do NOT reset this! We need to preserve dirty state in case DesignMode is reloaded!
24617
25450
  designEditorDirtyPending = false;
24618
25451
  //designEditorConfig = null; // Do NOT nullify this! We need it, in case DesignMode is toggled!
24619
25452
  //designEditorReloadConfig = null; // Do NOT nullify this! We need it, in case DesignMode is reloaded!
@@ -24623,6 +25456,7 @@ Fit.Controls.Input = function(ctlId)
24623
25456
  designEditorMustReloadWhenReady = false;
24624
25457
  designEditorMustDisposeWhenReady = false;
24625
25458
  designEditorActiveToolbarPanel = null;
25459
+ designEditorDetached = null;
24626
25460
 
24627
25461
  if (designEditorUpdateSizeDebouncer !== -1)
24628
25462
  {
@@ -24654,12 +25488,47 @@ Fit.Controls.Input = function(ctlId)
24654
25488
  return designEditorDom !== null; // Editor is fully loaded when editor DOM is made available
24655
25489
  }
24656
25490
 
25491
+ function designModeEnableImagePlugin()
25492
+ {
25493
+ var config = designEditorConfig || {};
25494
+ var enableImagePlugin = (config.Plugins && config.Plugins.Images && config.Plugins.Images.Enabled === true) || (config.Toolbar && config.Toolbar.Images === true) || false;
25495
+
25496
+ // Force enable image support if images are contained in value - otherwise editor will remove them
25497
+ if (enableImagePlugin === false && designEditorDetached !== null && designEditorDetached.GetValue().indexOf("<img ") > -1)
25498
+ {
25499
+ enableImagePlugin = true;
25500
+ }
25501
+ if (enableImagePlugin === false && me.Value().indexOf("<img ") > -1)
25502
+ {
25503
+ enableImagePlugin = true;
25504
+ }
25505
+
25506
+ return enableImagePlugin;
25507
+ }
25508
+
24657
25509
  function localize()
24658
25510
  {
25511
+ locale = Fit.Internationalization.GetLocale(me);
25512
+
24659
25513
  if (me.DesignMode() === true)
24660
25514
  {
25515
+ // Prevent reloadEditor() from reloading detached editor.
25516
+ // It will automatically reload when locale is changed.
25517
+ // Without this guard the editor would reload twice.
25518
+ var detachedEditor = null;
25519
+ if (designEditorDetached !== null)
25520
+ {
25521
+ detachedEditor = designEditorDetached;
25522
+ designEditorDetached = null; // Prevent reloadEditor() from reloading detached editor
25523
+ }
25524
+
24661
25525
  // Re-create editor with new language
24662
25526
  reloadEditor();
25527
+
25528
+ if (detachedEditor !== null)
25529
+ {
25530
+ designEditorDetached = detachedEditor;
25531
+ }
24663
25532
  }
24664
25533
  }
24665
25534
 
@@ -24776,6 +25645,40 @@ Fit.Controls.InputResizing = // Enums must exist runtime
24776
25645
  Horizontal: "Horizontal",
24777
25646
  Vertical: "Vertical"
24778
25647
  };
25648
+ Fit.Internationalization.AddLocalization(Fit.Controls.Input,
25649
+ {
25650
+ // Primary locales must be registered before country specific overrides.
25651
+ // Example order: en, en_GB, de, de_AT, etc.
25652
+ // All locales inherit from en. All country specific overrides inherit
25653
+ // from their general locale (e.g. de_AT inherits from de).
25654
+ // English (en) MUST be defined, and be defined first!
25655
+
25656
+ "en":
25657
+ {
25658
+ "Detach": "Open in dialog",
25659
+ "Ok": "OK",
25660
+ "Cancel": "Cancel",
25661
+ "CancelConfirmTitle": "Discard changes?",
25662
+ "CancelConfirmDescription": "Are you sure you want to discard the changes?"
25663
+ },
25664
+ "da":
25665
+ {
25666
+ "Detach": "Åben i dialog",
25667
+ "Ok": "OK",
25668
+ "Cancel": "Annullér",
25669
+ "CancelConfirmTitle": "Annullér ændringer?",
25670
+ "CancelConfirmDescription": "Er du sikker på at du vil annullere ændringerne?"
25671
+ },
25672
+ "de":
25673
+ {
25674
+ "Detach": "Im Dialog öffnen",
25675
+ "Ok": "OK",
25676
+ "Cancel": "Abbrechen",
25677
+ "CancelConfirmTitle": "Änderungen verwerfen?",
25678
+ "CancelConfirmDescription": "Möchten Sie die Änderungen wirklich verwerfen?"
25679
+ }
25680
+ });
25681
+
24779
25682
  /// <container name="Fit.Controls.ListView" extends="Fit.Controls.PickerBase;Fit.Controls.Component">
24780
25683
  /// Picker control which allows for entries
24781
25684
  /// to be selected in the DropDown control.
@@ -26094,7 +26997,7 @@ Fit.Controls.SoftLog = function(controlId)
26094
26997
 
26095
26998
  /// <function container="Fit.Controls.SoftLog" name="MaxEntries" access="public" returns="integer">
26096
26999
  /// <description> Get/set number of log entries preserved </description>
26097
- /// <param name="val" type="boolean" default="undefined"> If defined, changes number of log entries preserved </param>
27000
+ /// <param name="val" type="integer" default="undefined"> If defined, changes number of log entries preserved </param>
26098
27001
  /// </function>
26099
27002
  this.MaxEntries = function(val)
26100
27003
  {