fit-ui 2.9.1 → 2.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/Fit.UI.js CHANGED
@@ -682,7 +682,7 @@ Fit._internal =
682
682
  {
683
683
  Core:
684
684
  {
685
- VersionInfo: { Major: 2, Minor: 9, Patch: 1 } // 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: 2 } // Do NOT modify format - version numbers are programmatically changed when releasing new versions - MUST be on a separate line!
686
686
  }
687
687
  };
688
688
 
@@ -4307,12 +4307,6 @@ Fit.Controls.ControlBase = function(controlId)
4307
4307
  // focused state, and let the specialized control handle invocation of focus events when needed, and hand
4308
4308
  // back control to ControlBase when the modal dialog closes.
4309
4309
 
4310
- // Notice regarding Focused(): One could argue that Focused() should return True if focus state is locked,
4311
- // if control was focused when lock was enabled, and if OnBlur has not been fired. But that means that two
4312
- // controls could return True from Focused() which is just wrong, and the Focused() state would contradict
4313
- // what is returned from Fit.Dom.GetFocused() or document.activeElement, which could potentially lead to
4314
- // incorrect behaviour. So Focused() must give us the truth, even when focus state is locked.
4315
-
4316
4310
  if (Fit.Validation.IsSet(value) === true)
4317
4311
  {
4318
4312
  if (value !== focusStateLocked)
@@ -4320,7 +4314,10 @@ Fit.Controls.ControlBase = function(controlId)
4320
4314
  focusStateLocked = value;
4321
4315
 
4322
4316
  // Make sure ControlBase can handle focus in/out properly when focus state is unlocked again
4323
- hasFocus = Fit.Dom.Contained(me.GetDomElement(), Fit.Dom.GetFocused()) === true;
4317
+
4318
+ var meElement = me.GetDomElement();
4319
+ var focusElement = Fit.Dom.GetFocused();
4320
+ hasFocus = meElement === focusElement || Fit.Dom.Contained(meElement, focusElement) === true;
4324
4321
  }
4325
4322
  }
4326
4323
 
@@ -6778,6 +6775,7 @@ Fit.DragDrop.Draggable = function(domElm, domTriggerElm)
6778
6775
  var posState = null; // { position: "", left: "", top: "" };
6779
6776
  var trgElm = (domTriggerElm ? domTriggerElm : null);
6780
6777
  var bringToFrontOnActivation = false;
6778
+ var returnFocus = false;
6781
6779
 
6782
6780
  var onDragStart = null;
6783
6781
  var onDragging = null;
@@ -6807,6 +6805,8 @@ Fit.DragDrop.Draggable = function(domElm, domTriggerElm)
6807
6805
 
6808
6806
  // Mouse down
6809
6807
 
6808
+ var focusedBeforeDrag = null;
6809
+
6810
6810
  mouseDownEventId = Fit.Events.AddHandler(((trgElm !== null) ? trgElm : elm), (Fit.Browser.IsTouchEnabled() === true ? "touchstart" : "mousedown"), function(e)
6811
6811
  {
6812
6812
  if (Fit.DragDrop.Draggable._internal.active !== null)
@@ -6822,6 +6822,8 @@ Fit.DragDrop.Draggable = function(domElm, domTriggerElm)
6822
6822
  }
6823
6823
  }
6824
6824
 
6825
+ focusedBeforeDrag = returnFocus === true ? Fit.Dom.GetFocused() : null;
6826
+
6825
6827
  Fit.Dom.AddClass(elm, "FitDragDropDragging");
6826
6828
 
6827
6829
  // Initial positioning (used by Reset())
@@ -6959,6 +6961,14 @@ Fit.DragDrop.Draggable = function(domElm, domTriggerElm)
6959
6961
  dropzoneState.OnDrop(dropzone, draggable);
6960
6962
  }
6961
6963
 
6964
+ // Restore focus
6965
+
6966
+ if (focusedBeforeDrag !== null)
6967
+ {
6968
+ focusedBeforeDrag.focus();
6969
+ focusedBeforeDrag = null;
6970
+ }
6971
+
6962
6972
  // Fire OnDragStop after OnDrop to make sure OnDragStop can act
6963
6973
  // depending on whether draggable was dropped on a dropzone or not.
6964
6974
 
@@ -6970,6 +6980,11 @@ Fit.DragDrop.Draggable = function(domElm, domTriggerElm)
6970
6980
  });
6971
6981
  }
6972
6982
 
6983
+ Fit.Browser.IsTouchEnabled() === true && Fit.Events.AddHandler(document, "touchcancel", function(e)
6984
+ {
6985
+ focusedBeforeDrag = null;
6986
+ });
6987
+
6973
6988
  // Mouse move
6974
6989
 
6975
6990
  if (Fit.DragDrop.Draggable._internal.mouseMoveRegistered === false)
@@ -7139,13 +7154,31 @@ Fit.DragDrop.Draggable = function(domElm, domTriggerElm)
7139
7154
  return bringToFrontOnActivation;
7140
7155
  }
7141
7156
 
7157
+ /// <function container="Fit.DragDrop.Draggable" name="ReturnFocus" access="public" returns="boolean">
7158
+ /// <description> Get/set flag indicating whether focus is returned/restored after drag operation </description>
7159
+ /// <param name="val" type="boolean" default="undefined">
7160
+ /// A value of True causes draggable to return focus to previously
7161
+ /// focused element when drag operation is completed - defaults to False
7162
+ /// </param>
7163
+ /// </function>
7164
+ this.ReturnFocus = function(val)
7165
+ {
7166
+ Fit.Validation.ExpectBoolean(val, true);
7167
+
7168
+ if (Fit.Validation.IsSet(val) === true)
7169
+ {
7170
+ returnFocus = val;
7171
+ }
7172
+
7173
+ return returnFocus;
7174
+ }
7175
+
7142
7176
  /// <function container="Fit.DragDrop.Draggable" name="BringToFront" access="public">
7143
7177
  /// <description> Bring draggable to front </description>
7144
7178
  /// </function>
7145
7179
  this.BringToFront = function()
7146
7180
  {
7147
- Fit.DragDrop.Draggable._internal.zIndex++;
7148
- elm.style.zIndex = Fit.DragDrop.Draggable._internal.zIndex;
7181
+ elm.style.zIndex = Fit.DragDrop.Draggable._internal.getNextZindex();
7149
7182
  }
7150
7183
 
7151
7184
  /// <function container="Fit.DragDrop.Draggable" name="Reset" access="public">
@@ -7197,7 +7230,7 @@ Fit.DragDrop.Draggable = function(domElm, domTriggerElm)
7197
7230
  Fit.DragDrop.Draggable._internal.active = null;
7198
7231
  }
7199
7232
 
7200
- me = elm = posState = trgElm = bringToFrontOnActivation = onDragStart = onDragging = onDragStop = activationEventId = mouseDownEventId = null;
7233
+ me = elm = posState = trgElm = bringToFrontOnActivation = returnFocus = onDragStart = onDragging = onDragStop = activationEventId = mouseDownEventId = null;
7201
7234
  }
7202
7235
 
7203
7236
  // Event handling
@@ -7248,7 +7281,12 @@ Fit.DragDrop.Draggable = function(domElm, domTriggerElm)
7248
7281
  Fit.DragDrop.Draggable._internal =
7249
7282
  {
7250
7283
  /* Shared */
7251
- zIndex : 10000,
7284
+ _zIndex : 10000,
7285
+ getNextZindex: function()
7286
+ {
7287
+ Fit.DragDrop.Draggable._internal._zIndex++;
7288
+ return Fit.DragDrop.Draggable._internal._zIndex + (Fit.Dom.Data(document.documentElement, "fitui-companion") === "fluent-ui" ? 2000000 : 0);
7289
+ },
7252
7290
  mouseUpRegistered : false,
7253
7291
  mouseMoveRegistered : false,
7254
7292
 
@@ -7994,7 +8032,7 @@ Fit.Events.GetModifierKeys = function()
7994
8032
 
7995
8033
  /// <container name="Fit.EventTypeDefs.PointerState">
7996
8034
  /// <description> Pointer state </description>
7997
- /// <member name="Buttons" type="{ Primary: boolean, Secondary: boolean }"> Pointer buttons currently activated </member>
8035
+ /// <member name="Buttons" type="{ Primary: boolean, Secondary: boolean, Touch: boolean, Target: DOMElement | null }"> Pointer buttons currently activated </member>
7998
8036
  /// <member name="Coordinates" type="{ ViewPort: Fit.TypeDefs.Position, Document: Fit.TypeDefs.Position }"> Pointer position within viewport and document, which might have been scrolled </member>
7999
8037
  /// </container>
8000
8038
 
@@ -11418,14 +11456,37 @@ Fit.Controls.Button = function(controlId)
11418
11456
  var label = null;
11419
11457
  var width = { Value: -1, Unit: "px" }; // Initial width - a value of -1 indicates that size adjusts to content
11420
11458
  var height = { Value: -1, Unit: "px" }; // Initial height - a value of -1 indicates that size adjusts to content
11459
+ var returnFocus = false;
11421
11460
  var onClickHandlers = [];
11422
11461
 
11423
11462
  function init()
11424
11463
  {
11425
- Fit.Events.AddHandler(element, "click", function(e)
11464
+ var invokeClick = true;
11465
+ var focusedBeforeClick = null;
11466
+
11467
+ Fit.Events.AddHandler(element, Fit.Browser.IsTouchEnabled() === true ? "touchstart" : "mousedown", function(e)
11426
11468
  {
11427
- if (me.Enabled() === true)
11469
+ invokeClick = true;
11470
+ focusedBeforeClick = returnFocus === true ? Fit.Dom.GetFocused() : null;
11471
+ });
11472
+ Fit.Browser.IsTouchEnabled() === true && Fit.Events.AddHandler(element, "touchmove", function(e)
11473
+ {
11474
+ invokeClick = false;
11475
+ });
11476
+ Fit.Events.AddHandler(element, Fit.Browser.IsTouchEnabled() === true ? "touchend" : "click", function(e)
11477
+ {
11478
+ if (invokeClick === true && me.Enabled() === true)
11428
11479
  me.Click();
11480
+
11481
+ if (focusedBeforeClick !== null)
11482
+ {
11483
+ focusedBeforeClick.focus();
11484
+ focusedBeforeClick = null;
11485
+ }
11486
+ });
11487
+ Fit.Browser.IsTouchEnabled() === true && Fit.Events.AddHandler(element, "touchcancel", function(e)
11488
+ {
11489
+ focusedBeforeClick = null;
11429
11490
  });
11430
11491
  Fit.Events.AddHandler(element, "keydown", function(e)
11431
11492
  {
@@ -11626,6 +11687,24 @@ Fit.Controls.Button = function(controlId)
11626
11687
  return height;
11627
11688
  }
11628
11689
 
11690
+ /// <function container="Fit.Controls.Button" name="ReturnFocus" access="public" returns="boolean">
11691
+ /// <description> Get/set flag indicating whether button returns focus after click </description>
11692
+ /// <param name="val" type="boolean" default="undefined">
11693
+ /// A value of True causes button to return focus to previously focused element after click - defaults to False
11694
+ /// </param>
11695
+ /// </function>
11696
+ this.ReturnFocus = function(val)
11697
+ {
11698
+ Fit.Validation.ExpectBoolean(val, true);
11699
+
11700
+ if (Fit.Validation.IsSet(val) === true)
11701
+ {
11702
+ returnFocus = val;
11703
+ }
11704
+
11705
+ return returnFocus;
11706
+ }
11707
+
11629
11708
  /// <function container="Fit.Controls.ButtonTypeDefs" name="ClickEventHandler">
11630
11709
  /// <description> OnClick event handler </description>
11631
11710
  /// <param name="sender" type="Fit.Controls.Button"> Instance of Button </param>
@@ -11656,7 +11735,7 @@ Fit.Controls.Button = function(controlId)
11656
11735
 
11657
11736
  this.Dispose = Fit.Core.CreateOverride(this.Dispose, function()
11658
11737
  {
11659
- me = id = element = wrapper = icon = label = width = height = onClickHandlers = null;
11738
+ me = id = element = wrapper = icon = label = width = height = returnFocus = onClickHandlers = null;
11660
11739
  base();
11661
11740
  });
11662
11741
 
@@ -15262,6 +15341,7 @@ Fit.Controls.Dialog = function(controlId)
15262
15341
  {
15263
15342
  cmdMaximize = new Fit.Controls.Button();
15264
15343
  cmdMaximize.Icon((me.Maximized() === false ? "expand" : "compress"));
15344
+ cmdMaximize.ReturnFocus(true);
15265
15345
  cmdMaximize.OnClick(function(sender)
15266
15346
  {
15267
15347
  if (me.Maximized() === false)
@@ -15366,7 +15446,8 @@ Fit.Controls.Dialog = function(controlId)
15366
15446
 
15367
15447
  draggable = new Fit.DragDrop.Draggable(me.GetDomElement(), titleText);
15368
15448
  draggable.BringToFrontOnActivation(true);
15369
- draggable.OnDragStart(function()
15449
+ draggable.ReturnFocus(true);
15450
+ draggable.OnDragStart(function(elm)
15370
15451
  {
15371
15452
  if (me.Maximized() === true)
15372
15453
  {
@@ -15638,7 +15719,7 @@ Fit.Controls.Dialog = function(controlId)
15638
15719
 
15639
15720
  this.Render = function(toElement) // Override Render() on Fit.Controls.Component
15640
15721
  {
15641
- Fit.Validation.ThrowError("Use Open function to open Dialog");
15722
+ me.Open(toElement);
15642
15723
  }
15643
15724
 
15644
15725
  this.Dispose = Fit.Core.CreateOverride(this.Dispose, function()
@@ -15791,6 +15872,7 @@ Fit.Controls.Dialog = function(controlId)
15791
15872
 
15792
15873
  var ev = Fit.Events.GetEvent(e);
15793
15874
 
15875
+ var focusedBeforeResize = Fit.Dom.GetFocused();
15794
15876
  var initPos = Fit.Events.GetPointerState().Coordinates.ViewPort;
15795
15877
  var initDim = { Width: me.GetDomElement().offsetWidth, Height: me.GetDomElement().offsetHeight };
15796
15878
 
@@ -15807,6 +15889,8 @@ Fit.Controls.Dialog = function(controlId)
15807
15889
  {
15808
15890
  Fit.Events.RemoveHandler(document, cancelHandler);
15809
15891
  }
15892
+
15893
+ focusedBeforeResize.focus();
15810
15894
  };
15811
15895
 
15812
15896
  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)
@@ -22092,7 +22176,9 @@ Fit.Controls.Input = function(ctlId)
22092
22176
  var designEditorMustDisposeWhenReady = false;
22093
22177
  var designEditorUpdateSizeDebouncer = -1;
22094
22178
  var designEditorActiveToolbarPanel = null; // { DomElement: HTMLElement, UnlockFocusStateIfEmojiPanelIsClosed: function, CloseEmojiPanel: function }
22095
- var designEditorDetached = null; // { GetValue: function, Reload: function, Dispose: function }
22179
+ var designEditorDetached = null; // { GetValue: function, Reload: function, Dispose: function, ..... }
22180
+ var designModeDialogModeContent = null;
22181
+ var designModeDialogMode = null; // { DomElement: HTMLElement, AutoOpen: boolean, ..... }
22096
22182
  //var htmlWrappedInParagraph = false;
22097
22183
  var wasAutoChangedToMultiLineMode = false; // Used to revert to single line if multi line was automatically enabled along with DesignMode(true), Maximizable(true), or Resizable(true)
22098
22184
  var minimizeHeight = -1;
@@ -22195,6 +22281,19 @@ Fit.Controls.Input = function(ctlId)
22195
22281
  {
22196
22282
  restoreHiddenToolbarInDesignEditor(); // Make toolbar appear if currently hidden
22197
22283
  updateDesignEditorPlaceholder(true); // Clear placeholder text
22284
+
22285
+ // Make sure control gains focus in case a link received focus when navigating backwards (SHIFT + TAB)
22286
+ if (false && designModeDialogMode !== null && Fit.Dom.GetFocused().tagName === "A")
22287
+ {
22288
+ //me.GetDomElement().focus();
22289
+ //var scrollPos = designModeDialogMode.DomElement.scrollTop;
22290
+ designModeDialogMode.DomElement.focus();
22291
+ //designModeDialogMode.DomElement.scrollTop = scrollPos;
22292
+ }
22293
+ // if (designModeDialogMode !== null && me.GetDomElement() === Fit.Dom.GetFocused())
22294
+ // {
22295
+ // designModeDialogMode.DomElement.focus();
22296
+ // }
22198
22297
  });
22199
22298
  me.OnBlur(function()
22200
22299
  {
@@ -22212,7 +22311,7 @@ Fit.Controls.Input = function(ctlId)
22212
22311
 
22213
22312
  try
22214
22313
  {
22215
- // We rely on the .buttons property to optimization resizing for textarea (MultiLine mode).
22314
+ // We rely on the .buttons property to optimize resizing for textarea (MultiLine mode).
22216
22315
  // The MouseEvent class might not be available on older browsers or might throw an exception when constructing.
22217
22316
  nativeResizableAvailable = window.MouseEvent && new MouseEvent("mousemove", {}).buttons !== undefined || false;
22218
22317
  }
@@ -22223,6 +22322,30 @@ Fit.Controls.Input = function(ctlId)
22223
22322
  // Public - overrides
22224
22323
  // ============================================
22225
22324
 
22325
+ this.Visible = Fit.Core.CreateOverride(this.Visible, function(val)
22326
+ {
22327
+ Fit.Validation.ExpectBoolean(val, true);
22328
+
22329
+ if (Fit.Validation.IsSet(val) && designEditorDetached !== null)
22330
+ {
22331
+ designEditorDetached.SetVisible(val);
22332
+ }
22333
+
22334
+ return base(val);
22335
+ });
22336
+
22337
+ // this.Render = Fit.Core.CreateOverride(this.Render, function(toElement)
22338
+ // {
22339
+ // Fit.Validation.ExpectDomElement(toElement, true);
22340
+
22341
+ // base(toElement);
22342
+
22343
+ // if (designModeDialogMode !== null && designModeDialogMode.AutoOpen === true)
22344
+ // {
22345
+ // openDetachedDesignEditor();
22346
+ // }
22347
+ // });
22348
+
22226
22349
  // See documentation on ControlBase
22227
22350
  this.Enabled = function(val)
22228
22351
  {
@@ -22255,6 +22378,11 @@ Fit.Controls.Input = function(ctlId)
22255
22378
  Fit.Dom.Attribute(me.GetDomElement(), "tabindex", input.disabled !== true && me.DesignMode() === true ? "-1" : null); // Remove tabindex used to prevent control from losing focus when clicking toolbar buttons, as it will allow control to gain focus when clicked using the mouse
22256
22379
  }
22257
22380
 
22381
+ if (designEditorDetached !== null)
22382
+ {
22383
+ designEditorDetached.SetEnabled(val);
22384
+ }
22385
+
22258
22386
  me._internal.UpdateInternalState();
22259
22387
  me._internal.Repaint();
22260
22388
  }
@@ -22267,6 +22395,27 @@ Fit.Controls.Input = function(ctlId)
22267
22395
  {
22268
22396
  Fit.Validation.ExpectBoolean(focus, true);
22269
22397
 
22398
+ // TODO: This function does not work properly when editor is detached !
22399
+ // What should it do if Focused(true) or Focused(false) is invoked ????
22400
+ // What should it return in detached mode ??
22401
+
22402
+ if (designEditorDetached !== null)
22403
+ {
22404
+ //return true; // Hmm, what if 'focus' is set to false ???
22405
+ if (focus === true)
22406
+ {
22407
+ console.log("Redirecting focus to detached editor");
22408
+ designEditorDetached.Focus();
22409
+ }
22410
+ else
22411
+ {
22412
+ // Should it remove focus? Probably not! Focus is locked and editing experience is modal!
22413
+ }
22414
+
22415
+ return designEditorDetached.GetFocused();
22416
+ //return (Fit.Dom.Contained(designEditorDetached.DomElement, Fit.Dom.GetFocused()) === true);
22417
+ }
22418
+
22270
22419
  elm = input;
22271
22420
 
22272
22421
  if (me.DesignMode() === true)
@@ -22398,7 +22547,12 @@ Fit.Controls.Input = function(ctlId)
22398
22547
  /*if (val.indexOf("<p>") === 0)
22399
22548
  htmlWrappedInParagraph = true; // Indicates that val is comparable with value from CKEditor which wraps content in paragraphs*/
22400
22549
 
22401
- if (designModeEnabledAndReady() === true)
22550
+ if (designModeDialogMode !== null)
22551
+ {
22552
+ input.value = val;
22553
+ designModeDialogMode.DomElement.innerHTML = val;
22554
+ }
22555
+ else if (designModeEnabledAndReady() === true)
22402
22556
  {
22403
22557
  // NOTICE: Invalid HTML is removed, so an all invalid HTML string will be discarded
22404
22558
  // by the editor, resulting in the editor's getData() function returning an empty string.
@@ -22521,7 +22675,11 @@ Fit.Controls.Input = function(ctlId)
22521
22675
  {
22522
22676
  // This will destroy control - it will no longer work!
22523
22677
 
22524
- if (me.DesignMode() === true && designModeEnabledAndReady() === false) // DesignMode is enabled but editor is not done loading/initializing
22678
+ if (me.DesignMode() === true && designModeDialogMode !== null)
22679
+ {
22680
+ designModeDialogMode.Dispose();
22681
+ }
22682
+ else if (me.DesignMode() === true && designModeEnabledAndReady() === false) // DesignMode is enabled but editor is not done loading/initializing
22525
22683
  {
22526
22684
  // WARNING: This has the potential to leak memory if editor never loads and resumes task of disposing control!
22527
22685
  designEditorMustDisposeWhenReady = true;
@@ -22681,11 +22839,20 @@ Fit.Controls.Input = function(ctlId)
22681
22839
  me.Maximized(false);
22682
22840
  }
22683
22841
 
22842
+ // if (designModeDialogMode !== null)
22843
+ // {
22844
+ // // TODO: Remove autogrow if DesignMode/DialogMode is disabled
22845
+ // me._internal.Data("autogrow", val === -1 ? "true" : null); // Make control container adjust to editor's height
22846
+ // //designModeDialogMode.DomElement.style.height = val === -1 ? "auto" : "";
22847
+ // return base(val, unit);
22848
+ // }
22849
+
22684
22850
  me._internal.Data("resized", "false");
22685
22851
  me._internal.Data("autogrow", me.DesignMode() === true ? "false" : null);
22686
22852
 
22687
22853
  var autoGrowEnabled = false;
22688
- if (val === -1 && designModeEnabledAndReady() === true) // Enable auto grow if editor is loaded and ready - otherwise enabled in instanceReady handler
22854
+ //if (val === -1 && (designModeEnabledAndReady() === true || designModeDialogMode !== null)) // Enable auto grow if editor is loaded and ready - otherwise enabled in instanceReady handler
22855
+ if (val === -1 && (designModeEnabledAndReady() === true || designModeDialogModeContent !== null)) // Enable auto grow if editor is loaded and ready - otherwise enabled in instanceReady handler
22689
22856
  {
22690
22857
  // A value of -1 is used to reset control height (assume default height).
22691
22858
  // In DesignMode we want the control height to adjust to the content of the editor in this case.
@@ -22695,22 +22862,32 @@ Fit.Controls.Input = function(ctlId)
22695
22862
  autoGrowEnabled = true;
22696
22863
  }
22697
22864
 
22698
- var hideToolbarAgain = false;
22699
- if (isToolbarHiddenInDesignEditor() === true)
22865
+ var h = null;
22866
+
22867
+ //if (designModeDialogMode !== null)
22868
+ if (designModeDialogModeContent !== null)
22700
22869
  {
22701
- // If in DesignMode, temporarily restore toolbar to allow update to height.
22702
- // When toolbar is hidden, a fixed height is set on the editable area which
22703
- // prevent changes to control height.
22704
- restoreHiddenToolbarInDesignEditor();
22705
- hideToolbarAgain = true;
22870
+ h = base(val, unit);
22706
22871
  }
22872
+ else
22873
+ {
22874
+ var hideToolbarAgain = false;
22875
+ if (isToolbarHiddenInDesignEditor() === true)
22876
+ {
22877
+ // If in DesignMode, temporarily restore toolbar to allow update to height.
22878
+ // When toolbar is hidden, a fixed height is set on the editable area which
22879
+ // prevent changes to control height.
22880
+ restoreHiddenToolbarInDesignEditor();
22881
+ hideToolbarAgain = true;
22882
+ }
22707
22883
 
22708
- var h = base(val, unit);
22709
- updateDesignEditorSize();
22884
+ h = base(val, unit);
22885
+ updateDesignEditorSize();
22710
22886
 
22711
- if (hideToolbarAgain === true)
22712
- {
22713
- hideToolbarInDesignMode();
22887
+ if (hideToolbarAgain === true)
22888
+ {
22889
+ hideToolbarInDesignMode();
22890
+ }
22714
22891
  }
22715
22892
 
22716
22893
  // Calculate new maximize height if control is maximizable
@@ -22765,7 +22942,11 @@ Fit.Controls.Input = function(ctlId)
22765
22942
  {
22766
22943
  input.spellcheck = val;
22767
22944
 
22768
- if (me.DesignMode() === true)
22945
+ if (designModeDialogMode !== null)
22946
+ {
22947
+ designModeDialogMode.Reload()
22948
+ }
22949
+ else if (me.DesignMode() === true)
22769
22950
  {
22770
22951
  reloadEditor();
22771
22952
  }
@@ -22979,6 +23160,25 @@ Fit.Controls.Input = function(ctlId)
22979
23160
  {
22980
23161
  if (val !== resizable)
22981
23162
  {
23163
+ // Reset dimensions in case direction is changed - otherwise overflow might occur.
23164
+ // Control container has width:auto when Resizable is Horizontal and
23165
+ // width:height when Resizable is Vertical. Preserving dimensions will
23166
+ // result in resizable element assuming dimensions different from control container.
23167
+ if (designModeDialogMode !== null)
23168
+ {
23169
+ designModeDialogMode.DomElement.style.width = "";
23170
+ designModeDialogMode.DomElement.style.height = "";
23171
+ }
23172
+ else
23173
+ {
23174
+ input.style.width = "";
23175
+ input.style.height = "";
23176
+ input.style.margin = ""; // Chrome adds some odd margin when textarea is resized
23177
+ }
23178
+
23179
+ // Make sure control assumes configured dimensions (disables width:auto and height:auto until resized again)
23180
+ me._internal.Data("resized", "false");
23181
+
22982
23182
  if (val !== Fit.Controls.InputResizing.Disabled) // Resizing enabled
22983
23183
  {
22984
23184
  if (me.Maximizable() === true)
@@ -23007,15 +23207,6 @@ Fit.Controls.Input = function(ctlId)
23007
23207
  resizable = val;
23008
23208
  me._internal.Data("resizable", val.toLowerCase());
23009
23209
 
23010
- if (val === Fit.Controls.InputResizing.Disabled)
23011
- {
23012
- me._internal.Data("resized", "false");
23013
-
23014
- input.style.width = "";
23015
- input.style.height = "";
23016
- input.style.margin = ""; // Chrome adds some odd margin when textarea is resized
23017
- }
23018
-
23019
23210
  revertToSingleLineIfNecessary();
23020
23211
 
23021
23212
  if (me.DesignMode() === true)
@@ -23070,7 +23261,6 @@ Fit.Controls.Input = function(ctlId)
23070
23261
  minMaxUnit = h.Unit;
23071
23262
  maximizeHeightConfigured = heightMax || -1;
23072
23263
 
23073
-
23074
23264
  // Create maximize/minimize button
23075
23265
 
23076
23266
  cmdResize = document.createElement("span");
@@ -23338,6 +23528,11 @@ Fit.Controls.Input = function(ctlId)
23338
23528
  /// <member name="MaximumHeight" type="{ Value: number, Unit?: Fit.TypeDefs.CssUnit }" default="undefined"> Maximum height of dialog </member>
23339
23529
  /// </container>
23340
23530
 
23531
+ /// <container name="Fit.Controls.InputTypeDefs.DesignModeDialogMode" extends="Fit.Controls.InputTypeDefs.DesignModeDetachable">
23532
+ /// <description> DialogMode configuration </description>
23533
+ /// <member name="AutoOpen" type="boolean" default="undefined"> Flag indicating whether dialog is automatically opened </member>
23534
+ /// </container>
23535
+
23341
23536
  /// <container name="Fit.Controls.InputTypeDefs.DesignModeConfig">
23342
23537
  /// <description> Configuration for DesignMode </description>
23343
23538
  /// <member name="Plugins" type="Fit.Controls.InputTypeDefs.DesignModeConfigPlugins" default="undefined"> Plugins configuration </member>
@@ -23346,6 +23541,10 @@ Fit.Controls.Input = function(ctlId)
23346
23541
  /// <member name="Tags" type="Fit.Controls.InputTypeDefs.DesignModeConfigTags" default="undefined"> Tags configuration </member>
23347
23542
  /// <member name="AutoGrow" type="Fit.Controls.InputTypeDefs.DesignModeAutoGrow" default="undefined"> Auto grow configuration </member>
23348
23543
  /// <member name="Detachable" type="Fit.Controls.InputTypeDefs.DesignModeDetachable" default="undefined"> Detachable configuration </member>
23544
+ /// <member name="DialogMode" type="Fit.Controls.InputTypeDefs.DesignModeDialogMode" default="undefined">
23545
+ /// If set, control opens in dialog when activated (on click, on touch, and on ENTER key).
23546
+ /// Control is initially rendered as a read-only value which becomes editable on activation.
23547
+ /// </member>
23349
23548
  /// </container>
23350
23549
 
23351
23550
  /// <function container="Fit.Controls.Input" name="DesignMode" access="public" returns="boolean">
@@ -23394,29 +23593,36 @@ Fit.Controls.Input = function(ctlId)
23394
23593
  Fit.Validation.ExpectStringValue((((editorConfig || {}).AutoGrow || {}).MaximumHeight || {}).Unit, true);
23395
23594
  Fit.Validation.ExpectBoolean(((editorConfig || {}).AutoGrow || {}).PreventResizeBeyondMaximumHeight, true);
23396
23595
  Fit.Validation.ExpectObject((editorConfig || {}).Detachable, true);
23397
- Fit.Validation.ExpectString(((editorConfig || {}).Detachable || {}).Title, true);
23398
- Fit.Validation.ExpectBoolean(((editorConfig || {}).Detachable || {}).Maximizable, true);
23399
- Fit.Validation.ExpectBoolean(((editorConfig || {}).Detachable || {}).Maximized, true);
23400
- Fit.Validation.ExpectBoolean(((editorConfig || {}).Detachable || {}).Draggable, true);
23401
- Fit.Validation.ExpectBoolean(((editorConfig || {}).Detachable || {}).Resizable, true);
23402
- Fit.Validation.ExpectObject(((editorConfig || {}).Detachable || {}).Width, true);
23403
- Fit.Validation.ExpectNumber((((editorConfig || {}).Detachable || {}).Width || {}).Value, true);
23404
- Fit.Validation.ExpectStringValue((((editorConfig || {}).Detachable || {}).Width || {}).Unit, true);
23405
- Fit.Validation.ExpectObject(((editorConfig || {}).Detachable || {}).MinimumWidth, true);
23406
- Fit.Validation.ExpectNumber((((editorConfig || {}).Detachable || {}).MinimumWidth || {}).Value, true);
23407
- Fit.Validation.ExpectStringValue((((editorConfig || {}).Detachable || {}).MinimumWidth || {}).Unit, true);
23408
- Fit.Validation.ExpectObject(((editorConfig || {}).Detachable || {}).MaximumWidth, true);
23409
- Fit.Validation.ExpectNumber((((editorConfig || {}).Detachable || {}).MaximumWidth || {}).Value, true);
23410
- Fit.Validation.ExpectStringValue((((editorConfig || {}).Detachable || {}).MaximumWidth || {}).Unit, true);
23411
- Fit.Validation.ExpectObject(((editorConfig || {}).Detachable || {}).Height, true);
23412
- Fit.Validation.ExpectNumber((((editorConfig || {}).Detachable || {}).Height || {}).Value, true);
23413
- Fit.Validation.ExpectStringValue((((editorConfig || {}).Detachable || {}).Height || {}).Unit, true);
23414
- Fit.Validation.ExpectObject(((editorConfig || {}).Detachable || {}).MinimumHeight, true);
23415
- Fit.Validation.ExpectNumber((((editorConfig || {}).Detachable || {}).MinimumHeight || {}).Value, true);
23416
- Fit.Validation.ExpectStringValue((((editorConfig || {}).Detachable || {}).MinimumHeight || {}).Unit, true);
23417
- Fit.Validation.ExpectObject(((editorConfig || {}).Detachable || {}).MaximumHeight, true);
23418
- Fit.Validation.ExpectNumber((((editorConfig || {}).Detachable || {}).MaximumHeight || {}).Value, true);
23419
- Fit.Validation.ExpectStringValue((((editorConfig || {}).Detachable || {}).MaximumHeight || {}).Unit, true);
23596
+ Fit.Validation.ExpectObject((editorConfig || {}).DialogMode, true);
23597
+
23598
+ Fit.Array.ForEach([(editorConfig || {}).Detachable || null, (editorConfig || {}).DialogMode || null], function(detachableConfig)
23599
+ {
23600
+ if (detachableConfig === null) return;
23601
+
23602
+ Fit.Validation.ExpectString(detachableConfig.Title, true);
23603
+ Fit.Validation.ExpectBoolean(detachableConfig.Maximizable, true);
23604
+ Fit.Validation.ExpectBoolean(detachableConfig.Maximized, true);
23605
+ Fit.Validation.ExpectBoolean(detachableConfig.Draggable, true);
23606
+ Fit.Validation.ExpectBoolean(detachableConfig.Resizable, true);
23607
+ Fit.Validation.ExpectObject(detachableConfig.Width, true);
23608
+ Fit.Validation.ExpectNumber((detachableConfig.Width || {}).Value, true);
23609
+ Fit.Validation.ExpectStringValue((detachableConfig.Width || {}).Unit, true);
23610
+ Fit.Validation.ExpectObject(detachableConfig.MinimumWidth, true);
23611
+ Fit.Validation.ExpectNumber((detachableConfig.MinimumWidth || {}).Value, true);
23612
+ Fit.Validation.ExpectStringValue((detachableConfig.MinimumWidth || {}).Unit, true);
23613
+ Fit.Validation.ExpectObject(detachableConfig.MaximumWidth, true);
23614
+ Fit.Validation.ExpectNumber((detachableConfig.MaximumWidth || {}).Value, true);
23615
+ Fit.Validation.ExpectStringValue((detachableConfig.MaximumWidth || {}).Unit, true);
23616
+ Fit.Validation.ExpectObject(detachableConfig.Height, true);
23617
+ Fit.Validation.ExpectNumber((detachableConfig.Height || {}).Value, true);
23618
+ Fit.Validation.ExpectStringValue((detachableConfig.Height || {}).Unit, true);
23619
+ Fit.Validation.ExpectObject(detachableConfig.MinimumHeight, true);
23620
+ Fit.Validation.ExpectNumber((detachableConfig.MinimumHeight || {}).Value, true);
23621
+ Fit.Validation.ExpectStringValue((detachableConfig.MinimumHeight || {}).Unit, true);
23622
+ Fit.Validation.ExpectObject(detachableConfig.MaximumHeight, true);
23623
+ Fit.Validation.ExpectNumber((detachableConfig.MaximumHeight || {}).Value, true);
23624
+ Fit.Validation.ExpectStringValue((detachableConfig.MaximumHeight || {}).Unit, true);
23625
+ })
23420
23626
 
23421
23627
  if (editorConfig && editorConfig.Tags)
23422
23628
  {
@@ -23436,6 +23642,174 @@ Fit.Controls.Input = function(ctlId)
23436
23642
 
23437
23643
  if (Fit.Validation.IsSet(val) === true)
23438
23644
  {
23645
+ // TODO: Support reloading using DesignMode(true, {}) !!
23646
+ // What should happen if detached editor is open in dialog mode ??
23647
+ // Normal detached editor remains visible on screen so data is not lost.
23648
+ // Changes to data can still be committed or discarded, which makes sense.
23649
+
23650
+ //if (designEditorConfig !== null && designEditorConfig.DialogMode)
23651
+ var isDialogMode = (editorConfig || designEditorConfig || {}).DialogMode && true || false;
23652
+
23653
+ if (val === true && isDialogMode === true)
23654
+ {
23655
+ if (designModeDialogMode !== null)
23656
+ {
23657
+ designModeDialogMode.Dispose();
23658
+ }
23659
+
23660
+ if (Fit.Validation.IsSet(editorConfig) === true)
23661
+ {
23662
+ designEditorConfig = Fit.Core.Clone(editorConfig); // Clone to prevent external code from making changes later
23663
+ }
23664
+
23665
+ var autoOpen = designEditorConfig.DialogMode.AutoOpen === true;
23666
+ var dialogConfig = designEditorConfig.DialogMode;
23667
+
23668
+ delete designEditorConfig.DialogMode.AutoOpen;
23669
+ delete designEditorConfig.DialogMode;
23670
+
23671
+ designEditorConfig.Detachable = dialogConfig;
23672
+
23673
+ var dimOnMouseDown = { Width: -1, Height: -1 };
23674
+
23675
+ me._internal.Data("designmode", "true")
23676
+ me._internal.Data("dialogmode", "true")
23677
+ var div = document.createElement("div");
23678
+ designModeDialogModeContent = div; // TODO: Nullify if dialog mode is disabled
23679
+ div.tabIndex = 0;
23680
+ //me.GetDomElement().tabIndex = 0;
23681
+ me.GetDomElement().tabIndex = -1; // Necessary ?? Required to keep focus while opening detached editor perhaps ?
23682
+
23683
+ if (designEditorConfig.AutoGrow && designEditorConfig.AutoGrow.MinimumHeight)
23684
+ {
23685
+ div.style.minHeight = designEditorConfig.AutoGrow.MinimumHeight.Value + (designEditorConfig.AutoGrow.MinimumHeight.Unit || "px");
23686
+ }
23687
+ if (designEditorConfig.AutoGrow && designEditorConfig.AutoGrow.MaximumHeight)
23688
+ {
23689
+ div.style.maxHeight = designEditorConfig.AutoGrow.MaximumHeight.Value + (designEditorConfig.AutoGrow.MaximumHeight.Unit || "px");
23690
+ }
23691
+
23692
+ if (nativeResizableAvailable === true)
23693
+ {
23694
+ Fit.Events.AddHandler(div, Fit.Browser.IsTouchEnabled() ? "touchstart" : "mousedown", function(e)
23695
+ {
23696
+ dimOnMouseDown = { Width: div.offsetWidth, Height: div.offsetHeight };
23697
+ });
23698
+
23699
+ Fit.Events.AddHandler(div, Fit.Browser.IsTouchEnabled() ? "touchmove" : "mousemove", function(e)
23700
+ {
23701
+ // var ev = Fit.Events.GetEvent(e);
23702
+
23703
+ // if (ev.buttons !== 1) // The .buttons property does not exist in older browsers (see nativeResizableAvailable)
23704
+ // {
23705
+ // return; // Skip - primary button not held down - not resizing
23706
+ // }
23707
+
23708
+ if (Fit.Events.GetPointerState().Buttons.Primary === false && Fit.Events.GetPointerState().Buttons.Touch === false)
23709
+ {
23710
+ return; // Skip - primary button not held down - not resizing
23711
+ }
23712
+
23713
+ if (me.Resizable() !== Fit.Controls.InputResizing.Disabled && (div.style.width !== "" || div.style.height !== "")) // Div was resized
23714
+ {
23715
+ me._internal.Data("resized", "true");
23716
+ }
23717
+ });
23718
+ }
23719
+
23720
+ // Create synthetic #touchtap" event which executes on touchend, but only if
23721
+ // finger did not travel more than 10px. Otherwise a slide across div will open it which is annoying.
23722
+
23723
+ Fit.Events.AddHandler(div, Fit.Browser.IsTouchEnabled() ? "touchend" : "click", function(e)
23724
+ {
23725
+ console.log("Touch 1");
23726
+
23727
+ if (nativeResizableAvailable === true)
23728
+ {
23729
+ var newDim = { Width: div.offsetWidth, Height: div.offsetHeight };
23730
+
23731
+ console.log(dimOnMouseDown, newDim);
23732
+ if (Fit.Core.IsEqual(dimOnMouseDown, newDim) === false)
23733
+ {
23734
+ return;
23735
+ }
23736
+ }
23737
+
23738
+ console.log("Touch 2");
23739
+
23740
+ var target = Fit.Events.GetTarget(e);
23741
+ if (target.tagName !== "A" && me.Enabled() === true)
23742
+ {
23743
+ console.log("Touch 3");
23744
+ openDetachedDesignEditor();
23745
+ }
23746
+ });
23747
+ div.innerHTML = me.Value();
23748
+ Fit.Dom.InsertAfter(input, div);
23749
+
23750
+ designModeDialogMode = {
23751
+ DomElement: div,
23752
+ AutoOpen: autoOpen,
23753
+ Reload: function()
23754
+ {
23755
+ designEditorDetached.Reload();
23756
+ },
23757
+ Dispose: function()
23758
+ {
23759
+ designEditorDetached.Dispose();
23760
+
23761
+ me._internal.Data("designmode", "false")
23762
+ me._internal.Data("dialogmode", null)
23763
+ Fit.Dom.Remove(designModeDialogMode.DomElement);
23764
+
23765
+ designModeDialogMode = null;
23766
+ }
23767
+ };
23768
+
23769
+ // if (autoOpen)
23770
+ // {
23771
+ // me.Focused(true);
23772
+ // openDetachedDesignEditor();
23773
+ // }
23774
+
23775
+ var keyDownEventId = Fit.Events.AddHandler(me.GetDomElement(), "keydown", function(e)
23776
+ {
23777
+ var ev = Fit.Events.GetEvent(e);
23778
+ var target = Fit.Events.GetTarget(e);
23779
+
23780
+ if (ev.keyCode === 13 && target.tagName !== "A" && me.Enabled() === true)
23781
+ {
23782
+ openDetachedDesignEditor();
23783
+ }
23784
+ });
23785
+
23786
+ // TODO: Remove if disposed !!!
23787
+ var handlerId = Fit.Events.AddHandler(me.GetDomElement(), "#rooted", function()
23788
+ {
23789
+ Fit.Events.RemoveHandler(me.GetDomElement(), handlerId);
23790
+
23791
+ if (designModeDialogMode !== null && designModeDialogMode.AutoOpen === true)
23792
+ {
23793
+ openDetachedDesignEditor();
23794
+
23795
+ // designEditorDetached.DisposeCallback = function()
23796
+ // {
23797
+ // Fit.Events.RemoveHandler(me.GetDomElement(), keyDownEventId);
23798
+ // };
23799
+ }
23800
+ });
23801
+
23802
+ updateDesignEditorPlaceholder();
23803
+
23804
+ return true;
23805
+ }
23806
+ else if (val === false && designModeDialogMode !== null)
23807
+ {
23808
+ designModeDialogMode.Dispose();
23809
+
23810
+ return false;
23811
+ }
23812
+
23439
23813
  var designMode = (me._internal.Data("designmode") === "true");
23440
23814
 
23441
23815
  if (Fit._internal.Controls.Input.ActiveEditorForDialog === me && Fit._internal.Controls.Input.ActiveEditorForDialogDisabledPostponed === true)
@@ -23469,6 +23843,8 @@ Fit.Controls.Input = function(ctlId)
23469
23843
  designEditorConfig = Fit.Core.Clone(editorConfig); // Clone to prevent external code from making changes later
23470
23844
  }
23471
23845
 
23846
+ // Was here
23847
+
23472
23848
  // Notice: Identical logic found in Value(..)!
23473
23849
  if (designEditorConfig !== null && designEditorConfig.Plugins && designEditorConfig.Plugins.Images && designEditorConfig.Plugins.Images.RevokeExternalBlobUrlsOnDispose === true)
23474
23850
  {
@@ -23907,7 +24283,7 @@ Fit.Controls.Input = function(ctlId)
23907
24283
  }
23908
24284
  }
23909
24285
 
23910
- return (me._internal.Data("designmode") === "true");
24286
+ return (me._internal.Data("designmode") === "true" || designModeDialogMode !== null);
23911
24287
  }
23912
24288
 
23913
24289
  /// <function container="Fit.Controls.Input" name="DebounceOnChange" access="public" returns="integer">
@@ -24413,6 +24789,8 @@ Fit.Controls.Input = function(ctlId)
24413
24789
  {
24414
24790
  instanceReady: function()
24415
24791
  {
24792
+ //if (Fit.Dom.Data(Fit.Dom.GetFocused(), "dialogmode") === "true") debugger;
24793
+
24416
24794
  designEditorDom = // Object assignment will make designModeEnabledAndReady() return True, so it must be assigned immediately
24417
24795
  {
24418
24796
  OuterContainer: designEditor.container.$,
@@ -24770,12 +25148,3748 @@ Fit.Controls.Input = function(ctlId)
24770
25148
  },
24771
25149
  doubleclick: function(ev)
24772
25150
  {
25151
+ // Suppress link dialog when double clicking. Use link button instead which
25152
+ // triggers beforeCommandExec below which creates a focus lock to prevent control
25153
+ // from losing focus and firing OnBlur.
25154
+ if (ev.data.element.$.tagName === "A")
25155
+ {
25156
+ ev.cancel();
25157
+ return;
25158
+ }
25159
+
24773
25160
  // Suppress link dialog for tags (similar code found in beforeCommandExec handler below)
24774
- if (Fit.Dom.Data(ev.data.element.$, "tag-id") !== null)
25161
+ // DISABLED: No longer needed since all links now suppress the opening of the link dialog (see above)
25162
+ /*if (Fit.Dom.Data(ev.data.element.$, "tag-id") !== null)
25163
+ {
25164
+ ev.cancel();
25165
+ return;
25166
+ }*/
25167
+ },
25168
+ paste: function(ev)
25169
+ {
25170
+ // Prevent pasting (especially images) into tags.
25171
+ // OnPaste is suppressed using an OnPaste handler in capture phase, which will prevent the operation entirely
25172
+ // on supported browsers. On legacy browsers we handle this by invoking undo on the editor instance instead.
25173
+ //var path = ev.editor.elementPath(); // Null if dialog button is triggered without placing text cursor in editor first
25174
+ //if (Fit.Dom.Data(path.lastElement.$, "tag-id") !== null)
25175
+ if (designEditorSuppressPaste === true) // Also handled in a native OnPaste event handler (capture phase) for supported browsers, which suppresses the event entirely
25176
+ {
25177
+ setTimeout(function() // Postpone - allow editor to create snapshot
25178
+ {
25179
+ ev.editor.execCommand("undo"); // Undo change - paste event cannot be canceled, as it has already happened
25180
+ }, 0);
25181
+ return;
25182
+ }
25183
+ },
25184
+ beforeCommandExec: function(ev)
25185
+ {
25186
+ // Suppress any command (formatting, link dialog etc.) for tags (similar code found in doubleclick handler above).
25187
+ // Commmands can be triggered in multiple ways, e.g. using toolbar buttons, using keyboard shortcuts, and programmatically.
25188
+ var path = ev.editor.elementPath(); // Null if dialog button is triggered without placing text cursor in editor first
25189
+ if (path === null && ev.editor.getData().indexOf("<p><a data-tag-id=") === 0)
25190
+ {
25191
+ // Text cursor has not been placed in editor, but a command such as Bold or "insert image"
25192
+ // has been triggered, and editor content starts with a tag. This results in command being
25193
+ // applied to the tag, which we do not want. Usually this is prevented by the toolbar being
25194
+ // disabled when a tag is selected (see selectionChange event handler further up), but that
25195
+ // is not the case when the user has not yet placed the cursor in the editor.
25196
+ ev.cancel();
25197
+ return;
25198
+ }
25199
+ else if (path !== null && Fit.Dom.Data(path.lastElement.$, "tag-id") !== null && ev.data.name !== "undo") // Allow undo within tag, in case user typed something by mistake
25200
+ {
25201
+ // Cursor is currently placed in a tag - do not allow formatting
25202
+ ev.cancel();
25203
+ return;
25204
+ }
25205
+
25206
+ if (ev && ev.data && ev.data.command && ev.data.command.dialogName)
25207
+ {
25208
+ // Command triggered was a dialog
25209
+
25210
+ // IE9-IE11 does not fire OnFocus when user clicks a dialog button directly,
25211
+ // without placing the text cursor in the editing area first. To avoid this
25212
+ // problem, we simply ignore dialog commands if control does not already
25213
+ // have focus. We target all versions of IE for consistency.
25214
+ if (me.Focused() === false && Fit.Browser.GetBrowser() === "MSIE")
25215
+ {
25216
+ ev.cancel();
25217
+ return;
25218
+ }
25219
+
25220
+ // Prevent multiple control instances from opening a dialog at the same time.
25221
+ // This is very unlikely to happen, as it requires the second dialog to be
25222
+ // triggered programmatically, since a modal layer is immediately placed on top
25223
+ // of the page when clicking a button that opens a dialog, preventing additional
25224
+ // interaction with editors.
25225
+ // Naturally conflicting CSS causing the modal layer to remain hidden could
25226
+ // allow the user to trigger multiple dialogs. Better safe than sorry.
25227
+ if (Fit._internal.Controls.Input.ActiveEditorForDialog)
25228
+ {
25229
+ ev.cancel();
25230
+ return;
25231
+ }
25232
+
25233
+ // Make sure OnFocus fires before locking focus state
25234
+
25235
+ if (me.Focused() === false)
25236
+ {
25237
+ // Control not focused - make sure OnFocus fires when a button is clicked,
25238
+ // and make sure ControlBase internally considers itself focused, so there is
25239
+ // no risk of OnFocus being fired twice without OnBlur firing in between,
25240
+ // when focus state is unlocked, and focus is perhaps re-assigned to another
25241
+ // DOM element within the control, which will be the case if the design editor
25242
+ // is switched back to an ordinary input field (e.g. using DesignMode(false)).
25243
+ me.Focused(true);
25244
+ }
25245
+
25246
+ // Prevent control from firing OnBlur when dialogs are opened.
25247
+ // Notice that locking the focus state will also prevent OnFocus
25248
+ // from being fired automatically.
25249
+ me._internal.FocusStateLocked(true);
25250
+
25251
+ // Make control available to global dialog event handlers which
25252
+ // cannot access individual control instances otherwise.
25253
+
25254
+ Fit._internal.Controls.Input.ActiveEditorForDialog = me; // Editor instance is needed when OnHide event is fired for dialog on global CKEditor instance
25255
+ Fit._internal.Controls.Input.ActiveDialogForEditor = null; // Dialog instance associated with editor will be set when dialog's OnShow event fires
25256
+ }
25257
+ }
25258
+ }
25259
+ });
25260
+ }
25261
+
25262
+ function disableDesignEditorButtons() // Might be called multiple times, e.g. if navigating from one tag/mention to another - buttons must be disabled every time since CKEditor itself re-enable buttons when navigating elements in editor
25263
+ {
25264
+ var preserveButtonState = designEditorRestoreButtonState === null;
25265
+
25266
+ if (preserveButtonState === true)
25267
+ {
25268
+ designEditorRestoreButtonState = {};
25269
+ }
25270
+
25271
+ Fit.Array.ForEach(designEditor.toolbar, function(toolbarGroup)
25272
+ {
25273
+ var items = toolbarGroup.items;
25274
+
25275
+ Fit.Array.ForEach(toolbarGroup.items, function(item)
25276
+ {
25277
+ if (item.command) // Buttons have a command identifier which can be used to resolve the actual command instance
25278
+ {
25279
+ var cmd = designEditor.getCommand(item.command);
25280
+
25281
+ if (preserveButtonState === true && cmd.state !== CKEDITOR.TRISTATE_DISABLED) // https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_command.html#property-state
25282
+ {
25283
+ designEditorRestoreButtonState[item.command] = true;
25284
+ }
25285
+
25286
+ cmd.disable();
25287
+ }
25288
+ else if (item.setState) // MenuButtons allow for direct manipulation of enabled/disabled state
25289
+ {
25290
+ if (preserveButtonState === true && item.getState() !== CKEDITOR.TRISTATE_DISABLED) // https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_command.html#property-state
25291
+ {
25292
+ designEditorRestoreButtonState[item.name] = item;
25293
+ }
25294
+
25295
+ item.setState(CKEDITOR.TRISTATE_DISABLED);
25296
+ }
25297
+ });
25298
+ });
25299
+ }
25300
+
25301
+ function restoreDesignEditorButtons()
25302
+ {
25303
+ if (designEditorRestoreButtonState !== null)
25304
+ {
25305
+ Fit.Array.ForEach(designEditorRestoreButtonState, function(commandKey)
25306
+ {
25307
+ if (designEditorRestoreButtonState[commandKey] === true) // Command button
25308
+ {
25309
+ var cmd = designEditor.getCommand(commandKey);
25310
+ cmd.enable();
25311
+ }
25312
+ else // MenuButton
25313
+ {
25314
+ designEditorRestoreButtonState[commandKey].setState(CKEDITOR.TRISTATE_OFF); // Enabled but not highlighted/activated like e.g. a bold button would be when selecting bold text
25315
+ }
25316
+ });
25317
+
25318
+ designEditorRestoreButtonState = null;
25319
+ }
25320
+ };
25321
+
25322
+ function updateDesignEditorSize()
25323
+ {
25324
+ if (me.DesignMode() === true && designModeDialogMode === null)
25325
+ {
25326
+ if (designEditorUpdateSizeDebouncer !== -1)
25327
+ {
25328
+ clearTimeout(designEditorUpdateSizeDebouncer);
25329
+ designEditorUpdateSizeDebouncer = -1;
25330
+ }
25331
+
25332
+ if (designModeEnabledAndReady() === false)
25333
+ {
25334
+ // Postpone, editor is not ready yet.
25335
+ // This may happen when editor is created and Width(..) is
25336
+ // immediately set after creating and mounting the control.
25337
+ // https://github.com/Jemt/Fit.UI/issues/34
25338
+ // This is a problem because CKEditor uses setTimeout(..) to for instance
25339
+ // allow early registration of events, and because resources are loaded
25340
+ // in an async. manner.
25341
+ designEditorUpdateSizeDebouncer = setTimeout(function()
25342
+ {
25343
+ designEditorUpdateSizeDebouncer = -1;
25344
+ updateDesignEditorSize();
25345
+ }, 100);
25346
+
25347
+ return;
25348
+ }
25349
+
25350
+ //var w = me.Width();
25351
+ var h = me.Height();
25352
+
25353
+ // Default control width is 200px (defined in Styles.css).
25354
+ // NOTICE: resize does not work reliably when editor is hidden, e.g. behind a tab with display:none.
25355
+ // The height set will not have the height of the toolbar substracted since the height can not be
25356
+ // determined for hidden objects, so the editor will become larger than the value set (height specified + toolbar height).
25357
+ // http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-resize
25358
+ designEditorSuppressOnResize = true;
25359
+ designEditor.resize("100%", h.Value > -1 ? h.Value + h.Unit : "100%"); // A height of 100% allow editor to automatically adjust the height of the editor's content area to the height of its content (data-autogrow="true" must be set to make control container adjust to its content as well)
25360
+ designEditorSuppressOnResize = false;
25361
+
25362
+ // Set mutation observer responsible for updating editor size once it becomes visible
25363
+
25364
+ if (mutationObserverId !== -1) // Cancel any mutation observer previously registered
25365
+ {
25366
+ Fit.Events.RemoveMutationObserver(mutationObserverId);
25367
+ mutationObserverId = -1;
25368
+ }
25369
+
25370
+ var concealer = Fit.Dom.GetConcealer(me.GetDomElement()); // Get element hiding editor
25371
+
25372
+ if (concealer !== null) // Editor is hidden - adjust size when it becomes visible
25373
+ {
25374
+ mutationObserverId = Fit.Events.AddMutationObserver(concealer, function(elm)
25375
+ {
25376
+ if (Fit.Dom.IsVisible(me.GetDomElement()) === true)
25377
+ {
25378
+ designEditorSuppressOnResize = true;
25379
+ designEditor.resize("100%", h.Value > -1 ? h.Value + h.Unit : "100%"); // A height of 100% allow editor to automatically adjust the height of the editor's content area to the height of its content (data-autogrow="true" must be set to make control container adjust to its content as well)
25380
+ designEditorSuppressOnResize = false;
25381
+
25382
+ disconnect(); // Observers are expensive - remove when no longer needed
25383
+ }
25384
+ });
25385
+ }
25386
+ }
25387
+ }
25388
+
25389
+ function isToolbarHiddenInDesignEditor() // Returns True if editor is fully loaded and toolbar is hidden
25390
+ {
25391
+ var toolbarContainer = designModeEnabledAndReady() === true ? designEditorDom.Top || designEditorDom.Bottom : null; // Top is null if editor is placed at the bottom
25392
+ return (toolbarContainer !== null && toolbarContainer.style.display === "none");
25393
+ }
25394
+
25395
+ function hideToolbarInDesignMode() // Editor must be fully loaded before calling this function!
25396
+ {
25397
+ if (designEditorConfig !== null && designEditorConfig.Toolbar && designEditorConfig.Toolbar.HideInitially === true)
25398
+ {
25399
+ var toolbarContainer = designEditorDom.Top || designEditorDom.Bottom; // Top is null if editor is placed at the bottom
25400
+
25401
+ if (toolbarContainer.style.display === "none")
25402
+ {
25403
+ return; // Already hidden
25404
+ }
25405
+
25406
+ // Prevent editor from increasing its height when toolbar is shown.
25407
+ // This is not ideal. We use the top/bottom's (toolbar's) height but it might change
25408
+ // if window is resized, which will cause buttons to "word wrap". But that is
25409
+ // acceptable. In this case the editor might change dimensions when toolbar is
25410
+ // shown and static height on content area is removed in OnFocus handler registered
25411
+ // in init().
25412
+
25413
+ var content = designEditorDom.Editable;
25414
+ content.style.height = toolbarContainer.offsetHeight + content.offsetHeight + "px";
25415
+
25416
+ // Hide toolbar
25417
+
25418
+ toolbarContainer.style.display = "none";
25419
+
25420
+ // Make editable area adjust to take up space previously consumed by toolbar
25421
+ updateDesignEditorSize();
25422
+ }
25423
+ }
25424
+
25425
+ function restoreHiddenToolbarInDesignEditor()
25426
+ {
25427
+ if (designModeEnabledAndReady() === true && designEditorConfig !== null && designEditorConfig.Toolbar && designEditorConfig.Toolbar.HideInitially === true)
25428
+ {
25429
+ // Toolbar has been initially hidden - make it appear again
25430
+
25431
+ var toolbarContainer = designEditorDom.Top || designEditorDom.Bottom; // Top is null if editor is placed at the bottom
25432
+
25433
+ if (toolbarContainer.style.display === "")
25434
+ {
25435
+ return; // Already restored - no longer hidden
25436
+ }
25437
+
25438
+ toolbarContainer.style.display = "";
25439
+
25440
+ // Hiding the toolbar will cause the editor to decrease its height, while displaying the toolbar again
25441
+ // will cause it to increase its height. To avoid this "flickering" a fixed height (toolbar height + content height)
25442
+ // was applied when toolbar was hidden. But now that the toolbar is once again visible, we remove the fixed height
25443
+ // again - otherwise resizing and auto grow will not work.
25444
+
25445
+ var content = designEditorDom.Editable;
25446
+ content.style.height = "";
25447
+
25448
+ me._internal.Data("toolbar", "true");
25449
+
25450
+ // Update size of editable area in case auto grow is not enabled, in which case
25451
+ // toolbar will now have taken up space outside of control's container (overflowing).
25452
+ // Make editable area fit control container again.
25453
+ updateDesignEditorSize();
25454
+ }
25455
+ }
25456
+
25457
+ function updateDesignEditorPlaceholder(clearPlaceholder)
25458
+ {
25459
+ Fit.Validation.ExpectBoolean(clearPlaceholder, true);
25460
+
25461
+ if (designModeDialogModeContent !== null)
25462
+ {
25463
+ var val = clearPlaceholder !== true && me.Value() === "" ? me.Placeholder() : "";
25464
+ Fit.Dom.Data(designModeDialogMode.DomElement, "placeholder", val || null);
25465
+ }
25466
+ else if (designModeEnabledAndReady() === true)
25467
+ {
25468
+ if (Fit.Browser.GetBrowser() === "MSIE" && Fit.Browser.GetVersion() < 10)
25469
+ {
25470
+ // Native support for placeholders (using the real placeholder attribute) was
25471
+ // introduced in IE10, so we want to ensure consistent behaviour for all controls,
25472
+ // as e.g. Input and DatePicker uses the native placeholder implementation.
25473
+ return;
25474
+ }
25475
+
25476
+ // WARNING: Retrieving value from editor is expensive! Do not
25477
+ // call updateDesignEditorPlaceholder() too often (e.g. OnChange).
25478
+ // Simply make sure placeholder is updated OnFocus and OnBlur.
25479
+
25480
+ var val = clearPlaceholder !== true && me.Value() === "" ? me.Placeholder() : "";
25481
+ Fit.Dom.Data(designEditorDom.Editable, "placeholder", val || null);
25482
+ }
25483
+ }
25484
+
25485
+ function openDetachedDesignEditor()
25486
+ {
25487
+ // TODO:
25488
+ // - OK: input.AddValidationRule mappes ikke til detached editor
25489
+ // - OK: input.AutoPostBack(false) fejler når dialogen er åben (DialogMode)
25490
+ // - data-dialogmode="true" er ikke konsistent i sin tilstedeværelse - by design ?
25491
+ // - OK: input.Placeholder("Hejsa") har ingen effekt i DialogMode
25492
+ // - OK: input.AutoPostBack(true) virker ikke i detached editor
25493
+ // - Understøt reload af Dialog Mode med DesignMode(true, newUpdatedConfig) ??
25494
+ // - OK: input.Type() returnerer Textarea for dialog mode
25495
+ // - input.Visible(boolean) viser/skjuler ikke dialogen
25496
+ // - input.Maximizable(true) vises men positioneres forkert og virker ikke
25497
+ // - input.Resizable("enabled") viser ingen resize handle
25498
+ // - input.MultiLine(true) fejler
25499
+
25500
+ me._internal.FocusStateLocked(true);
25501
+
25502
+ var deConfig = null;
25503
+
25504
+ var updateDetachedConfiguration = function()
25505
+ {
25506
+ deConfig = Fit.Core.Clone(designEditorConfig || {});
25507
+
25508
+ // Configure detached editor like original, but with a few required changes.
25509
+ // We need to make sure image blobs are handled properly, that detached editor
25510
+ // cannot created another detached editor, that the toolbar is initially visible
25511
+ // at the top of the dialog, and that auto grow is disabled.
25512
+
25513
+ if (designModeEnableImagePlugin() === true)
25514
+ {
25515
+ deConfig = Fit.Core.Merge(deConfig, // Override image plugin configuration
25516
+ {
25517
+ Plugins:
25518
+ {
25519
+ Images:
25520
+ {
25521
+ Enabled: true,
25522
+ EmbedType: deConfig.Plugins && deConfig.Plugins.Images && deConfig.Plugins.Images.EmbedType,
25523
+
25524
+ // Image blobs added in detached editor must always be disposed if no longer referenced.
25525
+ // Furthermore we make sure image blobs originating from main editor are never disposed.
25526
+ // When detached editor is closed, images transfered from detached editor to main editor
25527
+ // are added to the main editor's index over image blobs so that the main editor becomes
25528
+ // responsible for the memory management of these.
25529
+ // If detached editor is closed without transfering changes (canceled), all images found
25530
+ // in the detached editor, which are not referenced in the main editor, are disposed.
25531
+ // See OnClick handlers for OK and Cancel buttons.
25532
+
25533
+ RevokeBlobUrlsOnDispose: "UnreferencedOnly", // Make dialog editor preserve newly added (and still referenced) image blobs when disposed
25534
+ RevokeExternalBlobUrlsOnDispose: false // Make dialog editor preserve images blobs initially added from main editor
25535
+ }
25536
+ }
25537
+ });
25538
+ }
25539
+
25540
+ deConfig.Toolbar = deConfig.Toolbar || {};
25541
+ deConfig.Toolbar.Detach = false;
25542
+ deConfig.Toolbar.Position = "Top";
25543
+ deConfig.Toolbar.Sticky = false;
25544
+ deConfig.Toolbar.HideInitially = false;
25545
+
25546
+ delete deConfig.AutoGrow;
25547
+ };
25548
+
25549
+ updateDetachedConfiguration();
25550
+
25551
+ // Create dialog and buttons
25552
+
25553
+ var dia = new Fit.Controls.Dialog();
25554
+ Fit.Dom.AddClass(dia.GetDomElement(), "FitUiControlInputDetached");
25555
+
25556
+ var cmdOk = new Fit.Controls.Button();
25557
+ var cmdCancel = new Fit.Controls.Button();
25558
+
25559
+ // Create editor
25560
+
25561
+ var de = new Fit.Controls.Input();
25562
+
25563
+ var setDetachedEditorSettings = function()
25564
+ {
25565
+ de.Width(100, "%");
25566
+ de.Height(-1);
25567
+ de.CheckSpelling(me.CheckSpelling());
25568
+ de.DesignMode(true, deConfig);
25569
+ };
25570
+
25571
+ setDetachedEditorSettings();
25572
+ de.Value(me.Value());
25573
+
25574
+ // Make editor adjust to the dimensions of the dialog
25575
+
25576
+ // Adjust editor size to fit dialog using a timer.
25577
+ // Alternatively expose an OnResize event on Fit.Controls.Dialog, use it in
25578
+ // combination with window.onresize, and adjust editor when these events are triggered.
25579
+ // However, the process of monitoring the dimensions using a timer is practically free,
25580
+ // so even though the timer interupts browser events such as onmousemove, onscroll, etc.,
25581
+ // it creates no lack at all.
25582
+ var height = -1
25583
+ var dimMonitorId = setInterval(function()
25584
+ {
25585
+ if (height === -1 && de._internal.DesignModeEnabledAndReady() === false)
25586
+ {
25587
+ return;
25588
+ }
25589
+
25590
+ if (dia.IsOpen() === false)
25591
+ {
25592
+ return; // Closed because control has been set invisible
25593
+ }
25594
+
25595
+ var newHeight = dia.GetDomElement().offsetHeight;
25596
+
25597
+ if (newHeight !== height)
25598
+ {
25599
+ height = newHeight
25600
+ de.Height(Fit.Dom.GetInnerDimensions(dia.GetContentDomElement()).Height);
25601
+ }
25602
+ }, 250);
25603
+
25604
+ // Configure dialog
25605
+
25606
+ var setDialogSettings = function(update)
25607
+ {
25608
+ var detachConfig = deConfig.Detachable || {};
25609
+ detachConfig = Fit.Core.Merge(detachConfig, // Apply default values - existing properties are preserved (Fit.Core.MergeOverwriteBehaviour.Never)
25610
+ {
25611
+ Title: "",
25612
+ Maximizable: true,
25613
+ Maximized: false,
25614
+ Draggable: true,
25615
+ Resizable: true,
25616
+ Width: detachConfig.Width ? detachConfig.Width : { Value: 850, Unit: "px" },
25617
+ MinimumWidth: detachConfig.MinimumWidth ? detachConfig.MinimumWidth : { Value: 20, Unit: "em" },
25618
+ MaximumWidth: detachConfig.MaximumWidth ? detachConfig.MaximumWidth : { Value: 100, Unit: "%" },
25619
+ Height: detachConfig.Height ? detachConfig.Height : { Value: 550, Unit: "px" },
25620
+ MinimumHeight: detachConfig.MinimumHeight ? detachConfig.MinimumHeight : { Value: 12, Unit: "em" },
25621
+ MaximumHeight: detachConfig.MaximumHeight ? detachConfig.MaximumHeight : { Value: 100, Unit: "%" }
25622
+ }, Fit.Core.MergeOverwriteBehaviour.Never);
25623
+
25624
+ var updateDimensions = (!detachConfig.Resizable || update !== true);
25625
+
25626
+ dia.Title(detachConfig.Title);
25627
+ dia.Modal(true);
25628
+ dia.Draggable(detachConfig.Draggable);
25629
+ dia.Resizable(detachConfig.Resizable);
25630
+ dia.Maximizable(detachConfig.Maximizable);
25631
+ dia.Maximized(detachConfig.Maximized);
25632
+ updateDimensions === true && dia.Width(detachConfig.Width.Value, detachConfig.Width.Unit || "px");
25633
+ updateDimensions === true && dia.Height(detachConfig.Height.Value, detachConfig.Height.Unit || "px");
25634
+ dia.MinimumWidth(detachConfig.MinimumWidth.Value, detachConfig.MinimumWidth.Unit || "px");
25635
+ dia.MinimumHeight(detachConfig.MinimumHeight.Value, detachConfig.MinimumHeight.Unit || "px");
25636
+ dia.MaximumWidth(detachConfig.MaximumWidth.Value, detachConfig.MaximumWidth.Unit || "px");
25637
+ dia.MaximumHeight(detachConfig.MaximumHeight.Value, detachConfig.MaximumHeight.Unit || "px");
25638
+ };
25639
+
25640
+ setDialogSettings();
25641
+
25642
+ // Localization support
25643
+
25644
+ var localizeDetachedEditor = function()
25645
+ {
25646
+ // Editor itself is already localized, so we just need to
25647
+ // localize the dialog. The locale variable will already have
25648
+ // been updated by the OnLocaleChanged handler registered in init().
25649
+
25650
+ cmdOk.Title(locale.Ok);
25651
+ cmdCancel.Title(locale.Cancel);
25652
+ };
25653
+ Fit.Internationalization.OnLocaleChanged(localizeDetachedEditor);
25654
+
25655
+ // Expose detached editor API
25656
+
25657
+ designEditorDetached =
25658
+ {
25659
+ GetValue: function()
25660
+ {
25661
+ return de.Value();
25662
+ },
25663
+
25664
+ SetVisible: function(val)
25665
+ {
25666
+ if (val === true)
25667
+ {
25668
+ dia.Open();
25669
+ }
25670
+ else if (val === false)
25671
+ {
25672
+ dia.Close();
25673
+ }
25674
+ },
25675
+
25676
+ SetEnabled: function(val)
25677
+ {
25678
+ Fit.Validation.ExpectBoolean(val);
25679
+
25680
+ if (val === false)
25681
+ {
25682
+ de.Enabled(false);
25683
+ cmdOk.Enabled(false);
25684
+ cmdCancel.Focused(true);
25685
+ }
25686
+ else
25687
+ {
25688
+ de.Enabled(true);
25689
+ cmdOk.Enabled(true);
25690
+ de.Focused(true);
25691
+ }
25692
+ },
25693
+
25694
+ Focus: function()
25695
+ {
25696
+ if (de.Enabled() === true)
25697
+ {
25698
+ de.Focused(true);
25699
+ }
25700
+ else
25701
+ {
25702
+ cmdCancel.Focused(true);
25703
+ }
25704
+ },
25705
+
25706
+ GetFocused: function()
25707
+ {
25708
+ return de.Focused() === true /* also returns true if e.g. link/image dialog is open */
25709
+ || cmdOk.Focused() === true || cmdCancel.Focused() === true;
25710
+ },
25711
+
25712
+ Reload: function()
25713
+ {
25714
+ // Update configuration
25715
+ updateDetachedConfiguration();
25716
+
25717
+ // Update editor
25718
+ setDetachedEditorSettings();
25719
+
25720
+ // Update dialog
25721
+ setDialogSettings(true);
25722
+
25723
+ // Make dimension monitor ensure proper editor height (dialog height might have been changed)
25724
+ height = -1;
25725
+ },
25726
+
25727
+ DisposeCallback: null,
25728
+
25729
+ Dispose: function()
25730
+ {
25731
+ clearInterval(dimMonitorId);
25732
+ Fit.Internationalization.RemoveOnLocaleChanged(localizeDetachedEditor);
25733
+ de.Dispose();
25734
+ dia.Dispose(); // Will also dispose associated buttons
25735
+ designEditorDetached.DisposeCallback && designEditorDetached.DisposeCallback();
25736
+ designEditorDetached = null;
25737
+ }
25738
+ };
25739
+
25740
+ cmdOk.Title(locale.Ok);
25741
+ cmdOk.Icon("check");
25742
+ cmdOk.Type(Fit.Controls.ButtonType.Success);
25743
+ cmdOk.OnClick(function(sender)
25744
+ {
25745
+ var referencedBlobUrls = Fit.String.ParseImageBlobUrls(de.Value());
25746
+ Fit.Array.ForEach(referencedBlobUrls, function(blobUrl)
25747
+ {
25748
+ if (Fit.Array.Contains(imageBlobUrls, blobUrl) === false)
25749
+ {
25750
+ Fit.Array.Add(imageBlobUrls, blobUrl);
25751
+ }
25752
+ });
25753
+
25754
+ me.Value(de.Value());
25755
+
25756
+ designEditorDetached.Dispose();
25757
+
25758
+ me.Focused(true);
25759
+ me._internal.FocusStateLocked(false);
25760
+ });
25761
+ dia.AddButton(cmdOk);
25762
+
25763
+ cmdCancel.Title(locale.Cancel);
25764
+ cmdCancel.Icon("ban");
25765
+ cmdCancel.Type(Fit.Controls.ButtonType.Danger);
25766
+ cmdCancel.OnClick(function(sender)
25767
+ {
25768
+ var closeDialog = function()
25769
+ {
25770
+ var referencedBlobUrls = Fit.String.ParseImageBlobUrls(de.Value());
25771
+ Fit.Array.ForEach(referencedBlobUrls, function(blobUrl)
25772
+ {
25773
+ if (Fit.Array.Contains(imageBlobUrls, blobUrl) === false) // Only remove images added in dialog editor
25774
+ {
25775
+ URL.revokeObjectURL(blobUrl);
25776
+ }
25777
+ });
25778
+
25779
+ var enabled = de.Enabled();
25780
+ designEditorDetached.Dispose(); // Dispose first so e.g. Focused(..) does not behave as if editor is still detached
25781
+
25782
+ if (enabled === true)
25783
+ {
25784
+ me.Focused(true);
25785
+ me._internal.FocusStateLocked(false);
25786
+ }
25787
+ else
25788
+ {
25789
+ me._internal.FocusStateLocked(false);
25790
+ me._internal.FireOnBlur();
25791
+ }
25792
+ };
25793
+
25794
+ if (de.IsDirty() === true)
25795
+ {
25796
+ Fit.Controls.Dialog.Confirm(locale.CancelConfirmTitle + "<br><br>" + locale.CancelConfirmDescription, function(res)
25797
+ {
25798
+ if (res === true)
25799
+ {
25800
+ closeDialog();
25801
+ }
25802
+ else
25803
+ {
25804
+ cmdCancel.Focused(true);
25805
+ }
25806
+ });
25807
+ }
25808
+ else
25809
+ {
25810
+ closeDialog();
25811
+ }
25812
+ });
25813
+ dia.AddButton(cmdCancel);
25814
+
25815
+ dia.Open();
25816
+ de.Render(dia.GetContentDomElement());
25817
+ window.focusingDetachedEditor = true; console.log("Focusing detached editor"); de.Focused(true); window.detachedEditorFocused = true;
25818
+ }
25819
+
25820
+ function revertToSingleLineIfNecessary()
25821
+ {
25822
+ if (wasAutoChangedToMultiLineMode === true && me.Maximizable() === false && me.Resizable() === Fit.Controls.InputResizing.Disabled && me.DesignMode() === false)
25823
+ {
25824
+ me.MultiLine(false); // Changes wasAutoChangedToMultiLineMode to false
25825
+ }
25826
+ }
25827
+
25828
+ function fireOnChange()
25829
+ {
25830
+ var newVal = me.Value();
25831
+
25832
+ if (newVal !== preVal)
25833
+ {
25834
+ // DISABLED: No longer necessary with the introduction of designEditorDirty which ensures
25835
+ // that we get the initial value set from Value(), unless changed by the user using the editor.
25836
+ /*if (designEditor !== null && htmlWrappedInParagraph === false) // A value not wrapped in paragraph(s) was assigned to HTML editor
25837
+ {
25838
+ // Do not trigger OnChange if the only difference is that CKEditor
25839
+ // wrapped the value initially assigned to control in a paragraph.
25840
+ // Only changes made programmatically through the Input control's API
25841
+ // or by the user should be pushed.
25842
+ // This approach is not perfect unfortunately. For instance CKEditor
25843
+ // trims the value, so assigning " hello world" or " <p>Hello world</p>"
25844
+ // to the control will result in OnChange firing if fireOnChange() is called.
25845
+
25846
+ var newValWithoutParagraph = newVal.replace(/^<p>/, "").replace(/<\/p>$/, ""); // Remove <p> and </p> at the beginning and end
25847
+
25848
+ if (newValWithoutParagraph === preVal)
25849
+ {
25850
+ return; // Do not fire OnChange
25851
+ }
25852
+ }*/
25853
+
25854
+ preVal = newVal;
25855
+ me._internal.FireOnChange();
25856
+ }
25857
+ }
25858
+
25859
+ function reloadEditor(force, reloadConfig)
25860
+ {
25861
+ Fit.Validation.ExpectBoolean(force, true);
25862
+ Fit.Validation.ExpectObject(reloadConfig, true); // Not validated further, as it has already been validated in DesignMode(..)
25863
+
25864
+ if (force !== true && (designModeEnabledAndReady() === false || designEditorMustReloadWhenReady === true))
25865
+ {
25866
+ // Attempting to reload editor while initializing - postpone until editor is fully loaded,
25867
+ // since we cannot guarantee reliable behavior with CKEditor if it's disposed while loading.
25868
+ designEditorMustReloadWhenReady = true;
25869
+ designEditorReloadConfig = reloadConfig || designEditorReloadConfig;
25870
+ return;
25871
+ }
25872
+
25873
+ designEditorMustReloadWhenReady = false;
25874
+
25875
+ // Disabling DesignMode brings it back to input or textarea mode.
25876
+ // If reverting to input mode, Height is reset, so we need to preserve that.
25877
+
25878
+ // NOTICE: Custom width/height set using resize handle is not preserved when editor is reloaded
25879
+
25880
+ var height = me.Height();
25881
+ var currentWasAutoChangedToMultiLineMode = wasAutoChangedToMultiLineMode; // DesignMode(false) will result in wasAutoChangedToMultiLineMode being set to false if DesignMode(true) changed the control to MultiLine mode
25882
+
25883
+ // Prevent detached editor from being closed when reloading, e.g. if
25884
+ // CheckSpelling is changed. Detached editor will be reloaded further down.
25885
+ var detachedEditor = null;
25886
+ if (designEditorDetached !== null)
25887
+ {
25888
+ detachedEditor = designEditorDetached;
25889
+ designEditorDetached = null; // Prevent me.DesignMode(false), which in turn calls destroyDesignEditorInstance(), from closing detached editor dialog
25890
+ }
25891
+
25892
+ me.DesignMode(false);
25893
+ 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
25894
+ designEditorReloadConfig = null;
25895
+
25896
+ if (detachedEditor !== null)
25897
+ {
25898
+ designEditorDetached = detachedEditor;
25899
+ designEditorDetached.Reload(); // Reload detached editor to reflect any changes made to configuration
25900
+ }
25901
+
25902
+ me.Height(height.Value, height.Unit);
25903
+ wasAutoChangedToMultiLineMode = currentWasAutoChangedToMultiLineMode;
25904
+ }
25905
+
25906
+ function destroyDesignEditorInstance()
25907
+ {
25908
+ // Destroying editor also fires OnHide event for any dialog currently open, which will clean up:
25909
+ // Fit._internal.Controls.Input.ActiveEditorForDialog;
25910
+ // Fit._internal.Controls.Input.ActiveEditorForDialogDestroyed;
25911
+ // Fit._internal.Controls.Input.ActiveEditorForDialogDisabledPostponed;
25912
+ // Fit._internal.Controls.Input.ActiveDialogForEditor;
25913
+ // Fit._internal.Controls.Input.ActiveDialogForEditorCanceled;
25914
+
25915
+ // Calling destroy() fires OnHide for any dialog currently open, which
25916
+ // in turn disables locked focus state and returns focus to the control.
25917
+
25918
+ designEditor.destroy();
25919
+
25920
+ if (designEditorDetached !== null)
25921
+ {
25922
+ designEditorDetached.Dispose();
25923
+ }
25924
+
25925
+ designEditor = null;
25926
+ designEditorDom = null;
25927
+ //designEditorDirty = false; // Do NOT reset this! We need to preserve dirty state in case DesignMode is reloaded!
25928
+ designEditorDirtyPending = false;
25929
+ //designEditorConfig = null; // Do NOT nullify this! We need it, in case DesignMode is toggled!
25930
+ //designEditorReloadConfig = null; // Do NOT nullify this! We need it, in case DesignMode is reloaded!
25931
+ designEditorRestoreButtonState = null;
25932
+ designEditorSuppressPaste = false;
25933
+ designEditorSuppressOnResize = false;
25934
+ designEditorMustReloadWhenReady = false;
25935
+ designEditorMustDisposeWhenReady = false;
25936
+ designEditorActiveToolbarPanel = null;
25937
+ designEditorDetached = null;
25938
+
25939
+ if (designEditorUpdateSizeDebouncer !== -1)
25940
+ {
25941
+ clearTimeout(designEditorUpdateSizeDebouncer);
25942
+ designEditorUpdateSizeDebouncer = -1;
25943
+ }
25944
+
25945
+ if (mutationObserverId !== -1)
25946
+ {
25947
+ Fit.Events.RemoveMutationObserver(mutationObserverId);
25948
+ mutationObserverId = -1;
25949
+ }
25950
+
25951
+ if (rootedEventId !== -1)
25952
+ {
25953
+ Fit.Events.RemoveHandler(me.GetDomElement(), rootedEventId);
25954
+ rootedEventId = -1;
25955
+ }
25956
+
25957
+ if (createWhenReadyIntervalId !== -1)
25958
+ {
25959
+ clearInterval(createWhenReadyIntervalId);
25960
+ createWhenReadyIntervalId = -1;
25961
+ }
25962
+ }
25963
+
25964
+ function designModeEnabledAndReady()
25965
+ {
25966
+ return designEditorDom !== null; // Editor is fully loaded when editor DOM is made available
25967
+ }
25968
+
25969
+ function designModeEnableImagePlugin()
25970
+ {
25971
+ var config = designEditorConfig || {};
25972
+ var enableImagePlugin = (config.Plugins && config.Plugins.Images && config.Plugins.Images.Enabled === true) || (config.Toolbar && config.Toolbar.Images === true) || false;
25973
+
25974
+ // Force enable image support if images are contained in value - otherwise editor will remove them
25975
+ if (enableImagePlugin === false && designEditorDetached !== null && designEditorDetached.GetValue().indexOf("<img ") > -1)
25976
+ {
25977
+ enableImagePlugin = true;
25978
+ }
25979
+ if (enableImagePlugin === false && me.Value().indexOf("<img ") > -1)
25980
+ {
25981
+ enableImagePlugin = true;
25982
+ }
25983
+
25984
+ return enableImagePlugin;
25985
+ }
25986
+
25987
+ function localize()
25988
+ {
25989
+ locale = Fit.Internationalization.GetLocale(me);
25990
+
25991
+ if (me.DesignMode() === true)
25992
+ {
25993
+ // Prevent reloadEditor() from reloading detached editor.
25994
+ // It will automatically reload when locale is changed.
25995
+ // Without this guard the editor would reload twice.
25996
+ var detachedEditor = null;
25997
+ if (designEditorDetached !== null)
25998
+ {
25999
+ detachedEditor = designEditorDetached;
26000
+ designEditorDetached = null; // Prevent reloadEditor() from reloading detached editor
26001
+ }
26002
+
26003
+ // Re-create editor with new language
26004
+ reloadEditor();
26005
+
26006
+ if (detachedEditor !== null)
26007
+ {
26008
+ designEditorDetached = detachedEditor;
26009
+ }
26010
+ }
26011
+ }
26012
+
26013
+ function repaint()
26014
+ {
26015
+ if (isIe8 === true)
26016
+ {
26017
+ me.AddCssClass("FitUi_Non_Existing_Input_Class");
26018
+ me.RemoveCssClass("FitUi_Non_Existing_Input_Class");
26019
+ }
26020
+ }
26021
+
26022
+ init();
26023
+ }
26024
+
26025
+ /// <container name="Fit.Controls.InputType">
26026
+ /// Enum values determining input type
26027
+ /// </container>
26028
+ Fit.Controls.InputType =
26029
+ {
26030
+ /// <member container="Fit.Controls.InputType" name="Textarea" access="public" static="true" type="string" default="Textarea">
26031
+ /// <description> Multi line input field </description>
26032
+ /// </member>
26033
+ Textarea: "Textarea",
26034
+
26035
+ /// <member container="Fit.Controls.InputType" name="Color" access="public" static="true" type="string" default="Color">
26036
+ /// <description> Input control useful for entering a color </description>
26037
+ /// </member>
26038
+ Color: "Color",
26039
+
26040
+ /// <member container="Fit.Controls.InputType" name="Date" access="public" static="true" type="string" default="Date">
26041
+ /// <description> Input control useful for entering a date </description>
26042
+ /// </member>
26043
+ Date: "Date",
26044
+
26045
+ /// <member container="Fit.Controls.InputType" name="DateTime" access="public" static="true" type="string" default="DateTime">
26046
+ /// <description> Input control useful for entering a date and time </description>
26047
+ /// </member>
26048
+ DateTime: "DateTime",
26049
+
26050
+ /// <member container="Fit.Controls.InputType" name="Email" access="public" static="true" type="string" default="Email">
26051
+ /// <description> Input control useful for entering an e-mail address </description>
26052
+ /// </member>
26053
+ Email: "Email",
26054
+
26055
+ /// <member container="Fit.Controls.InputType" name="Month" access="public" static="true" type="string" default="Month">
26056
+ /// <description> Input control useful for entering a month </description>
26057
+ /// </member>
26058
+ Month: "Month",
26059
+
26060
+ /// <member container="Fit.Controls.InputType" name="Number" access="public" static="true" type="string" default="Number">
26061
+ /// <description> Input control useful for entering a number </description>
26062
+ /// </member>
26063
+ Number: "Number",
26064
+
26065
+ /// <member container="Fit.Controls.InputType" name="Password" access="public" static="true" type="string" default="Password">
26066
+ /// <description> Input control useful for entering a password (characters are masked) </description>
26067
+ /// </member>
26068
+ Password: "Password",
26069
+
26070
+ /// <member container="Fit.Controls.InputType" name="PhoneNumber" access="public" static="true" type="string" default="PhoneNumber">
26071
+ /// <description> Input control useful for entering a phone number </description>
26072
+ /// </member>
26073
+ PhoneNumber: "PhoneNumber",
26074
+
26075
+ /// <member container="Fit.Controls.InputType" name="Text" access="public" static="true" type="string" default="Text">
26076
+ /// <description> Input control useful for entering ordinary text </description>
26077
+ /// </member>
26078
+ Text: "Text",
26079
+
26080
+ /// <member container="Fit.Controls.InputType" name="Time" access="public" static="true" type="string" default="Time">
26081
+ /// <description> Input control useful for entering time </description>
26082
+ /// </member>
26083
+ Time: "Time",
26084
+
26085
+ /// <member container="Fit.Controls.InputType" name="Week" access="public" static="true" type="string" default="Week">
26086
+ /// <description> Input control useful for entering a week number </description>
26087
+ /// </member>
26088
+ Week: "Week",
26089
+
26090
+ Unknown: "Unknown"
26091
+ }
26092
+
26093
+ Fit.Controls.Input.Type = Fit.Controls.InputType; // Backward compatibility
26094
+
26095
+ /// <container name="Fit._internal.Controls.Input">
26096
+ /// Allows for manipulating control (appearance, features, and behaviour).
26097
+ /// Features are NOT guaranteed to be backward compatible, and incorrect use might break control!
26098
+ /// </container>
26099
+ Fit._internal.Controls.Input = {};
26100
+
26101
+ /// <container name="Fit._internal.Controls.Input.Editor">
26102
+ /// Internal settings related to HTML Editor (Design Mode)
26103
+ /// </container>
26104
+ Fit._internal.Controls.Input.Editor =
26105
+ {
26106
+ /// <member container="Fit._internal.Controls.Input.Editor" name="Skin" access="public" static="true" type="'bootstrapck' | 'moono-lisa' | null">
26107
+ /// <description> Skin used with DesignMode - must be set before an editor is created and cannot be changed for each individual control </description>
26108
+ /// </member>
26109
+ Skin: null // Notice: CKEditor does not support multiple different skins on the same page - do not change value once an editor has been created
26110
+ };
26111
+
26112
+ /// <container name="Fit.Controls.InputResizing">
26113
+ /// <description> Resizing options </description>
26114
+ /// <member name="Enabled" access="public" static="true" type="string" default="Enabled"> Allow for resizing both vertically and horizontally </member>
26115
+ /// <member name="Disabled" access="public" static="true" type="string" default="Disabled"> Do not allow resizing </member>
26116
+ /// <member name="Horizontal" access="public" static="true" type="string" default="Horizontal"> Allow for horizontal resizing </member>
26117
+ /// <member name="Vertical" access="public" static="true" type="string" default="Vertical"> Allow for vertical resizing </member>
26118
+ /// </container>
26119
+ Fit.Controls.InputResizing = // Enums must exist runtime
26120
+ {
26121
+ Enabled: "Enabled",
26122
+ Disabled: "Disabled",
26123
+ Horizontal: "Horizontal",
26124
+ Vertical: "Vertical"
26125
+ };
26126
+ /// <container name="Fit.Controls.Input" extends="Fit.Controls.ControlBase">
26127
+ /// Input control which allows for one or multiple lines of
26128
+ /// text, and features a Design Mode for rich HTML content.
26129
+ /// Extending from Fit.Controls.ControlBase.
26130
+ /// </container>
26131
+
26132
+ /// <function container="Fit.Controls.Input" name="Input" access="public">
26133
+ /// <description> Create instance of Input control </description>
26134
+ /// <param name="ctlId" type="string" default="undefined"> Unique control ID that can be used to access control using Fit.Controls.Find(..) </param>
26135
+ /// </function>
26136
+ Fit.Controls.Input = function(ctlId)
26137
+ {
26138
+ Fit.Validation.ExpectStringValue(ctlId, true);
26139
+ Fit.Core.Extend(this, Fit.Controls.ControlBase).Apply(ctlId);
26140
+
26141
+ var me = this;
26142
+ var orgVal = ""; // Holds initial value used to determine IsDirty state
26143
+ var preVal = ""; // Holds latest change made by user - used to determine whether OnChange needs to be fired
26144
+ var input = null;
26145
+ var cmdResize = null;
26146
+ var designEditor = null;
26147
+ var designEditorDom = null; // DOM elements within CKEditor which we rely on - some <div> elements become <span> elements in older browsers
26148
+ /*{
26149
+ OuterContainer: null, // <div class="cke">
26150
+ InnerContainer: null, // <div class="cke_inner">
26151
+ Top: null, // <span class="cke_top">
26152
+ Content: null, // <div class="cke_contents">
26153
+ Editable: null, // <div class="cke_editable">
26154
+ Bottom: null // <span class="cke_bottom">
26155
+ }*/
26156
+ var designEditorDirty = false;
26157
+ var designEditorDirtyPending = false;
26158
+ var designEditorConfig = null;
26159
+ var designEditorReloadConfig = null;
26160
+ var designEditorRestoreButtonState = null;
26161
+ var designEditorSuppressPaste = false;
26162
+ var designEditorSuppressOnResize = false;
26163
+ var designEditorMustReloadWhenReady = false;
26164
+ var designEditorMustDisposeWhenReady = false;
26165
+ var designEditorUpdateSizeDebouncer = -1;
26166
+ var designEditorActiveToolbarPanel = null; // { DomElement: HTMLElement, UnlockFocusStateIfEmojiPanelIsClosed: function, CloseEmojiPanel: function }
26167
+ var designEditorDetached = null; // { GetValue: function, Reload: function, Dispose: function }
26168
+ //var htmlWrappedInParagraph = false;
26169
+ var wasAutoChangedToMultiLineMode = false; // Used to revert to single line if multi line was automatically enabled along with DesignMode(true), Maximizable(true), or Resizable(true)
26170
+ var minimizeHeight = -1;
26171
+ var maximizeHeight = -1;
26172
+ var minMaxUnit = null;
26173
+ var maximizeHeightConfigured = -1;
26174
+ var resizable = Fit.Controls.InputResizing.Disabled;
26175
+ var nativeResizableAvailable = false; // Updated in init()
26176
+ var mutationObserverId = -1; // Specific to DesignMode
26177
+ var rootedEventId = -1; // Specific to DesignMode
26178
+ var createWhenReadyIntervalId = -1; // Specific to DesignMode
26179
+ var isIe8 = (Fit.Browser.GetInfo().Name === "MSIE" && Fit.Browser.GetInfo().Version === 8);
26180
+ var debounceOnChangeTimeout = -1;
26181
+ var debouncedOnChange = null;
26182
+ var imageBlobUrls = []; // Specific to DesignMode
26183
+ var locale = null;
26184
+
26185
+ // ============================================
26186
+ // Init
26187
+ // ============================================
26188
+
26189
+ function init()
26190
+ {
26191
+ input = document.createElement("input");
26192
+ input.type = "text";
26193
+ input.autocomplete = "off";
26194
+ input.spellcheck = true;
26195
+ input.onkeyup = function()
26196
+ {
26197
+ if (debounceOnChangeTimeout === -1)
26198
+ {
26199
+ fireOnChange();
26200
+ }
26201
+ else
26202
+ {
26203
+ if (debouncedOnChange === null)
26204
+ {
26205
+ debouncedOnChange = Fit.Core.CreateDebouncer(fireOnChange, debounceOnChangeTimeout);
26206
+ }
26207
+
26208
+ debouncedOnChange.Invoke();
26209
+ }
26210
+
26211
+ if (me.Maximizable() === true)
26212
+ {
26213
+ // Scroll to bottom if nearby, to make sure text does not collide with maximize button.
26214
+ // Extra padding-bottom is added inside control to allow for spacing between text and maximize button.
26215
+
26216
+ var scrollContainer = designEditorDom && designEditorDom.Editable || input;
26217
+ var autoScrollToBottom = scrollContainer.scrollTop + scrollContainer.clientHeight > scrollContainer.scrollHeight - 15; // True when at bottom or very close (15px buffer)
26218
+
26219
+ if (autoScrollToBottom === true)
26220
+ {
26221
+ scrollContainer.scrollTop += 99;
26222
+ }
26223
+ }
26224
+ }
26225
+ input.onchange = function() // OnKeyUp does not catch changes by mouse (e.g. paste or moving selected text)
26226
+ {
26227
+ if (me === null)
26228
+ {
26229
+ // Fix for Chrome which fires OnChange and OnBlur (in both capturering and bubbling phase)
26230
+ // if control has focus while being removed from DOM, e.g. if used in a dialog closed using ESC.
26231
+ // More details here: https://bugs.chromium.org/p/chromium/issues/detail?id=866242
26232
+ return;
26233
+ }
26234
+
26235
+ input.onkeyup();
26236
+ }
26237
+ me._internal.AddDomElement(input);
26238
+
26239
+ me.AddCssClass("FitUiControlInput");
26240
+
26241
+ me._internal.Data("multiline", "false");
26242
+ me._internal.Data("maximizable", "false");
26243
+ me._internal.Data("maximized", "false");
26244
+ me._internal.Data("resizable", resizable.toLowerCase());
26245
+ me._internal.Data("resized", "false");
26246
+ me._internal.Data("designmode", "false");
26247
+
26248
+ Fit.Internationalization.OnLocaleChanged(localize);
26249
+ localize();
26250
+
26251
+ me.OnBlur(function(sender)
26252
+ {
26253
+ // Due to CKEditor and plugins allowing for inconsistency between what is being
26254
+ // pushed via OnChange and the editor's actual value, we ensure that the latest
26255
+ // and actual value is pushed via Input.OnChange when the control lose focus.
26256
+ // See related bug report for CKEditor here: https://github.com/ckeditor/ckeditor4/issues/4856
26257
+
26258
+ if (debouncedOnChange !== null)
26259
+ {
26260
+ debouncedOnChange.Cancel(); // Do not trigger fireOnChange twice (below) if currently scheduled for execution
26261
+ }
26262
+
26263
+ fireOnChange(); // Only fires OnChange if value has actually changed
26264
+ });
26265
+
26266
+ me.OnFocus(function()
26267
+ {
26268
+ restoreHiddenToolbarInDesignEditor(); // Make toolbar appear if currently hidden
26269
+ updateDesignEditorPlaceholder(true); // Clear placeholder text
26270
+ });
26271
+ me.OnBlur(function()
26272
+ {
26273
+ restoreDesignEditorButtons(); // Restore (enable) editor's toolbar buttons in case they were temporarily disabled
26274
+ updateDesignEditorPlaceholder(); // Show placeholder text if control value is empty
26275
+ });
26276
+
26277
+ Fit.Events.AddHandler(me.GetDomElement(), "paste", true, function(e)
26278
+ {
26279
+ if (me.DesignMode() === true && designEditorSuppressPaste === true)
26280
+ {
26281
+ Fit.Events.Stop(e);
26282
+ }
26283
+ });
26284
+
26285
+ try
26286
+ {
26287
+ // We rely on the .buttons property to optimize resizing for textarea (MultiLine mode).
26288
+ // The MouseEvent class might not be available on older browsers or might throw an exception when constructing.
26289
+ nativeResizableAvailable = window.MouseEvent && new MouseEvent("mousemove", {}).buttons !== undefined || false;
26290
+ }
26291
+ catch (err) {}
26292
+ }
26293
+
26294
+ // ============================================
26295
+ // Public - overrides
26296
+ // ============================================
26297
+
26298
+ // See documentation on ControlBase
26299
+ this.Visible = Fit.Core.CreateOverride(this.Visible, function(val)
26300
+ {
26301
+ Fit.Validation.ExpectBoolean(val, true);
26302
+
26303
+ if (Fit.Validation.IsSet(val) && designEditorDetached !== null)
26304
+ {
26305
+ designEditorDetached.SetVisible(val);
26306
+ }
26307
+
26308
+ return base(val);
26309
+ });
26310
+
26311
+ // See documentation on ControlBase
26312
+ this.Enabled = function(val)
26313
+ {
26314
+ Fit.Validation.ExpectBoolean(val, true);
26315
+
26316
+ if (Fit.Validation.IsSet(val) === true && val !== me.Enabled())
26317
+ {
26318
+ me._internal.Data("enabled", val === true ? "true" : "false");
26319
+
26320
+ if (val === false)
26321
+ {
26322
+ me.Focused(false);
26323
+ }
26324
+
26325
+ input.disabled = val === false;
26326
+
26327
+ if (designModeEnabledAndReady() === true) // ReadOnly mode will be set when instance is ready, if not ready at this time
26328
+ {
26329
+ designEditor.setReadOnly(input.disabled);
26330
+
26331
+ // Set tabindex to allow or disallow focus. Unfortunately there is no editor API for changing the tabindex.
26332
+ // Preventing focus is only possible by nullifying DOM attribute (these does not work: delete elm.tabIndex; elm.tabIndex = null|undefined|-1).
26333
+ Fit.Dom.Attribute(designEditorDom.Editable, "tabindex", input.disabled === true ? null : "0");
26334
+
26335
+ // Prevent control from losing focus when HTML editor is initialized,
26336
+ // e.g. if Design Mode is enabled when ordinary input control gains focus.
26337
+ // This also prevents control from losing focus if toolbar is clicked without
26338
+ // hitting a button. A value of -1 makes it focusable, but keeps it out of
26339
+ // tab flow (keyboard navigation). Also set when DesignMode(true) is called.
26340
+ Fit.Dom.Attribute(me.GetDomElement(), "tabindex", input.disabled !== true && me.DesignMode() === true ? "-1" : null); // Remove tabindex used to prevent control from losing focus when clicking toolbar buttons, as it will allow control to gain focus when clicked using the mouse
26341
+ }
26342
+
26343
+ if (designEditorDetached !== null)
26344
+ {
26345
+ designEditorDetached.SetEnabled(val);
26346
+ }
26347
+
26348
+ me._internal.UpdateInternalState();
26349
+ me._internal.Repaint();
26350
+ }
26351
+
26352
+ return me._internal.Data("enabled") === "true";
26353
+ }
26354
+
26355
+ // See documentation on ControlBase
26356
+ this.Focused = function(focus)
26357
+ {
26358
+ Fit.Validation.ExpectBoolean(focus, true);
26359
+
26360
+ if (designEditorDetached !== null)
26361
+ {
26362
+ if (focus === true)
26363
+ {
26364
+ designEditorDetached.Focus();
26365
+ }
26366
+ else if (focus === false)
26367
+ {
26368
+ Fit.Browser.Debug("WARNING: Unable to remove focus from Input control '" + me.GetId() + "' when modal detached editor is open");
26369
+ }
26370
+
26371
+ return me.Visible(); // Always considered focused if detached editor is open and control (along with detached editor) is visible
26372
+ //return designEditorDetached.GetFocused();
26373
+ }
26374
+
26375
+ elm = input;
26376
+
26377
+ if (me.DesignMode() === true)
26378
+ {
26379
+ if (designModeEnabledAndReady() === true)
26380
+ {
26381
+ elm = designEditor; // Notice: designEditor is an instance of CKEditor, not a DOM element, but it does expose a focus() function
26382
+ }
26383
+ else
26384
+ {
26385
+ elm = me.GetDomElement(); // Editor not loaded yet - focus control container temporarily - focus is later moved to editable area once instanceReady handler is invoked
26386
+ }
26387
+ }
26388
+
26389
+ if (Fit.Validation.IsSet(focus) === true)
26390
+ {
26391
+ if (focus === true)
26392
+ {
26393
+ if (Fit._internal.Controls.Input.ActiveEditorForDialog === me)
26394
+ {
26395
+ // Remove flag used to auto close editor dialog, in case Focused(false)
26396
+ // was called followed by Focused(true), while editor dialog was loading.
26397
+ delete Fit._internal.Controls.Input.ActiveDialogForEditorCanceled;
26398
+ }
26399
+
26400
+ elm.focus();
26401
+ }
26402
+ else // Remove focus
26403
+ {
26404
+ if (designModeEnabledAndReady() === true)
26405
+ {
26406
+ if (Fit._internal.Controls.Input.ActiveEditorForDialog === me)
26407
+ {
26408
+ if (Fit._internal.Controls.Input.ActiveDialogForEditor !== null)
26409
+ {
26410
+ // A dialog (e.g. link or image dialog) is currently open, and will now be closed
26411
+
26412
+ // Hide dialog - fires dialog's OnHide event and returns focus to editor
26413
+ Fit._internal.Controls.Input.ActiveDialogForEditor.hide();
26414
+
26415
+ // CKEditor instance has no blur() function, so we call blur() on DOM element currently focused within CKEditor
26416
+ Fit.Dom.GetFocused().blur();
26417
+
26418
+ // Fire OnBlur manually as blur() above didn't trigger this, as it normally
26419
+ // would. The call to the dialog's hide() function fires its OnHide event
26420
+ // which disables the focus lock, but does so asynchronously, which is
26421
+ // why OnBlur does not fire via ControlBase's onfocusout handler.
26422
+ me._internal.FireOnBlur();
26423
+ }
26424
+ else
26425
+ {
26426
+ // A dialog (e.g. link or image dialog) is currently loading. This situation
26427
+ // can be triggered for debugging purposes by adding the following code in the
26428
+ // beforeCommandExec event handler:
26429
+ // setTimeout(function() { me.Focused(false); }, 0);
26430
+ // Alternatively register an onwheel/onscroll handler on the document that
26431
+ // removes focus from the control, and quickly scroll the document while the
26432
+ // dialog is loading. Use network throttling to increase the load time of the
26433
+ // dialog if necessary.
26434
+
26435
+ // Make dialog close automatically when loaded and shown - handled in dialog's OnShow event handler
26436
+ Fit._internal.Controls.Input.ActiveDialogForEditorCanceled = true;
26437
+
26438
+ // CKEditor instance has no blur() function, so we call blur() on DOM element currently focused within CKEditor.
26439
+ // Notice that OnBlur does not fire immediately (focus state is locked), but does so when dialog's OnHide event fires (async).
26440
+ // While we could fire it immediately and prevent it from firing when the dialog's OnHide event fires, it would prevent
26441
+ // developers from using the OnBlur event to dispose a control in Design Mode, since CKEditor fails when being disposed
26442
+ // while dialogs are open. Focused() will return False after the call to blur() below though - as expected.
26443
+ Fit.Dom.GetFocused().blur();
26444
+ }
26445
+ }
26446
+ else
26447
+ {
26448
+ if (designEditorActiveToolbarPanel !== null)
26449
+ {
26450
+ designEditorActiveToolbarPanel.CloseEmojiPanel(); // Returns focus to editor and nullifies designEditorActiveToolbarPanel
26451
+ }
26452
+
26453
+ // Make sure this control is focused so that one control instance can not
26454
+ // be used to accidentially remove focus from another control instance.
26455
+ if (Fit.Dom.Contained(me.GetDomElement(), Fit.Dom.GetFocused()) === true)
26456
+ {
26457
+ // CKEditor instance has no blur() function, so we call blur() on DOM element currently focused within CKEditor
26458
+ Fit.Dom.GetFocused().blur();
26459
+ }
26460
+ }
26461
+ }
26462
+ else
26463
+ {
26464
+ elm.blur();
26465
+ }
26466
+ }
26467
+ }
26468
+
26469
+ if (me.DesignMode() === true)
26470
+ {
26471
+ // If a dialog is open and it belongs to this control instance, and focus is found within dialog, then control is considered having focus.
26472
+ // However, if <body> is focused while dialog is open, control is also considered to have focus, since dialog temporarily assigns focus to
26473
+ // <body> when tabbing between elements within the dialog. This seems safe as no other control can be considered focused if <body> has focus.
26474
+ if (Fit._internal.Controls.Input.ActiveEditorForDialog === me && (Fit.Dom.Contained(Fit._internal.Controls.Input.ActiveDialogForEditor.getElement().$, Fit.Dom.GetFocused()) === true || Fit.Dom.GetFocused() === document.body))
26475
+ return true;
26476
+
26477
+ // If a toolbar dialog/callout is open and contains the element currently having focus, then control is considered having focus.
26478
+ // If the dialog/callout contains an iframe in which an element has focus, then the iframe is considered focused in the main window.
26479
+ if (designEditorActiveToolbarPanel !== null && Fit.Dom.Contained(designEditorActiveToolbarPanel.DomElement, Fit.Dom.GetFocused()) === true)
26480
+ return true;
26481
+
26482
+ return Fit.Dom.GetFocused() === me.GetDomElement() || Fit.Dom.Contained(me.GetDomElement(), Fit.Dom.GetFocused());
26483
+ }
26484
+
26485
+ return (Fit.Dom.GetFocused() === elm);
26486
+ }
26487
+
26488
+ // See documentation on ControlBase
26489
+ this.Value = function(val, preserveDirtyState)
26490
+ {
26491
+ Fit.Validation.ExpectString(val, true);
26492
+ Fit.Validation.ExpectBoolean(preserveDirtyState, true);
26493
+
26494
+ if (Fit.Validation.IsSet(val) === true)
26495
+ {
26496
+ var fireOnChange = (me.Value() !== val);
26497
+
26498
+ orgVal = (preserveDirtyState !== true ? val : orgVal);
26499
+ preVal = val;
26500
+ designEditorDirty = designEditorDirtyPending === true ? true : false;
26501
+ designEditorDirtyPending = false;
26502
+
26503
+ /*if (val.indexOf("<p>") === 0)
26504
+ htmlWrappedInParagraph = true; // Indicates that val is comparable with value from CKEditor which wraps content in paragraphs*/
26505
+
26506
+ if (designModeEnabledAndReady() === true)
26507
+ {
26508
+ // NOTICE: Invalid HTML is removed, so an all invalid HTML string will be discarded
26509
+ // by the editor, resulting in the editor's getData() function returning an empty string.
26510
+
26511
+ // Calling setData(..) fires CKEditor's onchange event which in turn fires
26512
+ // Input's OnChange event. Suppress OnChange which is fired further down.
26513
+ me._internal.ExecuteWithNoOnChange(function()
26514
+ {
26515
+ CKEDITOR.instances[me.GetId() + "_DesignMode"].setData(val);
26516
+ });
26517
+
26518
+ updateDesignEditorPlaceholder();
26519
+ }
26520
+ else
26521
+ {
26522
+ input.value = val;
26523
+ }
26524
+
26525
+ // Notice: Identical logic found in DesignMode(true, config)!
26526
+ if (designEditorConfig !== null && designEditorConfig.Plugins && designEditorConfig.Plugins.Images && designEditorConfig.Plugins.Images.RevokeExternalBlobUrlsOnDispose === true)
26527
+ {
26528
+ // Keep track of image blobs added via Value(..) so we can dispose of them automatically.
26529
+ // When RevokeExternalBlobUrlsOnDispose is True it basically means that the Input control
26530
+ // is allowed (and expected) to take control over memory management for these blobs
26531
+ // based on the rule set in RevokeBlobUrlsOnDispose.
26532
+ // This code is also found in DesignMode(true, config) since images might be added before
26533
+ // editor is created, in which case we do not yet have the editor configuration used to determine
26534
+ // the desired behaviour.
26535
+
26536
+ var blobUrls = Fit.String.ParseImageBlobUrls(val);
26537
+
26538
+ Fit.Array.ForEach(blobUrls, function(blobUrl)
26539
+ {
26540
+ if (Fit.Array.Contains(imageBlobUrls, blobUrl) === false)
26541
+ {
26542
+ Fit.Array.Add(imageBlobUrls, blobUrl);
26543
+ }
26544
+ });
26545
+ }
26546
+
26547
+ if (fireOnChange === true)
26548
+ me._internal.FireOnChange();
26549
+ }
26550
+
26551
+ if (designModeEnabledAndReady() === true)
26552
+ {
26553
+ // If user has not changed value, then return the value initially set.
26554
+ // CKEditor may change (optimize) HTML when applied, but we always want
26555
+ // the value initially set when no changes have been made by the user.
26556
+ // See additional comments regarding this in the IsDirty() implementation.
26557
+ if (designEditorDirty === false)
26558
+ {
26559
+ return orgVal;
26560
+ }
26561
+
26562
+ var curVal = CKEDITOR.instances[me.GetId() + "_DesignMode"].getData();
26563
+
26564
+ // Remove extra line break added by htmlwriter plugin at the end: <p>Hello world</p>\n
26565
+ curVal = curVal.replace(/<\/p>\n$/, "</p>");
26566
+
26567
+ // Remove empty class attribute on <img> tags which may be temporarily set when selecting
26568
+ // an image using the dragresize plugin. This plugin adds a CSS class (ckimgrsz) to the image
26569
+ // tag while being selected, although the class name is removed when calling getData() above.
26570
+ // However, the empty class attribute is useless, so we remove it. It also results in IsDirty()
26571
+ // returning True while the image is selected if we keep it. Actually the class attribute should
26572
+ // never have been returned since the allowedContent option does not allow it - might be a minor bug.
26573
+ curVal = curVal.replace(/(<img.*?) class=""(.*?>)/, "$1$2"); // Not using /g switch as only one image can be selected
26574
+
26575
+ return curVal;
26576
+ }
26577
+
26578
+ return input.value;
26579
+ }
26580
+
26581
+ // See documentation on ControlBase
26582
+ this.UserValue = Fit.Core.CreateOverride(this.UserValue, function(val)
26583
+ {
26584
+ if (Fit.Validation.IsSet(val) === true && me.DesignMode() === true)
26585
+ {
26586
+ designEditorDirtyPending = true;
26587
+ }
26588
+
26589
+ return base(val);
26590
+ });
26591
+
26592
+ // See documentation on ControlBase
26593
+ this.IsDirty = function()
26594
+ {
26595
+ if (me.DesignMode() === true)
26596
+ {
26597
+ // Never do value comparison in DesignMode.
26598
+ // A value such as "Hello world" could have been provided,
26599
+ // which by CKEditor would be returned as "<p>Hello world</p>".
26600
+ // A value such as '<p style="text-align: center;">Hello</p>' could
26601
+ // also have been set, which by CKEditor would be optimized to
26602
+ // '<p style="text-align:center">Hello</p>' via ACF (Advanced Content Filter):
26603
+ // https://ckeditor.com/docs/ckeditor4/latest/guide/dev_advanced_content_filter.html
26604
+ // Furthermore invalid HTML is removed while valid HTML is kept.
26605
+ // All this makes it very difficult to reliably determine dirty state
26606
+ // by comparing values. Therefore, if the user changed anything by interacting
26607
+ // with the editor, or UserValue(..) was called, always consider the value dirty.
26608
+
26609
+ // Another positive of avoiding value comparison to determine dirty state
26610
+ // is that retrieving the value from CKEditor is fairly expensive.
26611
+
26612
+ return designEditorDirty;
26613
+ }
26614
+
26615
+ return (orgVal !== me.Value());
26616
+ }
26617
+
26618
+ // See documentation on ControlBase
26619
+ this.Clear = function()
26620
+ {
26621
+ me.Value("");
26622
+ }
26623
+
26624
+ // See documentation on ControlBase
26625
+ this.Dispose = Fit.Core.CreateOverride(this.Dispose, function()
26626
+ {
26627
+ // This will destroy control - it will no longer work!
26628
+
26629
+ if (me.DesignMode() === true && designModeEnabledAndReady() === false) // DesignMode is enabled but editor is not done loading/initializing
26630
+ {
26631
+ // WARNING: This has the potential to leak memory if editor never loads and resumes task of disposing control!
26632
+ designEditorMustDisposeWhenReady = true;
26633
+
26634
+ // Editor was disposed while loading/initializing.
26635
+ // Postpone destruction of control to make sure we can clean up resources
26636
+ // reliably once editor is ready. We know that CKEditor does not dispose properly
26637
+ // unless fully loaded (it may leave an instance on the global CKEDITOR object or even fail).
26638
+ Fit.Browser.Debug("WARNING: Attempting to dispose Input control '" + me.GetId() + "' while initializing DesignMode! Control will be disposed later.");
26639
+
26640
+ // Do not keep control in user interface when disposed.
26641
+ // Mount control in document root and move it off screen (outside visible
26642
+ // viewport area). CKEditor will fail initialization if not mounted in DOM.
26643
+ me.Render(document.body);
26644
+ me.GetDomElement().style.position = "fixed";
26645
+ me.GetDomElement().style.left = "0px";
26646
+ me.GetDomElement().style.bottom = "-100px";
26647
+ me.GetDomElement().style.maxHeight = "100px";
26648
+
26649
+ // Detect memory leak
26650
+ /* setTimeout(function()
26651
+ {
26652
+ if (me !== null)
26653
+ {
26654
+ Fit.Browser.Debug("WARNING: Input in DesignMode was not properly disposed in time - potential memory leak detected");
26655
+ }
26656
+ }, 5000); // Usually the load time for an editor is barely measurable, so 5 seconds seems sufficient */
26657
+
26658
+ return;
26659
+ }
26660
+
26661
+ var curVal = designEditorConfig !== null && designEditorConfig.Plugins && designEditorConfig.Plugins.Images && designEditorConfig.Plugins.Images.RevokeBlobUrlsOnDispose === "UnreferencedOnly" ? me.Value() : null;
26662
+
26663
+ if (Fit._internal.Controls.Input.ActiveEditorForDialog === me)
26664
+ {
26665
+ if (Fit._internal.Controls.Input.ActiveDialogForEditor === null)
26666
+ {
26667
+ // Dialog is currently loading.
26668
+ // CKEditor will throw an error if disposed while a dialog (e.g. the link dialog) is loading,
26669
+ // leaving a modal layer on the page behind, making it unusable. This may happen if disposed
26670
+ // from e.g. a DOM event handler, a mutation observer, a timer, or an AJAX request. The input control
26671
+ // itself does not fire any events while the dialog is loading which could trigger this situation, so
26672
+ // this can only happen from "external code".
26673
+
26674
+ // WARNING: This has the potential to leak memory if dialog never loads and resumes task of disposing control!
26675
+ Fit._internal.Controls.Input.ActiveEditorForDialogDestroyed = designEditor;
26676
+ Fit.Dom.Remove(me.GetDomElement());
26677
+
26678
+ // Detect memory leak
26679
+ /* setTimeout(function()
26680
+ {
26681
+ if (me !== null)
26682
+ {
26683
+ Fit.Browser.Log("WARNING: Input in DesignMode was not properly disposed in time - potential memory leak detected");
26684
+ }
26685
+ }, 5000); // Usually the load time for a dialog is barely measurable, so 5 seconds seems sufficient */
26686
+
26687
+ return;
26688
+ }
26689
+ else
26690
+ {
26691
+ Fit._internal.Controls.Input.ActiveDialogForEditor.hide(); // Fires dialog's OnHide event
26692
+ }
26693
+ }
26694
+
26695
+ if (designEditor !== null)
26696
+ {
26697
+ // Destroying editor also fires OnHide event for any dialog currently open, which will clean up
26698
+ // Fit._internal.Controls.Input.ActiveEditorForDialog;
26699
+ // Fit._internal.Controls.Input.ActiveEditorForDialogDestroyed;
26700
+ // Fit._internal.Controls.Input.ActiveEditorForDialogDisabledPostponed;
26701
+ // Fit._internal.Controls.Input.ActiveDialogForEditor;
26702
+ // Fit._internal.Controls.Input.ActiveDialogForEditorCanceled;
26703
+
26704
+ destroyDesignEditorInstance(); // Destroys editor and stops related mutation observers, timers, etc.
26705
+ }
26706
+
26707
+ Fit.Internationalization.RemoveOnLocaleChanged(localize);
26708
+
26709
+ /*if (designEditorUpdateSizeDebouncer !== -1)
26710
+ {
26711
+ clearTimeout(designEditorUpdateSizeDebouncer);
26712
+ }
26713
+
26714
+ if (mutationObserverId !== -1)
26715
+ {
26716
+ Fit.Events.RemoveMutationObserver(mutationObserverId);
26717
+ }
26718
+
26719
+ if (rootedEventId !== -1)
26720
+ {
26721
+ Fit.Events.RemoveHandler(me.GetDomElement(), rootedEventId);
26722
+ }
26723
+
26724
+ if (createWhenReadyIntervalId !== -1)
26725
+ {
26726
+ clearInterval(createWhenReadyIntervalId);
26727
+ }*/
26728
+
26729
+ if (debouncedOnChange !== null)
26730
+ {
26731
+ debouncedOnChange.Cancel();
26732
+ }
26733
+
26734
+ if (designEditorConfig === null || !designEditorConfig.Plugins || !designEditorConfig.Plugins.Images || !designEditorConfig.Plugins.Images.RevokeBlobUrlsOnDispose || designEditorConfig.Plugins.Images.RevokeBlobUrlsOnDispose === "All")
26735
+ {
26736
+ Fit.Array.ForEach(imageBlobUrls, function(imageUrl)
26737
+ {
26738
+ URL.revokeObjectURL(imageUrl);
26739
+ });
26740
+ }
26741
+ else // UnreferencedOnly
26742
+ {
26743
+ Fit.Array.ForEach(imageBlobUrls, function(imageUrl)
26744
+ {
26745
+ if (curVal.match(new RegExp("img.*src=([\"'])" + imageUrl + "\\1", "i")) === null)
26746
+ {
26747
+ URL.revokeObjectURL(imageUrl);
26748
+ }
26749
+ });
26750
+ }
26751
+
26752
+ 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;
26753
+
26754
+ base();
26755
+ });
26756
+
26757
+ // See documentation on ControlBase
26758
+ this.Width = Fit.Core.CreateOverride(this.Width, function(val, unit)
26759
+ {
26760
+ Fit.Validation.ExpectNumber(val, true);
26761
+ Fit.Validation.ExpectStringValue(unit, true);
26762
+
26763
+ if (Fit.Validation.IsSet(val) === true)
26764
+ {
26765
+ me._internal.Data("resized", "false");
26766
+
26767
+ base(val, unit);
26768
+ updateDesignEditorSize();
26769
+ }
26770
+
26771
+ return base();
26772
+ });
26773
+
26774
+ // See documentation on ControlBase
26775
+ this.Height = Fit.Core.CreateOverride(this.Height, function(val, unit, suppressMinMax)
26776
+ {
26777
+ Fit.Validation.ExpectNumber(val, true);
26778
+ Fit.Validation.ExpectStringValue(unit, true);
26779
+ Fit.Validation.ExpectBoolean(suppressMinMax, true);
26780
+
26781
+ if (Fit.Validation.IsSet(val) === true)
26782
+ {
26783
+ // Restore/minimize control if currently maximized
26784
+ if (me.Maximizable() === true && suppressMinMax !== true)
26785
+ {
26786
+ me.Maximized(false);
26787
+ }
26788
+
26789
+ me._internal.Data("resized", "false");
26790
+ me._internal.Data("autogrow", me.DesignMode() === true ? "false" : null);
26791
+
26792
+ var autoGrowEnabled = false;
26793
+ if (val === -1 && designModeEnabledAndReady() === true) // Enable auto grow if editor is loaded and ready - otherwise enabled in instanceReady handler
26794
+ {
26795
+ // A value of -1 is used to reset control height (assume default height).
26796
+ // In DesignMode we want the control height to adjust to the content of the editor in this case.
26797
+ // The editor's ability to adjust to the HTML content is handled in updateDesignEditorSize() below.
26798
+ // Auto grow can also be enabled using configuration object passed to DesignMode(true, config).
26799
+ me._internal.Data("autogrow", "true"); // Make control container adjust to editor's height
26800
+ autoGrowEnabled = true;
26801
+ }
26802
+
26803
+ var hideToolbarAgain = false;
26804
+ if (isToolbarHiddenInDesignEditor() === true)
26805
+ {
26806
+ // If in DesignMode, temporarily restore toolbar to allow update to height.
26807
+ // When toolbar is hidden, a fixed height is set on the editable area which
26808
+ // prevent changes to control height.
26809
+ restoreHiddenToolbarInDesignEditor();
26810
+ hideToolbarAgain = true;
26811
+ }
26812
+
26813
+ var h = base(val, unit);
26814
+ updateDesignEditorSize();
26815
+
26816
+ if (hideToolbarAgain === true)
26817
+ {
26818
+ hideToolbarInDesignMode();
26819
+ }
26820
+
26821
+ // Calculate new maximize height if control is maximizable
26822
+ if (me.Maximizable() === true && suppressMinMax !== true)
26823
+ {
26824
+ minimizeHeight = h.Value;
26825
+ maximizeHeight = (maximizeHeightConfigured !== -1 ? maximizeHeightConfigured : (minimizeHeight !== -1 ? minimizeHeight * 2 : 300));
26826
+ minMaxUnit = h.Unit;
26827
+ }
26828
+
26829
+ if (autoGrowEnabled === true) // Repaint in case auto grow was enabled above
26830
+ {
26831
+ repaint();
26832
+ }
26833
+ }
26834
+
26835
+ return base();
26836
+ });
26837
+
26838
+ // ============================================
26839
+ // Public
26840
+ // ============================================
26841
+
26842
+ /// <function container="Fit.Controls.Input" name="Placeholder" access="public" returns="string">
26843
+ /// <description> Get/set value used as a placeholder to indicate expected input on supported browsers </description>
26844
+ /// <param name="val" type="string" default="undefined"> If defined, value is set as placeholder </param>
26845
+ /// </function>
26846
+ this.Placeholder = function(val)
26847
+ {
26848
+ Fit.Validation.ExpectString(val, true);
26849
+
26850
+ if (Fit.Validation.IsSet(val) === true)
26851
+ {
26852
+ input.placeholder = val;
26853
+ updateDesignEditorPlaceholder();
26854
+ }
26855
+
26856
+ return (input.placeholder ? input.placeholder : "");
26857
+ }
26858
+
26859
+ /// <function container="Fit.Controls.Input" name="CheckSpelling" access="public" returns="boolean">
26860
+ /// <description> Get/set value indicating whether control should have spell checking enabled (default) or disabled </description>
26861
+ /// <param name="val" type="boolean" default="undefined"> If defined, true enables spell checking while false disables it </param>
26862
+ /// </function>
26863
+ this.CheckSpelling = function(val)
26864
+ {
26865
+ Fit.Validation.ExpectBoolean(val, true);
26866
+
26867
+ if (Fit.Validation.IsSet(val) === true)
26868
+ {
26869
+ if (val !== input.spellcheck)
26870
+ {
26871
+ input.spellcheck = val;
26872
+
26873
+ if (me.DesignMode() === true)
26874
+ {
26875
+ reloadEditor();
26876
+ }
26877
+ }
26878
+ }
26879
+
26880
+ return input.spellcheck;
26881
+ }
26882
+
26883
+ /// <function container="Fit.Controls.Input" name="Type" access="public" returns="Fit.Controls.InputType">
26884
+ /// <description> Get/set input type (e.g. Text, Password, Email, etc.) </description>
26885
+ /// <param name="val" type="Fit.Controls.InputType" default="undefined"> If defined, input type is changed to specified value </param>
26886
+ /// </function>
26887
+ this.Type = function(val)
26888
+ {
26889
+ Fit.Validation.ExpectStringValue(val, true);
26890
+
26891
+ if (Fit.Validation.IsSet(val) === true)
26892
+ {
26893
+ if (Fit.Validation.IsSet(Fit.Controls.InputType[val]) === false || val === Fit.Controls.InputType.Unknown)
26894
+ Fit.Validation.ThrowError("Unsupported input type specified - use e.g. Fit.Controls.InputType.Text");
26895
+
26896
+ if (val === Fit.Controls.InputType.Textarea)
26897
+ {
26898
+ me.MultiLine(true);
26899
+ }
26900
+ else
26901
+ {
26902
+ me.MultiLine(false);
26903
+
26904
+ if (val === Fit.Controls.InputType.Color)
26905
+ input.type = "color";
26906
+ else if (val === Fit.Controls.InputType.Date)
26907
+ input.type = "date";
26908
+ else if (val === Fit.Controls.InputType.DateTime)
26909
+ input.type = "datetime";
26910
+ else if (val === Fit.Controls.InputType.Email)
26911
+ input.type = "email";
26912
+ else if (val === Fit.Controls.InputType.Month)
26913
+ input.type = "month";
26914
+ else if (val === Fit.Controls.InputType.Number)
26915
+ input.type = "number";
26916
+ else if (val === Fit.Controls.InputType.Password)
26917
+ input.type = "password";
26918
+ else if (val === Fit.Controls.InputType.PhoneNumber)
26919
+ input.type = "tel";
26920
+ else if (val === Fit.Controls.InputType.Text)
26921
+ input.type = "text";
26922
+ else if (val === Fit.Controls.InputType.Time)
26923
+ input.type = "time";
26924
+ else if (val === Fit.Controls.InputType.Week)
26925
+ input.type = "week";
26926
+ }
26927
+ }
26928
+
26929
+ if (me.MultiLine() === true || me.DesignMode() === true)
26930
+ return Fit.Controls.InputType.Textarea;
26931
+ else if (input.type === "color")
26932
+ return Fit.Controls.InputType.Color;
26933
+ else if (input.type === "date")
26934
+ return Fit.Controls.InputType.Date;
26935
+ else if (input.type === "datetime")
26936
+ return Fit.Controls.InputType.DateTime;
26937
+ else if (input.type === "email")
26938
+ return Fit.Controls.InputType.Email;
26939
+ else if (input.type === "month")
26940
+ return Fit.Controls.InputType.Month;
26941
+ else if (input.type === "number")
26942
+ return Fit.Controls.InputType.Number;
26943
+ else if (input.type === "password")
26944
+ return Fit.Controls.InputType.Password;
26945
+ else if (input.type === "tel")
26946
+ return Fit.Controls.InputType.PhoneNumber;
26947
+ else if (input.type === "text")
26948
+ return Fit.Controls.InputType.Text;
26949
+ else if (input.type === "time")
26950
+ return Fit.Controls.InputType.Time;
26951
+ else if (input.type === "week")
26952
+ return Fit.Controls.InputType.Week;
26953
+
26954
+ return Fit.Controls.InputType.Unknown; // Only happens if someone changed the type to an unsupported value through the DOM (e.g. hidden or checkbox)
26955
+ }
26956
+
26957
+ /// <function container="Fit.Controls.Input" name="MultiLine" access="public" returns="boolean">
26958
+ /// <description> Get/set value indicating whether control is in Multi Line mode (textarea) </description>
26959
+ /// <param name="val" type="boolean" default="undefined"> If defined, True enables Multi Line mode, False disables it </param>
26960
+ /// </function>
26961
+ this.MultiLine = function(val)
26962
+ {
26963
+ Fit.Validation.ExpectBoolean(val, true);
26964
+
26965
+ if (Fit.Validation.IsSet(val) === true)
26966
+ {
26967
+ if (me.DesignMode() === true && designModeEnabledAndReady() === false)
26968
+ {
26969
+ console.error("MultiLine(boolean) is not allowed for Input control '" + me.GetId() + "' while DesignMode editor is initializing!");
26970
+ return false; // Return current un-modified state - Input control is not in MultiLine mode when DesignMode is enabled
26971
+ }
26972
+
26973
+ if (me.DesignMode() === true)
26974
+ me.DesignMode(false);
26975
+
26976
+ if (val === true && wasAutoChangedToMultiLineMode === true)
26977
+ {
26978
+ wasAutoChangedToMultiLineMode = false;
26979
+ }
26980
+
26981
+ if (val === true && input.tagName === "INPUT")
26982
+ {
26983
+ var focused = me.Focused();
26984
+
26985
+ var oldInput = input;
26986
+ me._internal.RemoveDomElement(oldInput);
26987
+
26988
+ input = document.createElement("textarea");
26989
+ input.value = oldInput.value;
26990
+ input.spellcheck = oldInput.spellcheck;
26991
+ input.placeholder = oldInput.placeholder;
26992
+ input.disabled = oldInput.disabled;
26993
+ input.onkeyup = oldInput.onkeyup;
26994
+ input.onchange = oldInput.onchange;
26995
+ me._internal.AddDomElement(input);
26996
+
26997
+ if (nativeResizableAvailable === true)
26998
+ {
26999
+ Fit.Events.AddHandler(input, "mousemove", function(e)
27000
+ {
27001
+ var ev = Fit.Events.GetEvent(e);
27002
+
27003
+ if (ev.buttons !== 1) // The .buttons property does not exist in older browsers (see nativeResizableAvailable)
27004
+ {
27005
+ return; // Skip - primary button not held down - not resizing
27006
+ }
27007
+
27008
+ if (me.Resizable() !== Fit.Controls.InputResizing.Disabled && (input.style.width !== "" || input.style.height !== "")) // Textarea was resized
27009
+ {
27010
+ me._internal.Data("resized", "true");
27011
+ }
27012
+ });
27013
+ }
27014
+
27015
+ if (focused === true)
27016
+ input.focus();
27017
+
27018
+ me._internal.Data("multiline", "true");
27019
+ repaint();
27020
+ }
27021
+ else if (val === false && input.tagName === "TEXTAREA")
27022
+ {
27023
+ var focused = me.Focused();
27024
+
27025
+ var oldInput = input;
27026
+ me._internal.RemoveDomElement(oldInput);
27027
+
27028
+ if (cmdResize !== null)
27029
+ {
27030
+ me._internal.RemoveDomElement(cmdResize);
27031
+ cmdResize = null;
27032
+
27033
+ me._internal.Data("maximized", "false");
27034
+ me._internal.Data("maximizable", "false");
27035
+ repaint();
27036
+ }
27037
+ else if (resizable !== Fit.Controls.InputResizing.Disabled)
27038
+ {
27039
+ resizable = Fit.Controls.InputResizing.Disabled;
27040
+ me._internal.Data("resizable", resizable.toLowerCase());
27041
+ me._internal.Data("resized", "false");
27042
+ }
27043
+
27044
+ input = document.createElement("input");
27045
+ input.autocomplete = "off";
27046
+ input.type = "text";
27047
+ input.value = oldInput.value;
27048
+ input.spellcheck = oldInput.spellcheck;
27049
+ input.placeholder = oldInput.placeholder;
27050
+ input.disabled = oldInput.disabled;
27051
+ input.onkeyup = oldInput.onkeyup;
27052
+ input.onchange = oldInput.onchange;
27053
+ me._internal.AddDomElement(input);
27054
+
27055
+ me.Height(-1);
27056
+
27057
+ if (focused === true)
27058
+ input.focus();
27059
+
27060
+ wasAutoChangedToMultiLineMode = false;
27061
+
27062
+ me._internal.Data("multiline", "false");
27063
+ repaint();
27064
+ }
27065
+ }
27066
+
27067
+ return (input.tagName === "TEXTAREA" && me.DesignMode() === false);
27068
+ }
27069
+
27070
+ /// <function container="Fit.Controls.Input" name="Resizable" access="public" returns="Fit.Controls.InputResizing">
27071
+ /// <description>
27072
+ /// Get/set value indicating whether control is resizable on supported
27073
+ /// (modern) browsers. Making control resizable will disable Maximizable.
27074
+ /// </description>
27075
+ /// <param name="val" type="Fit.Controls.InputResizing" default="undefined">
27076
+ /// If defined, determines whether control resizes, and in what direction(s).
27077
+ /// </param>
27078
+ /// </function>
27079
+ this.Resizable = function(val)
27080
+ {
27081
+ Fit.Validation.ExpectStringValue(val, true);
27082
+
27083
+ if (val === Fit.Controls.InputResizing.Enabled || val === Fit.Controls.InputResizing.Disabled || val === Fit.Controls.InputResizing.Horizontal || val === Fit.Controls.InputResizing.Vertical)
27084
+ {
27085
+ if (val !== resizable)
27086
+ {
27087
+ if (val !== Fit.Controls.InputResizing.Disabled) // Resizing enabled
27088
+ {
27089
+ if (me.Maximizable() === true)
27090
+ {
27091
+ me.Maximizable(false);
27092
+ //Fit.Browser.Log("Maximizable disabled as Resizable was enabled!");
27093
+ }
27094
+
27095
+ if (me.MultiLine() === false && me.DesignMode() === false)
27096
+ {
27097
+ me.MultiLine(true);
27098
+ wasAutoChangedToMultiLineMode = true;
27099
+ }
27100
+ }
27101
+ else // Resizing disabled
27102
+ {
27103
+ // Reset custom width/height set by user
27104
+
27105
+ var w = me.Width();
27106
+ me.Width(w.Value, w.Unit);
27107
+
27108
+ var h = me.Height();
27109
+ me.Height(h.Value, h.Unit);
27110
+ }
27111
+
27112
+ resizable = val;
27113
+ me._internal.Data("resizable", val.toLowerCase());
27114
+
27115
+ if (val === Fit.Controls.InputResizing.Disabled)
27116
+ {
27117
+ me._internal.Data("resized", "false");
27118
+
27119
+ input.style.width = "";
27120
+ input.style.height = "";
27121
+ input.style.margin = ""; // Chrome adds some odd margin when textarea is resized
27122
+ }
27123
+
27124
+ revertToSingleLineIfNecessary();
27125
+
27126
+ if (me.DesignMode() === true)
27127
+ {
27128
+ reloadEditor();
27129
+ }
27130
+ }
27131
+ }
27132
+
27133
+ return resizable;
27134
+ }
27135
+
27136
+ /// <function container="Fit.Controls.Input" name="Maximizable" access="public" returns="boolean">
27137
+ /// <description>
27138
+ /// Get/set value indicating whether control is maximizable.
27139
+ /// Making control maximizable will disable Resizable.
27140
+ /// </description>
27141
+ /// <param name="val" type="boolean" default="undefined"> If defined, True enables maximize button, False disables it </param>
27142
+ /// <param name="heightMax" type="number" default="undefined">
27143
+ /// If defined, this becomes the height of the input control when maximized.
27144
+ /// The value is considered the same unit set using Height(..) which defaults to px.
27145
+ /// If not set, the value assumes twice the height set using Height(..).
27146
+ /// </param>
27147
+ /// </function>
27148
+ this.Maximizable = function(val, heightMax)
27149
+ {
27150
+ Fit.Validation.ExpectBoolean(val, true);
27151
+ Fit.Validation.ExpectNumber(heightMax, true);
27152
+
27153
+ if (Fit.Validation.IsSet(val) === true)
27154
+ {
27155
+ if (val === true && cmdResize === null)
27156
+ {
27157
+ if (me.Resizable() !== Fit.Controls.InputResizing.Disabled)
27158
+ {
27159
+ me.Resizable(Fit.Controls.InputResizing.Disabled);
27160
+ //Fit.Browser.Log("Resizable disabled as Maximizable was enabled!");
27161
+ }
27162
+
27163
+ if (me.MultiLine() === false && me.DesignMode() === false)
27164
+ {
27165
+ me.MultiLine(true);
27166
+ wasAutoChangedToMultiLineMode = true;
27167
+ }
27168
+
27169
+ // Determine height to use when maximizing and minimizing
27170
+
27171
+ var h = me.Height();
27172
+
27173
+ minimizeHeight = h.Value;
27174
+ maximizeHeight = ((Fit.Validation.IsSet(heightMax) === true) ? heightMax : ((minimizeHeight !== -1) ? minimizeHeight * 2 : 300));
27175
+ minMaxUnit = h.Unit;
27176
+ maximizeHeightConfigured = heightMax || -1;
27177
+
27178
+ // Create maximize/minimize button
27179
+
27180
+ cmdResize = document.createElement("span");
27181
+ cmdResize.tabIndex = -1; // Allow button to temporarily gain focus so control does not fire OnBlur
27182
+ cmdResize.onclick = function()
27183
+ {
27184
+ me.Maximized(!me.Maximized());
27185
+ me.Focused(true);
27186
+ }
27187
+ Fit.Dom.AddClass(cmdResize, "fa");
27188
+ Fit.Dom.AddClass(cmdResize, "fa-chevron-down");
27189
+ me._internal.AddDomElement(cmdResize);
27190
+
27191
+ // Update UI
27192
+
27193
+ me._internal.Data("maximizable", "true");
27194
+ repaint();
27195
+ }
27196
+ else if (val === false && cmdResize !== null)
27197
+ {
27198
+ me._internal.RemoveDomElement(cmdResize);
27199
+ cmdResize = null;
27200
+
27201
+ me.Height(minimizeHeight, minMaxUnit);
27202
+ minimizeHeight = -1;
27203
+ maximizeHeight = -1;
27204
+ minMaxUnit = null;
27205
+
27206
+ me._internal.Data("maximizable", "false"); // Also set in MultiLine(..)
27207
+
27208
+ revertToSingleLineIfNecessary();
27209
+
27210
+ repaint();
27211
+ }
27212
+ else if (val === true && cmdResize !== null && Fit.Validation.IsSet(heightMax) === true)
27213
+ {
27214
+ // Already enabled - just update maximize height
27215
+ maximizeHeight = heightMax !== -1 ? heightMax : minimizeHeight * 2;
27216
+ maximizeHeightConfigured = heightMax;
27217
+ }
27218
+ }
27219
+
27220
+ return (cmdResize !== null);
27221
+ }
27222
+
27223
+ /// <function container="Fit.Controls.Input" name="Maximized" access="public" returns="boolean">
27224
+ /// <description> Get/set value indicating whether control is maximized </description>
27225
+ /// <param name="val" type="boolean" default="undefined"> If defined, True maximizes control, False minimizes it </param>
27226
+ /// </function>
27227
+ this.Maximized = function(val)
27228
+ {
27229
+ Fit.Validation.ExpectBoolean(val, true);
27230
+
27231
+ var autoGrowEnabled = me.Height().Value === -1 && me.DesignMode() === true;
27232
+
27233
+ if (Fit.Validation.IsSet(val) === true && me.Maximizable() === true && autoGrowEnabled === false)
27234
+ {
27235
+ if (val === true && Fit.Dom.HasClass(cmdResize, "fa-chevron-up") === false)
27236
+ {
27237
+ me.Height(maximizeHeight, minMaxUnit, true);
27238
+ Fit.Dom.RemoveClass(cmdResize, "fa-chevron-down");
27239
+ Fit.Dom.AddClass(cmdResize, "fa-chevron-up");
27240
+
27241
+ me._internal.Data("maximized", "true");
27242
+ repaint();
27243
+ }
27244
+ else if (val === false && Fit.Dom.HasClass(cmdResize, "fa-chevron-down") === false)
27245
+ {
27246
+ me.Height(minimizeHeight, minMaxUnit, true);
27247
+ Fit.Dom.RemoveClass(cmdResize, "fa-chevron-up");
27248
+ Fit.Dom.AddClass(cmdResize, "fa-chevron-down");
27249
+
27250
+ me._internal.Data("maximized", "false"); // Also set in MultiLine(..)
27251
+ repaint();
27252
+ }
27253
+ }
27254
+
27255
+ return (cmdResize !== null && Fit.Dom.HasClass(cmdResize, "fa-chevron-up") === true);
27256
+ }
27257
+
27258
+ /// <container name="Fit.Controls.InputTypeDefs.DesignModeConfigPluginsImagesConfig">
27259
+ /// <description> Configuration for image plugins </description>
27260
+ /// <member name="Enabled" type="boolean"> Flag indicating whether to enable image plugins or not (defaults to False) </member>
27261
+ /// <member name="EmbedType" type="'base64' | 'blob'" default="undefined">
27262
+ /// How to store and embed images. Base64 (default) is persistent while blob is temporary
27263
+ /// and must be extracted from memory and uploaded/stored to be permanantly persisted.
27264
+ /// References to blobs can be parsed from the HTML value produced by the editor.
27265
+ /// </member>
27266
+ /// <member name="RevokeBlobUrlsOnDispose" type="'All' | 'UnreferencedOnly'" default="undefined">
27267
+ /// This option is in effect when EmbedType is blob.
27268
+ /// Dispose images from blob storage (revoke blob URLs) added though image plugins when control is disposed.
27269
+ /// If "UnreferencedOnly" is specified, the component using Fit.UI's input control will be responsible for
27270
+ /// disposing referenced blobs. Failing to do so may cause a memory leak. Defaults to All.
27271
+ /// </member>
27272
+ /// <member name="RevokeExternalBlobUrlsOnDispose" type="boolean" default="undefined">
27273
+ /// This option is in effect when EmbedType is blob.
27274
+ /// Dispose images from blob storage (revoke blob URLs) added through Value(..)
27275
+ /// function when control is disposed. Basically ownership of these blobs are handed
27276
+ /// over to the control for the duration of its life time.
27277
+ /// These images are furthermore subject to the rule set in RevokeBlobUrlsOnDispose.
27278
+ /// Defaults to False.
27279
+ /// </member>
27280
+ /// </container>
27281
+
27282
+ /// <container name="Fit.Controls.InputTypeDefs.DesignModeConfigPlugins">
27283
+ /// <description> Additional plugins enabled in DesignMode </description>
27284
+ /// <member name="Emojis" type="boolean" default="undefined"> Plugin(s) related to emoji support (defaults to False) </member>
27285
+ /// <member name="Images" type="Fit.Controls.InputTypeDefs.DesignModeConfigPluginsImagesConfig" default="undefined"> Plugin(s) related to support for images (defaults to False) </member>
27286
+ /// </container>
27287
+
27288
+ /// <container name="Fit.Controls.InputTypeDefs.DesignModeConfigToolbar">
27289
+ /// <description> Toolbar buttons enabled in DesignMode </description>
27290
+ /// <member name="Formatting" type="boolean" default="undefined"> Enable text formatting (bold, italic, underline) (defaults to True) </member>
27291
+ /// <member name="Justify" type="boolean" default="undefined"> Enable text alignment (defaults to True) </member>
27292
+ /// <member name="Lists" type="boolean" default="undefined"> Enable ordered and unordered lists with indentation (defaults to True) </member>
27293
+ /// <member name="Links" type="boolean" default="undefined"> Enable links (defaults to True) </member>
27294
+ /// <member name="Emojis" type="boolean" default="undefined"> Enable emoji button (defaults to False) </member>
27295
+ /// <member name="Images" type="boolean" default="undefined"> Enable image button (defaults to false) </member>
27296
+ /// <member name="Detach" type="boolean" default="undefined"> Enable detach button (defaults to false) </member>
27297
+ /// <member name="Position" type="'Top' | 'Bottom'" default="undefined"> Toolbar position (defaults to Top) </member>
27298
+ /// <member name="Sticky" type="boolean" default="undefined"> Make toolbar stick to edge of scroll container on supported browsers when scrolling (defaults to False) </member>
27299
+ /// <member name="HideInitially" type="boolean" default="undefined"> Hide toolbar until control gains focus (defaults to False) </member>
27300
+ /// </container>
27301
+
27302
+ /// <container name="Fit.Controls.InputTypeDefs.DesignModeConfigInfoPanel">
27303
+ /// <description> Information panel at the top or bottom of the editor, depending on the location of the toolbar </description>
27304
+ /// <member name="Text" type="string" default="undefined"> Text to display </member>
27305
+ /// <member name="Alignment" type="'Left' | 'Center' | 'Right'" default="undefined"> Text alignment - defaults to Center </member>
27306
+ /// </container>
27307
+
27308
+ /// <container name="Fit.Controls.InputTypeDefs.DesignModeTagsOnRequestEventHandlerArgs">
27309
+ /// <description> Request handler event arguments </description>
27310
+ /// <member name="Sender" type="Fit.Controls.Input"> Instance of control </member>
27311
+ /// <member name="Request" type="Fit.Http.JsonRequest | Fit.Http.JsonpRequest"> Instance of JsonRequest or JsonpRequest </member>
27312
+ /// <member name="Query" type="{ Marker: string, Query: string }"> Query information </member>
27313
+ /// </container>
27314
+ /// <function container="Fit.Controls.InputTypeDefs" name="DesignModeTagsOnRequest" returns="boolean | void">
27315
+ /// <description> Cancelable request event handler </description>
27316
+ /// <param name="sender" type="Fit.Controls.Input"> Instance of control </param>
27317
+ /// <param name="eventArgs" type="Fit.Controls.InputTypeDefs.DesignModeTagsOnRequestEventHandlerArgs"> Event arguments </param>
27318
+ /// </function>
27319
+
27320
+ /// <container name="Fit.Controls.InputTypeDefs.DesignModeTagsOnResponseJsonTag">
27321
+ /// <description> JSON object representing tag </description>
27322
+ /// <member name="Value" type="string"> Unique value </member>
27323
+ /// <member name="Title" type="string"> Title </member>
27324
+ /// <member name="Icon" type="string" default="undefined"> Optional URL to icon/image </member>
27325
+ /// <member name="Url" type="string" default="undefined"> Optional URL to associate with tag </member>
27326
+ /// <member name="Data" type="string" default="undefined"> Optional data to associate with tag </member>
27327
+ /// <member name="Context" type="string" default="undefined"> Optional context information to associate with tag </member>
27328
+ /// </container>
27329
+ /// <container name="Fit.Controls.InputTypeDefs.DesignModeTagsOnResponseEventHandlerArgs">
27330
+ /// <description> Response handler event arguments </description>
27331
+ /// <member name="Sender" type="Fit.Controls.Input"> Instance of control </member>
27332
+ /// <member name="Request" type="Fit.Http.JsonRequest | Fit.Http.JsonpRequest"> Instance of JsonRequest or JsonpRequest </member>
27333
+ /// <member name="Query" type="{ Marker: string, Query: string }"> Query information </member>
27334
+ /// <member name="Tags" type="Fit.Controls.InputTypeDefs.DesignModeTagsOnResponseJsonTag[]"> Tags received from WebService </member>
27335
+ /// </container>
27336
+ /// <function container="Fit.Controls.InputTypeDefs" name="DesignModeTagsOnResponse">
27337
+ /// <description> Response event handler </description>
27338
+ /// <param name="sender" type="Fit.Controls.Input"> Instance of control </param>
27339
+ /// <param name="eventArgs" type="Fit.Controls.InputTypeDefs.DesignModeTagsOnResponseEventHandlerArgs"> Event arguments </param>
27340
+ /// </function>
27341
+
27342
+ /// <container name="Fit.Controls.InputTypeDefs.DesignModeTagsTagCreatorReturnType">
27343
+ /// <description> JSON object representing tag to be inserted into editor </description>
27344
+ /// <member name="Title" type="string"> Tag title </member>
27345
+ /// <member name="Value" type="string"> Tag value (ID) </member>
27346
+ /// <member name="Type" type="string"> Tag type (marker) </member>
27347
+ /// <member name="Url" type="string" default="undefined"> Optional tag URL </member>
27348
+ /// <member name="Data" type="string" default="undefined"> Optional tag data </member>
27349
+ /// <member name="Context" type="string" default="undefined"> Optional tag context </member>
27350
+ /// </container>
27351
+ /// <container name="Fit.Controls.InputTypeDefs.DesignModeTagsTagCreatorCallbackArgs">
27352
+ /// <description> TagCreator event arguments </description>
27353
+ /// <member name="Sender" type="Fit.Controls.Input"> Instance of control </member>
27354
+ /// <member name="QueryMarker" type="string"> Query marker </member>
27355
+ /// <member name="Tag" type="Fit.Controls.InputTypeDefs.DesignModeTagsOnResponseJsonTag"> Tag received from WebService </member>
27356
+ /// </container>
27357
+ /// <function container="Fit.Controls.InputTypeDefs" name="DesignModeTagsTagCreator" returns="Fit.Controls.InputTypeDefs.DesignModeTagsTagCreatorReturnType | null | void">
27358
+ /// <description>
27359
+ /// Function producing JSON object representing tag to be inserted into editor.
27360
+ /// Returning nothing or Null results in default tag being inserted into editor.
27361
+ /// </description>
27362
+ /// <param name="sender" type="Fit.Controls.Input"> Instance of control </param>
27363
+ /// <param name="eventArgs" type="Fit.Controls.InputTypeDefs.DesignModeTagsTagCreatorCallbackArgs"> Event arguments </param>
27364
+ /// </function>
27365
+
27366
+ /// <container name="Fit.Controls.InputTypeDefs.DesignModeConfigTags">
27367
+ /// <description> Configuration for tags in DesignMode </description>
27368
+ /// <member name="Triggers" type="{ Marker: string, MinimumCharacters?: integer, DebounceQuery?: integer }[]"> Markers triggering tags request and context menu </member>
27369
+ /// <member name="QueryUrl" type="string">
27370
+ /// URL to request data from. Endpoint receives the following payload:
27371
+ /// { Marker: "@", Query: "search" }
27372
+ ///
27373
+ /// Data is expected to be returned in the following format:
27374
+ /// [
27375
+ /// { Value: "t-1", Title: "Tag 1", Icon: "images/img1.jpeg", Url: "show/1", Data: "..." },
27376
+ /// { Value: "t-2", Title: "Tag 2", Icon: "images/img2.jpeg", Url: "show/2", Data: "..." }, ...
27377
+ /// ]
27378
+ ///
27379
+ /// The Value and Title properties are required. The Icon property is optional and must specify the path to an image.
27380
+ /// The Url property is optional and must specify a path to a related page/resource.
27381
+ /// The Data property is optional and allows for additional data to be associated with the tag.
27382
+ /// To hold multiple values, consider using a base64 encoded JSON object:
27383
+ /// btoa(JSON.stringify({ creationDate: new Date(), active: true }))
27384
+ ///
27385
+ /// The data eventuelly results in a tag being added to the editor with the following format:
27386
+ /// <a data-tag-type="@" data-tag-id="unique id 1" data-tag-data="..." data-tag-context="..." href="show/1">Tag name 1</a>
27387
+ /// The data-tag-data and data-tag-context attributes are only declared if the corresponding Data and Context properties are defined in data.
27388
+ /// </member>
27389
+ /// <member name="JsonpCallback" type="string" default="undefined"> Name of URL parameter receiving name of JSONP callback function (only for JSONP services) </member>
27390
+ /// <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>
27391
+ /// <member name="OnRequest" type="Fit.Controls.InputTypeDefs.DesignModeTagsOnRequest" default="undefined">
27392
+ /// Event handler invoked when tags are requested. Request may be canceled by returning False.
27393
+ /// Function receives two arguments:
27394
+ /// Sender (Fit.Controls.Input) and EventArgs object.
27395
+ /// EventArgs object contains the following properties:
27396
+ /// - Sender: Fit.Controls.Input instance
27397
+ /// - Request: Fit.Http.JsonpRequest or Fit.Http.JsonRequest instance
27398
+ /// - Query: Contains query information in its Marker and Query property
27399
+ /// </member>
27400
+ /// <member name="OnResponse" type="Fit.Controls.InputTypeDefs.DesignModeTagsOnResponse" default="undefined">
27401
+ /// Event handler invoked when tags data is received, allowing for data transformation.
27402
+ /// Function receives two arguments:
27403
+ /// Sender (Fit.Controls.Input) and EventArgs object.
27404
+ /// EventArgs object contains the following properties:
27405
+ /// - Sender: Fit.Controls.Input instance
27406
+ /// - Request: Fit.Http.JsonpRequest or Fit.Http.JsonRequest instance
27407
+ /// - Query: Contains query information in its Marker and Query property
27408
+ /// - Tags: JSON tags array received from WebService
27409
+ /// </member>
27410
+ /// <member name="TagCreator" type="Fit.Controls.InputTypeDefs.DesignModeTagsTagCreator" default="undefined">
27411
+ /// Callback invoked when a tag is being inserted into editor, allowing
27412
+ /// for customization to the title and attributes associated with the tag.
27413
+ /// Function receives two arguments:
27414
+ /// Sender (Fit.Controls.Input) and EventArgs object.
27415
+ /// EventArgs object contains the following properties:
27416
+ /// - Sender: Fit.Controls.Input instance
27417
+ /// - QueryMarker: String containing query marker
27418
+ /// - Tag: JSON tag received from WebService
27419
+ /// </member>
27420
+ /// </container>
27421
+
27422
+ /// <container name="Fit.Controls.InputTypeDefs.DesignModeAutoGrow">
27423
+ /// <description> Auto grow configuration </description>
27424
+ /// <member name="Enabled" type="boolean"> Flag indicating whether auto grow feature is enabled or not - on by default if no height is set, or if Height(-1) is set </member>
27425
+ /// <member name="MinimumHeight" type="{ Value: number, Unit?: Fit.TypeDefs.CssUnit }" default="undefined"> Minimum height of editable area </member>
27426
+ /// <member name="MaximumHeight" type="{ Value: number, Unit?: Fit.TypeDefs.CssUnit }" default="undefined"> Maximum height of editable area </member>
27427
+ /// <member name="PreventResizeBeyondMaximumHeight" type="boolean" default="undefined"> Prevent user from resizing editor beyond maximum height (see MaximumHeight property - defaults to False) </member>
27428
+ /// </container>
27429
+
27430
+ /// <container name="Fit.Controls.InputTypeDefs.DesignModeDetachable">
27431
+ /// <description> Detachable configuration </description>
27432
+ /// <member name="Title" type="string" default="undefined"> Dialog title </member>
27433
+ /// <member name="Maximizable" type="boolean" default="undefined"> Flag indicating whether dialog is maximizable </member>
27434
+ /// <member name="Maximized" type="boolean" default="undefined"> Flag indicating whether dialog is initially maximized </member>
27435
+ /// <member name="Draggable" type="boolean" default="undefined"> Flag indicating whether dialog is draggable </member>
27436
+ /// <member name="Resizable" type="boolean" default="undefined"> Flag indicating whether dialog is resizable </member>
27437
+ /// <member name="Width" type="{ Value: number, Unit?: Fit.TypeDefs.CssUnit }" default="undefined"> Dialog width </member>
27438
+ /// <member name="MinimumWidth" type="{ Value: number, Unit?: Fit.TypeDefs.CssUnit }" default="undefined"> Minimum width of dialog </member>
27439
+ /// <member name="MaximumWidth" type="{ Value: number, Unit?: Fit.TypeDefs.CssUnit }" default="undefined"> Maximum Width of dialog </member>
27440
+ /// <member name="Height" type="{ Value: number, Unit?: Fit.TypeDefs.CssUnit }" default="undefined"> Dialog height </member>
27441
+ /// <member name="MinimumHeight" type="{ Value: number, Unit?: Fit.TypeDefs.CssUnit }" default="undefined"> Minimum height of dialog </member>
27442
+ /// <member name="MaximumHeight" type="{ Value: number, Unit?: Fit.TypeDefs.CssUnit }" default="undefined"> Maximum height of dialog </member>
27443
+ /// </container>
27444
+
27445
+ /// <container name="Fit.Controls.InputTypeDefs.DesignModeConfig">
27446
+ /// <description> Configuration for DesignMode </description>
27447
+ /// <member name="Plugins" type="Fit.Controls.InputTypeDefs.DesignModeConfigPlugins" default="undefined"> Plugins configuration </member>
27448
+ /// <member name="Toolbar" type="Fit.Controls.InputTypeDefs.DesignModeConfigToolbar" default="undefined"> Toolbar configuration </member>
27449
+ /// <member name="InfoPanel" type="Fit.Controls.InputTypeDefs.DesignModeConfigInfoPanel" default="undefined"> Information panel configuration </member>
27450
+ /// <member name="Tags" type="Fit.Controls.InputTypeDefs.DesignModeConfigTags" default="undefined"> Tags configuration </member>
27451
+ /// <member name="AutoGrow" type="Fit.Controls.InputTypeDefs.DesignModeAutoGrow" default="undefined"> Auto grow configuration </member>
27452
+ /// <member name="Detachable" type="Fit.Controls.InputTypeDefs.DesignModeDetachable" default="undefined"> Detachable configuration </member>
27453
+ /// </container>
27454
+
27455
+ /// <function container="Fit.Controls.Input" name="DesignMode" access="public" returns="boolean">
27456
+ /// <description>
27457
+ /// Get/set value indicating whether control is in Design Mode allowing for rich HTML content.
27458
+ /// Notice that this control type requires dimensions (Width/Height) to be specified in pixels.
27459
+ /// </description>
27460
+ /// <param name="val" type="boolean" default="undefined"> If defined, True enables Design Mode, False disables it </param>
27461
+ /// <param name="editorConfig" type="Fit.Controls.InputTypeDefs.DesignModeConfig" default="undefined">
27462
+ /// If provided and DesignMode is enabled, configuration is applied when editor is created.
27463
+ /// </param>
27464
+ /// </function>
27465
+ this.DesignMode = function(val, editorConfig)
27466
+ {
27467
+ Fit.Validation.ExpectBoolean(val, true);
27468
+ Fit.Validation.ExpectObject(editorConfig, true);
27469
+ Fit.Validation.ExpectObject((editorConfig || {}).Plugins, true);
27470
+ Fit.Validation.ExpectBoolean(((editorConfig || {}).Plugins || {}).Emojis, true);
27471
+ Fit.Validation.ExpectObject(((editorConfig || {}).Plugins || {}).Images, true);
27472
+ Fit.Validation.ExpectBoolean((((editorConfig || {}).Plugins || {}).Images || {}).Enabled, true);
27473
+ Fit.Validation.ExpectStringValue((((editorConfig || {}).Plugins || {}).Images || {}).EmbedType, true);
27474
+ Fit.Validation.ExpectStringValue((((editorConfig || {}).Plugins || {}).Images || {}).RevokeBlobUrlsOnDispose, true);
27475
+ Fit.Validation.ExpectBoolean((((editorConfig || {}).Plugins || {}).Images || {}).RevokeExternalBlobUrlsOnDispose, true);
27476
+ Fit.Validation.ExpectObject((editorConfig || {}).Toolbar, true);
27477
+ Fit.Validation.ExpectBoolean(((editorConfig || {}).Toolbar || {}).Formatting, true);
27478
+ Fit.Validation.ExpectBoolean(((editorConfig || {}).Toolbar || {}).Justify, true);
27479
+ Fit.Validation.ExpectBoolean(((editorConfig || {}).Toolbar || {}).Lists, true);
27480
+ Fit.Validation.ExpectBoolean(((editorConfig || {}).Toolbar || {}).Links, true);
27481
+ Fit.Validation.ExpectBoolean(((editorConfig || {}).Toolbar || {}).Emojis, true);
27482
+ Fit.Validation.ExpectBoolean(((editorConfig || {}).Toolbar || {}).Images, true);
27483
+ Fit.Validation.ExpectStringValue(((editorConfig || {}).Toolbar || {}).Position, true);
27484
+ Fit.Validation.ExpectBoolean(((editorConfig || {}).Toolbar || {}).Sticky, true);
27485
+ Fit.Validation.ExpectBoolean(((editorConfig || {}).Toolbar || {}).HideInitially, true);
27486
+ Fit.Validation.ExpectBoolean(((editorConfig || {}).Toolbar || {}).Detach, true);
27487
+ Fit.Validation.ExpectObject((editorConfig || {}).InfoPanel, true);
27488
+ Fit.Validation.ExpectString(((editorConfig || {}).InfoPanel || {}).Text, true);
27489
+ Fit.Validation.ExpectString(((editorConfig || {}).InfoPanel || {}).Alignment, true);
27490
+ Fit.Validation.ExpectObject((editorConfig || {}).Tags, true);
27491
+ Fit.Validation.ExpectObject((editorConfig || {}).AutoGrow, true);
27492
+ Fit.Validation.ExpectBoolean(((editorConfig || {}).AutoGrow || {}).Enabled, true);
27493
+ Fit.Validation.ExpectObject(((editorConfig || {}).AutoGrow || {}).MinimumHeight, true);
27494
+ Fit.Validation.ExpectNumber((((editorConfig || {}).AutoGrow || {}).MinimumHeight || {}).Value, true);
27495
+ Fit.Validation.ExpectStringValue((((editorConfig || {}).AutoGrow || {}).MinimumHeight || {}).Unit, true);
27496
+ Fit.Validation.ExpectObject(((editorConfig || {}).AutoGrow || {}).MaximumHeight, true);
27497
+ Fit.Validation.ExpectNumber((((editorConfig || {}).AutoGrow || {}).MaximumHeight || {}).Value, true);
27498
+ Fit.Validation.ExpectStringValue((((editorConfig || {}).AutoGrow || {}).MaximumHeight || {}).Unit, true);
27499
+ Fit.Validation.ExpectBoolean(((editorConfig || {}).AutoGrow || {}).PreventResizeBeyondMaximumHeight, true);
27500
+ Fit.Validation.ExpectObject((editorConfig || {}).Detachable, true);
27501
+ Fit.Validation.ExpectString(((editorConfig || {}).Detachable || {}).Title, true);
27502
+ Fit.Validation.ExpectBoolean(((editorConfig || {}).Detachable || {}).Maximizable, true);
27503
+ Fit.Validation.ExpectBoolean(((editorConfig || {}).Detachable || {}).Maximized, true);
27504
+ Fit.Validation.ExpectBoolean(((editorConfig || {}).Detachable || {}).Draggable, true);
27505
+ Fit.Validation.ExpectBoolean(((editorConfig || {}).Detachable || {}).Resizable, true);
27506
+ Fit.Validation.ExpectObject(((editorConfig || {}).Detachable || {}).Width, true);
27507
+ Fit.Validation.ExpectNumber((((editorConfig || {}).Detachable || {}).Width || {}).Value, true);
27508
+ Fit.Validation.ExpectStringValue((((editorConfig || {}).Detachable || {}).Width || {}).Unit, true);
27509
+ Fit.Validation.ExpectObject(((editorConfig || {}).Detachable || {}).MinimumWidth, true);
27510
+ Fit.Validation.ExpectNumber((((editorConfig || {}).Detachable || {}).MinimumWidth || {}).Value, true);
27511
+ Fit.Validation.ExpectStringValue((((editorConfig || {}).Detachable || {}).MinimumWidth || {}).Unit, true);
27512
+ Fit.Validation.ExpectObject(((editorConfig || {}).Detachable || {}).MaximumWidth, true);
27513
+ Fit.Validation.ExpectNumber((((editorConfig || {}).Detachable || {}).MaximumWidth || {}).Value, true);
27514
+ Fit.Validation.ExpectStringValue((((editorConfig || {}).Detachable || {}).MaximumWidth || {}).Unit, true);
27515
+ Fit.Validation.ExpectObject(((editorConfig || {}).Detachable || {}).Height, true);
27516
+ Fit.Validation.ExpectNumber((((editorConfig || {}).Detachable || {}).Height || {}).Value, true);
27517
+ Fit.Validation.ExpectStringValue((((editorConfig || {}).Detachable || {}).Height || {}).Unit, true);
27518
+ Fit.Validation.ExpectObject(((editorConfig || {}).Detachable || {}).MinimumHeight, true);
27519
+ Fit.Validation.ExpectNumber((((editorConfig || {}).Detachable || {}).MinimumHeight || {}).Value, true);
27520
+ Fit.Validation.ExpectStringValue((((editorConfig || {}).Detachable || {}).MinimumHeight || {}).Unit, true);
27521
+ Fit.Validation.ExpectObject(((editorConfig || {}).Detachable || {}).MaximumHeight, true);
27522
+ Fit.Validation.ExpectNumber((((editorConfig || {}).Detachable || {}).MaximumHeight || {}).Value, true);
27523
+ Fit.Validation.ExpectStringValue((((editorConfig || {}).Detachable || {}).MaximumHeight || {}).Unit, true);
27524
+
27525
+ if (editorConfig && editorConfig.Tags)
27526
+ {
27527
+ Fit.Validation.ExpectTypeArray(editorConfig.Tags.Triggers, function(trigger)
27528
+ {
27529
+ Fit.Validation.ExpectStringValue(trigger.Marker);
27530
+ Fit.Validation.ExpectInteger(trigger.MinimumCharacters, true);
27531
+ Fit.Validation.ExpectInteger(trigger.DebounceQuery, true);
27532
+ });
27533
+ Fit.Validation.ExpectStringValue(editorConfig.Tags.QueryUrl);
27534
+ Fit.Validation.ExpectStringValue(editorConfig.Tags.JsonpCallback, true);
27535
+ Fit.Validation.ExpectInteger(editorConfig.Tags.JsonpTimeout, true);
27536
+ Fit.Validation.ExpectFunction(editorConfig.Tags.OnRequest, true);
27537
+ Fit.Validation.ExpectFunction(editorConfig.Tags.OnResponse, true);
27538
+ Fit.Validation.ExpectFunction(editorConfig.Tags.TagCreator, true);
27539
+ }
27540
+
27541
+ if (Fit.Validation.IsSet(val) === true)
27542
+ {
27543
+ var designMode = (me._internal.Data("designmode") === "true");
27544
+
27545
+ if (Fit._internal.Controls.Input.ActiveEditorForDialog === me && Fit._internal.Controls.Input.ActiveEditorForDialogDisabledPostponed === true)
27546
+ designMode = false; // Not considered in Design Mode if scheduled to be disabled (postponed because a dialog is currently loading)
27547
+
27548
+ if ((val === true && designMode === false) || (val === true && Fit.Validation.IsSet(editorConfig) === true && Fit.Core.IsEqual(editorConfig, designEditorConfig) === false))
27549
+ {
27550
+ var configUpdated = designMode === true; // Already in DesignMode which means editorConfig was changed
27551
+
27552
+ if (Fit._internal.Controls.Input.ActiveEditorForDialog === me)
27553
+ {
27554
+ // Control is actually already in Design Mode, but waiting
27555
+ // for dialog to finish loading, so DesignMode can be disabled (scheduled).
27556
+ // Remove flag responsible for disabling DesignMode so it remains an editor.
27557
+ delete Fit._internal.Controls.Input.ActiveEditorForDialogDisabledPostponed;
27558
+
27559
+ if (configUpdated === false)
27560
+ {
27561
+ return true;
27562
+ }
27563
+ }
27564
+
27565
+ if (configUpdated === true)
27566
+ {
27567
+ reloadEditor(false, Fit.Core.Clone(editorConfig)); // Clone to prevent external code from making changes later
27568
+ return true;
27569
+ }
27570
+
27571
+ if (Fit.Validation.IsSet(editorConfig) === true)
27572
+ {
27573
+ designEditorConfig = Fit.Core.Clone(editorConfig); // Clone to prevent external code from making changes later
27574
+ }
27575
+
27576
+ // Notice: Identical logic found in Value(..)!
27577
+ if (designEditorConfig !== null && designEditorConfig.Plugins && designEditorConfig.Plugins.Images && designEditorConfig.Plugins.Images.RevokeExternalBlobUrlsOnDispose === true)
27578
+ {
27579
+ // Keep track of image blobs added via Value(..) so we can dispose of them automatically.
27580
+ // When RevokeExternalBlobUrlsOnDispose is True it basically means that the Input control
27581
+ // is allowed (and expected) to take control over memory management for these blobs
27582
+ // based on the rule set in RevokeBlobUrlsOnDispose.
27583
+ // This code is also found in Value(..) since images might be added after editor has been created.
27584
+
27585
+ var blobUrls = Fit.String.ParseImageBlobUrls(me.Value());
27586
+
27587
+ Fit.Array.ForEach(blobUrls, function(blobUrl)
27588
+ {
27589
+ if (Fit.Array.Contains(imageBlobUrls, blobUrl) === false)
27590
+ {
27591
+ Fit.Array.Add(imageBlobUrls, blobUrl);
27592
+ }
27593
+ });
27594
+ }
27595
+
27596
+ if (me.MultiLine() === false)
27597
+ {
27598
+ me.MultiLine(true);
27599
+ wasAutoChangedToMultiLineMode = true;
27600
+ }
27601
+
27602
+ me._internal.Data("designmode", "true");
27603
+ me._internal.Data("toolbar", designEditorConfig !== null && designEditorConfig.Toolbar && designEditorConfig.Toolbar.HideInitially === true ? "false" : "true");
27604
+ me._internal.Data("toolbar-position", designEditorConfig !== null && designEditorConfig.Toolbar && designEditorConfig.Toolbar.Position === "Bottom" ? "bottom" : "top");
27605
+ me._internal.Data("toolbar-sticky", designEditorConfig !== null && designEditorConfig.Toolbar && designEditorConfig.Toolbar.Sticky === true ? "true" : "false");
27606
+
27607
+ // Prevent control from losing focus when HTML editor is initialized,
27608
+ // e.g. if Design Mode is enabled when ordinary input control gains focus.
27609
+ // This also prevents control from losing focus if toolbar is clicked without
27610
+ // hitting a button. A value of -1 makes it focusable, but keeps it out of
27611
+ // tab flow (keyboard navigation). Also set when Enabled(true) is called.
27612
+ me.GetDomElement().tabIndex = -1; // TabIndex is removed if DesignMode is disabled (DesignMode(false)) or if control is disabled (Enabled(false))
27613
+
27614
+ if (me.Focused() === true)
27615
+ {
27616
+ // Move focus from input to control's outer container (tabindex
27617
+ // set above) to keep focus while editor is loading/initializing.
27618
+ // Focus is moved to editor once initialization is complete - see
27619
+ // instanceReady handler.
27620
+ me.GetDomElement().focus();
27621
+ }
27622
+
27623
+ input.id = me.GetId() + "_DesignMode";
27624
+
27625
+ if (window.CKEDITOR === undefined)
27626
+ {
27627
+ window.CKEDITOR = null;
27628
+
27629
+ Fit.Loader.LoadScript(Fit.GetUrl() + "/Resources/CKEditor/ckeditor.js?cacheKey=" + Fit.GetVersion().Version, function(src) // Using Fit.GetUrl() rather than Fit.GetPath() to allow editor to be used on e.g. JSFiddle (Cross-Origin Resource Sharing policy)
27630
+ {
27631
+ // WARNING: Control could potentially have been disposed at this point, but
27632
+ // we still need to finalize the configuration of CKEditor which is global.
27633
+
27634
+ if (Fit.Validation.IsSet(Fit._internal.Controls.Input.Editor.Skin) === true)
27635
+ {
27636
+ CKEDITOR.config.skin = Fit._internal.Controls.Input.Editor.Skin;
27637
+ }
27638
+
27639
+ CKEDITOR.on("instanceReady", function(ev)
27640
+ {
27641
+ // Do not produce XHTML self-closing tags such as <br /> and <img src="img.jpg" />
27642
+ // https://ckeditor.com/docs/ckeditor4/latest/features/output_format.html
27643
+ // NOTICE: The htmlwriter plugin is required for this to work!
27644
+ // Output produced is now both HTML4 and HTML5 compatible, but is not valid
27645
+ // XHTML anymore! Self-closing tags are allowed in HTML5 but not valid in HTML4.
27646
+ ev.editor.dataProcessor.writer.selfClosingEnd = ">"; // Defaults to ' />'
27647
+ });
27648
+
27649
+ // Register OnShow and OnHide event handlers when a dialog is opened for the first time.
27650
+ // IMPORTANT: These event handlers are shared by all input control instances in Design Mode,
27651
+ // so we cannot use 'me' to access the current control for which a dialog is opened.
27652
+ // Naturally 'me' will always be a reference to the first control that opened a given dialog.
27653
+ CKEDITOR.on("dialogDefinition", function(e) // OnDialogDefinition fires only once
27654
+ {
27655
+ //var dialogName = e.data.name;
27656
+ var dialog = e.data.definition.dialog;
27657
+
27658
+ dialog.on("show", function(ev)
27659
+ {
27660
+ if (Fit._internal.Controls.Input.ActiveDialogForEditorCanceled)
27661
+ {
27662
+ // Focused(false) was called on control while dialog was loading - close dialog
27663
+
27664
+ if (Fit.Browser.GetBrowser() === "MSIE" && Fit.Browser.GetVersion() < 9)
27665
+ {
27666
+ // CKEditor uses setTimeout(..) to focus an input field in the dialog, but if the dialog is
27667
+ // closed immediately, that input field will be removed from DOM along with the dialog of course,
27668
+ // which in IE8 results in an error:
27669
+ // "Can't move focus to the control because it is invisible, not enabled, or of a type that does not accept the focus."
27670
+ // Other browsers simply ignore the request to focus a control that is no longer found in DOM.
27671
+ setTimeout(function()
27672
+ {
27673
+ ev.sender.hide(); // Fires OnHide
27674
+ }, 100);
27675
+ }
27676
+ else
27677
+ {
27678
+ ev.sender.hide(); // Fires OnHide
27679
+ }
27680
+
27681
+ return;
27682
+ }
27683
+
27684
+ if (Fit._internal.Controls.Input.ActiveEditorForDialog === undefined)
27685
+ return; // Control was disposed while waiting for dialog to load and open
27686
+
27687
+ Fit.Dom.Data(ev.sender.getElement().$, "skin", CKEDITOR.config.skin); // Add e.g. data-skin="bootstrapck" to dialog - used in Input.css
27688
+
27689
+ // Keep instance to dialog so we can close it if e.g. Focused(false) is invoked
27690
+ Fit._internal.Controls.Input.ActiveDialogForEditor = ev.sender;
27691
+
27692
+ if (Fit._internal.Controls.Input.ActiveEditorForDialogDestroyed)
27693
+ {
27694
+ // Dispose() was called on control while dialog was loading.
27695
+ // Since destroying editor while a dialog is loading would cause
27696
+ // an error in CKEditor, the operation has been postponed til dialog's
27697
+ // OnShow event fires, and the dialog is ready.
27698
+ setTimeout(function()
27699
+ {
27700
+ // Dispose() calls destroy() on editor which closes dialog and causes the dialog's OnHide event to fire.
27701
+ // Dispose() uses Fit._internal.Controls.Input.ActiveDialogForEditor, which is why it is set above, before
27702
+ // checking whether control has been destroyed (scheduled for destruction).
27703
+ Fit._internal.Controls.Input.ActiveEditorForDialog.Dispose();
27704
+ }, 0); // Postponed - CKEditor throws an error if destroyed from OnShow event handler
27705
+
27706
+ return;
27707
+ }
27708
+
27709
+ if (Fit._internal.Controls.Input.ActiveEditorForDialogDisabledPostponed)
27710
+ {
27711
+ setTimeout(function()
27712
+ {
27713
+ delete Fit._internal.Controls.Input.ActiveEditorForDialogDisabledPostponed;
27714
+
27715
+ // DesignMode(false) calls destroy() on editor which closes dialog and causes the dialog's OnHide event to fire.
27716
+ Fit._internal.Controls.Input.ActiveEditorForDialog.DesignMode(false);
27717
+ }, 0); // Postponed - CKEditor throws an error if destroyed from OnShow event handler
27718
+
27719
+ return;
27720
+ }
27721
+
27722
+ // Allow light dismissable panels/callouts to prevent close/dismiss
27723
+ // when interacting with editor dialogs hosted outside of these panels/callouts,
27724
+ // by detecting the presence of the data-disable-light-dismiss="true" attribute.
27725
+
27726
+ var ckeDialogElement = this.getElement().$;
27727
+ Fit.Dom.Data(ckeDialogElement, "disable-light-dismiss", "true");
27728
+
27729
+ var bgModalLayer = document.querySelector("div.cke_dialog_background_cover"); // Shared among instances
27730
+ if (bgModalLayer !== null) // Better safe than sorry
27731
+ {
27732
+ Fit.Dom.Data(bgModalLayer, "disable-light-dismiss", "true");
27733
+ }
27734
+
27735
+ // Reduce pollution of document root
27736
+
27737
+ if (Fit._internal.ControlBase.ReduceDocumentRootPollution === true)
27738
+ {
27739
+ // Move dialog to control - otherwise placed in the root of the document where it pollutes,
27740
+ // and makes it impossible to interact with the dialog in light dismissable panels and callouts.
27741
+ // Dialog is placed alongside control and not within the control's container, to prevent Fit.UI
27742
+ // styling from affecting the dialog.
27743
+ // DISABLED: It breaks file picker controls in dialogs which are hosted in iframes.
27744
+ // When an iframe is re-rooted in DOM it reloads, and any dynamically created content is lost.
27745
+ // We will have to increase the z-index to make sure dialogs open on top of modal layers.
27746
+ // EDIT 2021-08-20: Enabled again. The base64image plugin has now been altered so it no longer
27747
+ // uses CKEditor's built-in file picker which is wrapped in an iFrame. Therefore the dialog can
27748
+ // once again be mounted next to the Input control.
27749
+
27750
+ var ckeDialogElement = this.getElement().$;
27751
+ Fit.Dom.InsertAfter(Fit._internal.Controls.Input.ActiveEditorForDialog.GetDomElement(), ckeDialogElement);
27752
+
27753
+ // 2nd+ time dialog is opened it remains invisible - make it appear and position it
27754
+ ckeDialogElement.style.display = !CKEDITOR.env.ie || CKEDITOR.env.edge ? "flex" : ""; // https://github.com/ckeditor/ckeditor4/blob/8b208d05d1338d046cdc8f971c9faf21604dd75d/plugins/dialog/plugin.js#L152
27755
+ this.layout(); // 'this' is the dialog instance - layout() positions dialog
27756
+
27757
+ // Temporarily move modal background layer next to control to ensure it is part of same
27758
+ // stacking context as control and dialog. Otherwise it might stay on top of a panel containing
27759
+ // the editor, if that panel has position:fixed (which creates a separate stacking context)
27760
+ // and a lower z-index than the background layer.
27761
+ // Be aware that the background layer also has position:fixed so it will "escape" a parent
27762
+ // container that also has position:fixed, still making it stretch from the upper left corner
27763
+ // of the screen to the lower right corner of the screen. However, if a parent is using
27764
+ // CSS transform, it will prevent the background layer from escaping, instead positioning it
27765
+ // and making it stretch from the upper left corner of the container using transform, to the
27766
+ // lower right corner of that container.
27767
+ // Also be aware that the use of transform will cause minor problems when moving/dragging dialog,
27768
+ // although that is not related to remounting of the background layer.
27769
+ var bgModalLayer = document.querySelector("div.cke_dialog_background_cover");
27770
+ if (bgModalLayer !== null) // Better safe than sorry
27771
+ {
27772
+ Fit.Dom.InsertAfter(Fit._internal.Controls.Input.ActiveEditorForDialog.GetDomElement(), bgModalLayer);
27773
+ }
27774
+
27775
+ if (ev.sender.definition.title === "Image")
27776
+ {
27777
+ // Hide image resize handles placed in the root of the document.
27778
+ // When the modal background layer above is rooted next to the control,
27779
+ // it becomes impossible to ensure the resize handles remains hidden behind
27780
+ // the background layer, since the control may be part of a stacking context
27781
+ // different from the one containing the image resize handles (document root).
27782
+ // It would require dynamic z-index values to achieve this. Therefore the
27783
+ // resize handles are temporarily hidden instead.
27784
+
27785
+ var imageResizeHandlersContainer = document.querySelector("#ckimgrsz");
27786
+ if (imageResizeHandlersContainer !== null) // Better safe than sorry
27787
+ {
27788
+ imageResizeHandlersContainer.style.display = "none";
27789
+ }
27790
+ }
27791
+ }
27792
+ });
27793
+
27794
+ dialog.on("hide", function(ev) // Fires when user closes dialog, or when hide() is called on dialog, or if destroy() is called on editor instance from Dispose() or DesignMode(false)
27795
+ {
27796
+ var inputControl = Fit._internal.Controls.Input.ActiveEditorForDialog;
27797
+ var showCanceledDueToBlur = Fit._internal.Controls.Input.ActiveDialogForEditorCanceled === true;
27798
+
27799
+ // Clean up global references accessible while dialog is open
27800
+ delete Fit._internal.Controls.Input.ActiveEditorForDialog;
27801
+ delete Fit._internal.Controls.Input.ActiveEditorForDialogDestroyed;
27802
+ delete Fit._internal.Controls.Input.ActiveEditorForDialogDisabledPostponed;
27803
+ delete Fit._internal.Controls.Input.ActiveDialogForEditor;
27804
+ delete Fit._internal.Controls.Input.ActiveDialogForEditorCanceled;
27805
+
27806
+ if (Fit._internal.ControlBase.ReduceDocumentRootPollution === true)
27807
+ {
27808
+ // Return modal background layer to document root - it was temporarily moved
27809
+ // next to control to ensure it works properly with current stacking context.
27810
+ // See comments regarding this in dialog "show" handler registered above.
27811
+ // The background layer will not work for other editor instances if not moved back.
27812
+ var bgModalLayer = document.querySelector("div.cke_dialog_background_cover");
27813
+ if (bgModalLayer !== null) // Better safe than sorry
27814
+ {
27815
+ Fit.Dom.Add(document.body, bgModalLayer);
27816
+ }
27817
+
27818
+ // Allow image resize handlers to show up again (hidden in "show" handler registered above)
27819
+ if (ev.sender.definition.title === "Image")
27820
+ {
27821
+ var imageResizeHandlersContainer = document.querySelector("#ckimgrsz");
27822
+ if (imageResizeHandlersContainer !== null) // Better safe than sorry
27823
+ {
27824
+ imageResizeHandlersContainer.style.display = "";
27825
+ }
27826
+ }
27827
+ }
27828
+
27829
+ // Disable focus lock - let ControlBase handle OnFocus and OnBlur automatically again.
27830
+ // This is done postponed since unlocking it immediately will cause OnFocus to fire when
27831
+ // dialog returns focus to the editor.
27832
+ setTimeout(function()
27833
+ {
27834
+ if (inputControl.GetDomElement() === null)
27835
+ return; // Control was disposed - OnHide was fired because destroy() was called on editor instance from Dispose()
27836
+
27837
+ inputControl._internal.FocusStateLocked(false);
27838
+
27839
+ if (showCanceledDueToBlur === true)
27840
+ {
27841
+ // Undo focus which dialog returned to editor.
27842
+ // ControlBase fires OnBlur because focus state was unlocked above.
27843
+ Fit.Dom.GetFocused().blur();
27844
+ }
27845
+ }, 0);
27846
+ });
27847
+ });
27848
+
27849
+ if (me === null)
27850
+ return; // Control was disposed while waiting for jQuery UI to load
27851
+
27852
+ if (me.DesignMode() === false)
27853
+ return; // DesignMode was disabled while waiting for resources to load
27854
+
27855
+ createEditor();
27856
+ });
27857
+ }
27858
+ else if (window.CKEDITOR === null)
27859
+ {
27860
+ if (createWhenReadyIntervalId === -1) // Make sure DesignMode has not been enabled multiple times - e.g. DesignMode(true); DesignMode(false); DesignMode(true); - in which case an interval timer may already be "waiting" for CKEditor resources to finish loading
27861
+ {
27862
+ createWhenReadyIntervalId = setInterval(function()
27863
+ {
27864
+ /*if (me === null)
27865
+ {
27866
+ // Control was disposed while waiting for CKEditor to finish loading
27867
+ clearInterval(iId);
27868
+ return;
27869
+ }*/
27870
+
27871
+ if (window.CKEDITOR !== null)
27872
+ {
27873
+ clearInterval(createWhenReadyIntervalId);
27874
+ createWhenReadyIntervalId = -1;
27875
+
27876
+ // Create editor if still in DesignMode (might have been disabled while waiting for
27877
+ // CKEditor resources to finish loading), and if editor has not already been created.
27878
+ // Editor may already exist if control had DesignMode enabled, then disabled, and then
27879
+ // enabled once again.
27880
+ // If the control is the first one to enabled DesignMode, it will start loading CKEditor
27881
+ // resources and postpone editor creation until resources have finished loading.
27882
+ // When disabled and re-enabled, the control will realize that resources are being loaded,
27883
+ // and postpone editor creation once again, this time using the interval timer here.
27884
+ // When resources are loaded, it will create the editor instances, and when the interval
27885
+ // timer here executes, it will also create the editor instance, unless we prevent it by
27886
+ // making sure only to do it if designEditor is null. Without this check we might experience
27887
+ // the following warning in the browser console, when editor is being created on the same
27888
+ // textarea control multiple times:
27889
+ // [CKEDITOR] Error code: editor-element-conflict. {editorName: "64992ea4-bd01-4081-b606-aa9ff23f417b_DesignMode"}
27890
+ // [CKEDITOR] For more information about this error go to https://ckeditor.com/docs/ckeditor4/latest/guide/dev_errors.html#editor-element-conflict
27891
+ if (me.DesignMode() === true && designEditor === null)
27892
+ {
27893
+ createEditor();
27894
+ }
27895
+ }
27896
+ }, 500);
27897
+ }
27898
+ }
27899
+ else
27900
+ {
27901
+ createEditor();
27902
+ }
27903
+
27904
+ if (me.Resizable() !== Fit.Controls.InputResizing.Disabled) // Undo any resizing done in ordinary MultiLine mode
27905
+ {
27906
+ Fit.Dom.Data(me.GetDomElement(), "resized", "false");
27907
+
27908
+ input.style.width = "";
27909
+ input.style.height = "";
27910
+ input.style.margin = ""; // Chrome adds some odd margin when textarea is resized
27911
+ }
27912
+
27913
+ var enableAutoGrow = me.Height().Value === -1 || (designEditorConfig !== null && designEditorConfig.AutoGrow && designEditorConfig.AutoGrow.Enabled === true);
27914
+
27915
+ if (enableAutoGrow === true && me.Maximizable() === true)
27916
+ {
27917
+ // Maximize button is disabled when auto grow is enabled, but we make sure to "minimize" control
27918
+ // so maximize button returns to its initial state, in case control was maximized prior to enabling
27919
+ // DesignMode with auto grow. Otherwise the button indicates that the control is maximized, and so
27920
+ // does calls to Maximized() which will incorrectly return True.
27921
+ me.Maximized(false);
27922
+ }
27923
+
27924
+ repaint();
27925
+ }
27926
+ else if (val === false && designMode === true)
27927
+ {
27928
+ if (designModeEnabledAndReady() === false)
27929
+ {
27930
+ console.error("DesignMode(false) is not allowed for Input control '" + me.GetId() + "' while DesignMode editor is initializing!");
27931
+ return true; // Return current un-modified state - DesignMode remains enabled
27932
+ }
27933
+
27934
+ var focused = me.Focused();
27935
+
27936
+ if (focused === true) // Make sure focus is preserved when editor is destroyed
27937
+ {
27938
+ me.GetDomElement().focus();
27939
+ }
27940
+
27941
+ if (Fit._internal.Controls.Input.ActiveEditorForDialog === me)
27942
+ {
27943
+ if (Fit._internal.Controls.Input.ActiveDialogForEditor !== null)
27944
+ {
27945
+ Fit._internal.Controls.Input.ActiveDialogForEditor.hide(); // Fires dialog's OnHide event
27946
+ }
27947
+ else
27948
+ {
27949
+ // Dialog is still loading - calling designEditor.destroy() below will cause an error,
27950
+ // leaving a modal layer on the page behind, making it unusable. This may happen if Design Mode is disabled
27951
+ // from e.g. a DOM event handler, a mutation observer, a timer, or an AJAX request. The input control
27952
+ // itself does not fire any events while the dialog is loading which could trigger this situation, so
27953
+ // this can only happen from "external code".
27954
+
27955
+ // WARNING: This has the potential to leak memory if dialog never loads and resumes task of destroying control!
27956
+ Fit._internal.Controls.Input.ActiveEditorForDialogDisabledPostponed = true;
27957
+
27958
+ // Detect memory leak
27959
+ /* setTimeout(function()
27960
+ {
27961
+ if (me !== null && me.DesignMode() === false && Fit._internal.Controls.Input.ActiveEditorForDialog === me)
27962
+ {
27963
+ Fit.Browser.Log("WARNING: Input in DesignMode was not properly disposed in time - potential memory leak detected");
27964
+ }
27965
+ }, 5000); // Usually the load time for a dialog is barely measurable, so 5 seconds seems sufficient */
27966
+
27967
+ return true; // Return current un-modified state - DesignMode remains enabled until dialog is done loading
27968
+ }
27969
+ }
27970
+
27971
+ if (designEditorActiveToolbarPanel !== null)
27972
+ {
27973
+ designEditorActiveToolbarPanel.CloseEmojiPanel();
27974
+ }
27975
+
27976
+ // Destroy editor - content is automatically synchronized to input control.
27977
+ // Calling destroy() fires OnHide for any dialog currently open, which in turn
27978
+ // disables locked focus state and returns focus to the control.
27979
+ destroyDesignEditorInstance();
27980
+
27981
+ me._internal.Data("designmode", "false");
27982
+ Fit.Dom.Data(me.GetDomElement(), "resized", "false");
27983
+
27984
+ // Remove DesignMode specific data attributes
27985
+ me._internal.Data("autogrow", null);
27986
+ me._internal.Data("toolbar", null);
27987
+ me._internal.Data("toolbar-position", null);
27988
+ me._internal.Data("toolbar-sticky", null);
27989
+
27990
+ revertToSingleLineIfNecessary();
27991
+ if (focused === true)
27992
+ {
27993
+ // On IE8 input.focus() does not work if input field is switched to a traditional input field,
27994
+ // or if input field is hidden/invisible. It's just not reliable and not worth it. Remove focus
27995
+ // from the control in IE8 when DesignMode is disabled and preserve focus in every other browser.
27996
+
27997
+ if (isIe8 === false)
27998
+ {
27999
+ input.focus();
28000
+ }
28001
+ else
28002
+ {
28003
+ me.GetDomElement().blur(); // Control container was given focus further up - this will fire OnBlur as expected
28004
+ }
28005
+ }
28006
+
28007
+ // Remove tabindex used to prevent control from losing focus when clicking toolbar buttons
28008
+ Fit.Dom.Attribute(me.GetDomElement(), "tabindex", null);
28009
+
28010
+ repaint();
28011
+ }
28012
+ }
28013
+
28014
+ return (me._internal.Data("designmode") === "true");
28015
+ }
28016
+
28017
+ /// <function container="Fit.Controls.Input" name="DebounceOnChange" access="public" returns="integer">
28018
+ /// <description>
28019
+ /// Get/set number of milliseconds used to postpone onchange event.
28020
+ /// Every new keystroke/change resets the timer. Debouncing can
28021
+ /// improve performance when working with large amounts of data.
28022
+ /// </description>
28023
+ /// <param name="timeout" type="integer"> If defined, timeout value (milliseconds) is updated - a value of -1 disables debouncing </param>
28024
+ /// </function>
28025
+ this.DebounceOnChange = function(timeout)
28026
+ {
28027
+ Fit.Validation.ExpectInteger(timeout);
28028
+
28029
+ if (Fit.Validation.IsSet(debounceOnChangeTimeout) === true && timeout !== debounceOnChangeTimeout)
28030
+ {
28031
+ debounceOnChangeTimeout = timeout;
28032
+
28033
+ if (debouncedOnChange !== null)
28034
+ {
28035
+ debouncedOnChange.Flush();
28036
+ debouncedOnChange = null; // Re-created when needed with new timeout value
28037
+ }
28038
+ }
28039
+
28040
+ return debounceOnChangeTimeout;
28041
+ }
28042
+
28043
+ // ============================================
28044
+ // Protected
28045
+ // ============================================
28046
+
28047
+ this._internal = (this._internal ? this._internal : {});
28048
+
28049
+ this._internal.DesignModeEnabledAndReady = function()
28050
+ {
28051
+ return designModeEnabledAndReady();
28052
+ }
28053
+
28054
+ // ============================================
28055
+ // Private
28056
+ // ============================================
28057
+
28058
+ function createEditor()
28059
+ {
28060
+ // NOTICE: CKEDITOR requires input control to be rooted in DOM.
28061
+ // Creating the editor when Render(..) is called is not the solution, since the programmer
28062
+ // may call GetDomElement() instead and root the element at any given time which is out of our control.
28063
+ // It may be possible to temporarily root the control and make it invisible while the control
28064
+ // is being created, and remove it from the DOM when instanceReady is fired. However, since creating
28065
+ // the editor is an asynchronous operation, we need to detect whether the element has been rooted
28066
+ // elsewhere when instanceCreated is fired, and only remove it from the DOM if this is not the case.
28067
+ // This problem needs to be solved some other time as it may spawn other problems, such as determining
28068
+ // the size of objects while being invisible. The CKEditor team may also solve the bug in an update.
28069
+ if (Fit.Dom.IsRooted(me.GetDomElement()) === false)
28070
+ {
28071
+ //Fit.Validation.ThrowError("Control must be appended/rendered to DOM before DesignMode can be initialized");
28072
+
28073
+ var retry = function()
28074
+ {
28075
+ if (Fit.Dom.IsRooted(me.GetDomElement()) === true)
28076
+ {
28077
+ if (me.DesignMode() === true)
28078
+ {
28079
+ createEditor();
28080
+ }
28081
+
28082
+ return true;
28083
+ }
28084
+
28085
+ // Return False to indicate that we still need to keep retrying (still in DesignMode).
28086
+ // Otherwie return True to indicate success - retrying is no longer relevant.
28087
+ return (me.DesignMode() === true ? false : true);
28088
+ };
28089
+
28090
+ setTimeout(function() // Queue to allow control to be rooted
28091
+ {
28092
+ if (me === null)
28093
+ {
28094
+ return; // Control was disposed
28095
+ }
28096
+
28097
+ if (retry() === false)
28098
+ {
28099
+ // Still not rooted - add observer to create editor instance once control is rooted
28100
+
28101
+ rootedEventId = Fit.Events.AddHandler(me.GetDomElement(), "#rooted", function(e)
28102
+ {
28103
+ if (retry() === true || me.DesignMode() === false)
28104
+ {
28105
+ Fit.Events.RemoveHandler(me.GetDomElement(), rootedEventId);
28106
+ rootedEventId = -1;
28107
+ }
28108
+ });
28109
+ }
28110
+ }, 0);
28111
+
28112
+ return;
28113
+ }
28114
+
28115
+ var langSupport = ["da", "de", "en"];
28116
+ var localeCode = Fit.Internationalization.Locale().length === 2 ? Fit.Internationalization.Locale() : Fit.Internationalization.Locale().substring(0, 2);
28117
+ var lang = Fit.Array.Contains(langSupport, localeCode) === true ? localeCode : "en";
28118
+ var plugins = [];
28119
+ var toolbar = [];
28120
+ var mentions = [];
28121
+
28122
+ var config = designEditorConfig || {};
28123
+
28124
+ // Enable additional plugins not compiled into CKEditor by default
28125
+
28126
+ if ((config.Plugins && config.Plugins.Emojis === true) || (config.Toolbar && config.Toolbar.Emojis === true))
28127
+ {
28128
+ Fit.Array.Add(plugins, "emoji");
28129
+ }
28130
+
28131
+ if (designModeEnableImagePlugin() === true)
28132
+ {
28133
+ if (config.Toolbar && config.Toolbar.Images === true)
28134
+ {
28135
+ Fit.Array.Add(plugins, "base64image");
28136
+ }
28137
+
28138
+ plugins = Fit.Array.Merge(plugins, ["base64imagepaste", "dragresize"]);
28139
+ }
28140
+
28141
+ Fit.Array.Add(plugins, "custombuttons");
28142
+
28143
+ // Add toolbar buttons
28144
+
28145
+ if (!config.Toolbar || config.Toolbar.Formatting !== false)
28146
+ {
28147
+ Fit.Array.Add(toolbar,
28148
+ {
28149
+ name: "BasicFormatting",
28150
+ items: [ "Bold", "Italic", "Underline" ]
28151
+ });
28152
+ }
28153
+
28154
+ if (!config.Toolbar || config.Toolbar.Justify !== false)
28155
+ {
28156
+ Fit.Array.Add(toolbar,
28157
+ {
28158
+ name: "Justify",
28159
+ items: [ "JustifyLeft", "JustifyCenter", "JustifyRight" ]
28160
+ });
28161
+ }
28162
+
28163
+ if (!config.Toolbar || config.Toolbar.Lists !== false)
28164
+ {
28165
+ Fit.Array.Add(toolbar,
28166
+ {
28167
+ name: "Lists",
28168
+ items: [ "NumberedList", "BulletedList", "Indent", "Outdent" ]
28169
+ });
28170
+ }
28171
+
28172
+ if (!config.Toolbar || config.Toolbar.Links !== false)
28173
+ {
28174
+ Fit.Array.Add(toolbar,
28175
+ {
28176
+ name: "Links",
28177
+ items: [ "Link", "Unlink" ]
28178
+ });
28179
+ }
28180
+
28181
+ if (config.Toolbar)
28182
+ {
28183
+ var insert = [];
28184
+
28185
+ if (config.Toolbar.Emojis === true)
28186
+ {
28187
+ Fit.Array.Add(insert, "EmojiPanel");
28188
+ }
28189
+
28190
+ if (config.Toolbar.Images === true)
28191
+ {
28192
+ Fit.Array.Add(insert, "base64image");
28193
+ }
28194
+
28195
+ if (insert.length > 0)
28196
+ {
28197
+ Fit.Array.Add(toolbar,
28198
+ {
28199
+ name: "Insert",
28200
+ items: insert
28201
+ });
28202
+ }
28203
+
28204
+ var customButtons = [];
28205
+ var customToolbarGroups = [];
28206
+
28207
+ if (config.Toolbar.Detach === true)
28208
+ {
28209
+ Fit.Array.Add(customButtons,
28210
+ {
28211
+ Label: locale.Detach,
28212
+ Command: "Detach",
28213
+ Icon: Fit.GetUrl() + "/Controls/Input/" + (window.devicePixelRatio === 2 ? "maximize-highres.png" : "maximize.png"),
28214
+ Callback: function(args)
28215
+ {
28216
+ //console.log("Command " + args.Command.name + " executed", args);
28217
+ openDetachedDesignEditor();
28218
+ }
28219
+ });
28220
+
28221
+ /*Fit.Array.Add(customButtons,
28222
+ {
28223
+ Label: "Testing 1-2-3",
28224
+ Command: "TestButton",
28225
+ Icon: "/files/images/Bird.png",
28226
+ Callback: function(args)
28227
+ {
28228
+ alert("Hello world");
28229
+ }
28230
+ });*/
28231
+
28232
+ Fit.Array.Add(customToolbarGroups,
28233
+ {
28234
+ name: "DetachableEditor",
28235
+ items: ["Detach"/*, "TestButton"*/]
28236
+ });
28237
+ }
28238
+
28239
+ toolbar = Fit.Array.Merge(toolbar, customToolbarGroups);
28240
+ }
28241
+
28242
+ // Configure tags/mentions plugin
28243
+
28244
+ if (config.Tags)
28245
+ {
28246
+ var requestAwaiting = null;
28247
+
28248
+ var createEventArgs = function(marker, query, request) // EventsArgs for OnRequest and OnResponse
28249
+ {
28250
+ return { Sender: me, Query: { Marker: marker, Query: query }, Request: request };
28251
+ };
28252
+
28253
+ Fit.Array.ForEach(config.Tags.Triggers, function(trigger)
28254
+ {
28255
+ var mention =
28256
+ {
28257
+ marker: trigger.Marker,
28258
+ minChars: trigger.MinimumCharacters || 0,
28259
+ throttle: 0, // Throttling is not debouncing - it merely ensures that no more than 1 request is made every X milliseconds when value is changed (defaults to 200ms) - real debouncing implemented further down, which reduce and cancel network calls as user types - also a work around for https://github.com/ckeditor/ckeditor4/issues/5036
28260
+ feed: function(args, resolve)
28261
+ {
28262
+ // WebService is expected to return tag items in an array like so:
28263
+ // [ { Title: string, Value: string, Icon?: string, Url?: string, Data?: string }, { ... }, ... ]
28264
+
28265
+ var req = null;
28266
+
28267
+ if (config.Tags.JsonpCallback)
28268
+ {
28269
+ req = new Fit.Http.JsonpRequest(config.Tags.QueryUrl, config.Tags.JsonpCallback);
28270
+ config.Tags.JsonpTimeout && req.Timeout(config.Tags.JsonpTimeout);
28271
+ req.SetParameter("Marker", args.marker);
28272
+ req.SetParameter("Query", args.query);
28273
+ }
28274
+ else
28275
+ {
28276
+ req = new Fit.Http.JsonRequest(config.Tags.QueryUrl);
28277
+ req.SetData({ Marker: args.marker, Query: args.query });
28278
+ }
28279
+
28280
+ if (config.Tags.OnRequest)
28281
+ {
28282
+ var eventArgs = createEventArgs(args.marker, args.query, req);
28283
+
28284
+ if (config.Tags.OnRequest(me, eventArgs) === false)
28285
+ {
28286
+ resolve([]);
28287
+ return;
28288
+ }
28289
+
28290
+ if (eventArgs.Request !== req)
28291
+ {
28292
+ // Support for changing request instans to
28293
+ // take control over webservice communication.
28294
+
28295
+ // Restrict to support for Fit.Http.Request or classes derived from this
28296
+ Fit.Validation.ExpectInstance(eventArgs.Request, Fit.Http.Request);
28297
+
28298
+ req = eventArgs.Request;
28299
+ }
28300
+ }
28301
+
28302
+ var processDataAndResolve = function(items)
28303
+ {
28304
+ if (config.Tags.OnResponse) // OnResponse is allowed to manipulate tags
28305
+ {
28306
+ var eventArgs = Fit.Core.Merge(createEventArgs(args.marker, args.query, req), { Tags: items });
28307
+ config.Tags.OnResponse(me, eventArgs);
28308
+
28309
+ items = eventArgs.Tags; // In case OnResponse event handler assigned new collection
28310
+ }
28311
+
28312
+ Fit.Array.ForEach(items, function(item)
28313
+ {
28314
+ // Set properties required by mentions plugin
28315
+ item.id = item.Value;
28316
+ item.name = item.Title;
28317
+ });
28318
+
28319
+ resolve(items); // Opens context menu immediately if array contain elements, unless user managed to add a space after the search value while waiting for a response, in which case the context menu will not be opened
28320
+
28321
+ if (items.length > 0)
28322
+ {
28323
+ // Calling resolve(..) above immediately opens the context menu from which a tag can be selected
28324
+
28325
+ // Get the autocomplete context menu currently open. There can be only one
28326
+ // such menu open at any time. Each editor can declare multiple autocomplete
28327
+ // context menus since each tag marker is associated with its own context menu.
28328
+ var ctm = document.querySelector("ul.cke_autocomplete_opened");
28329
+
28330
+ if (ctm !== null) // Null if user managed to enter a space after tag search value, before response was received - in this case resolve(..) above will not open the context menu
28331
+ {
28332
+ // Allow light dismissable panels/callouts to prevent close/dismiss
28333
+ // when interacting with tags context menu hosted outside of these panels/callouts,
28334
+ // by detecting the presence of the data-disable-light-dismiss="true" attribute.
28335
+ Fit.Dom.Data(ctm, "disable-light-dismiss", "true");
28336
+
28337
+ if (Fit._internal.ControlBase.ReduceDocumentRootPollution === true)
28338
+ {
28339
+ // Tags context menu is placed in the root of the document where
28340
+ // it pollutes the global scope. Move it next to the Fit.UI control.
28341
+ // We do not mount it within the Fit.UI control as it could cause Fit.UI styles
28342
+ // to take effect on the context menu.
28343
+
28344
+ // Has position:absolute by default, but this may be affected by a positioned
28345
+ // container (offsetParent), so we change it to position:fixed. Downside:
28346
+ // It no longer sticks to the editor when scrolling. However, if a container has CSS
28347
+ // transform set, the context menu's position will be affected and become inaccurate.
28348
+ ctm.style.position = "fixed";
28349
+ Fit.Dom.InsertAfter(me.GetDomElement(), ctm);
28350
+ }
28351
+ }
28352
+ }
28353
+ };
28354
+
28355
+ if (Fit.Core.InstanceOf(req, Fit.Http.JsonpRequest) === true)
28356
+ {
28357
+ req.OnSuccess(function(sender)
28358
+ {
28359
+ var response = req.GetResponse();
28360
+ var items = ((response instanceof Array) ? response : []);
28361
+
28362
+ processDataAndResolve(items);
28363
+ });
28364
+
28365
+ req.OnTimeout(function(sender)
28366
+ {
28367
+ resolve([]);
28368
+ Fit.Validation.ThrowError("Unable to get tags - request did not return data in time (JSONP timeout reached)");
28369
+ });
28370
+ }
28371
+ else
28372
+ {
28373
+ req.OnSuccess(function(sender)
28374
+ {
28375
+ var response = req.GetResponseJson();
28376
+ var items = ((response instanceof Array) ? response : []);
28377
+
28378
+ processDataAndResolve(items);
28379
+ });
28380
+
28381
+ req.OnFailure(function(sender)
28382
+ {
28383
+ resolve([]);
28384
+ Fit.Validation.ThrowError("Unable to get tags - request failed with HTTP Status code " + req.GetHttpStatus());
28385
+ });
28386
+ }
28387
+
28388
+ if (requestAwaiting !== null)
28389
+ {
28390
+ requestAwaiting.Abort();
28391
+ }
28392
+
28393
+ requestAwaiting = req;
28394
+ req.Start();
28395
+ },
28396
+ itemTemplate: function(item) // Item must define "name" and "id" properties - the {name} placeholder is replaced by "@" + the value of the "name" property - to get rid of "@" simply use an alternative property such as nameWithoutTag:"Some username"
28397
+ {
28398
+ if (item.Icon)
28399
+ {
28400
+ return '<li data-id="' + item.Value + '"><img src="' + item.Icon + '" style="width: 24px; height: 24px; border-radius: 24px; vertical-align: middle" alt=""><span style="display: inline-block; width: 165px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; vertical-align: middle; margin-left: 5px">' + item.Title + '</span></li>';
28401
+ }
28402
+ else
28403
+ {
28404
+ return '<li data-id="' + item.Value + '">' + item.Title + '</li>';
28405
+ }
28406
+ },
28407
+ outputTemplate: function(item)
28408
+ {
28409
+ // IMPORTANT: Output produced must respect ACF (Advanced Content Filter).
28410
+ // So the tag produced must be allowed, and any attributes contained must be allowed.
28411
+
28412
+ var alternativeItem = null;
28413
+
28414
+ if (config.Tags.TagCreator)
28415
+ {
28416
+ var callbackArgs = { Sender: me, QueryMarker: trigger.Marker, Tag: Fit.Core.Clone(item) };
28417
+ alternativeItem = config.Tags.TagCreator(me, callbackArgs) || null;
28418
+ }
28419
+
28420
+ // Function should return a link for tags to "just work". Returning a <span> requires the span to be whitelisted in
28421
+ // extraAllowedContent configuration, but even then the editor will continue writing text within the <span> element,
28422
+ // rather than next to it. So one would expect something like: We will assign <span data-tag-type="@" ..>@James Bond</span> to this mission.
28423
+ // But what we get instead is something like: We will assign <span data-tag-type="@" ..>@James Bond to this mission</span>.
28424
+ // The same happens to link tags if the href attribute is removed, which is why we always add it, even when no URL is defined.
28425
+
28426
+ if (alternativeItem !== null)
28427
+ {
28428
+ 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>';
28429
+ }
28430
+ else
28431
+ {
28432
+ 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>';
28433
+ }
28434
+ }
28435
+ };
28436
+
28437
+ if (trigger.DebounceQuery !== 0) // A value of 0 (zero) disables debouncing
28438
+ {
28439
+ // Wrap feed handler in debounce function so that every time it gets invoked, it cancels the previous invocation
28440
+ mention.feed = Fit.Core.CreateDebouncer(mention.feed, trigger.DebounceQuery || 300).Invoke;
28441
+ }
28442
+
28443
+ Fit.Array.Add(mentions, mention);
28444
+ });
28445
+ }
28446
+
28447
+ var onImageAdded = function(args)
28448
+ {
28449
+ if (args.type === "blob")
28450
+ {
28451
+ // For a list of blobs in Chrome see: chrome://blob-internals/
28452
+ // Be aware that garbage is NOT being collected unless needed, so
28453
+ // don't expect the list to update immediately. Garbage collection
28454
+ // can be triggered in Dev Tools > Memory: click the trash can icon.
28455
+ // Make sure to garbage collect from the tab/window running Fit.UI,
28456
+ // NOT from the tab/window listing blobs!
28457
+ // Use https://jsfiddle.net/ute87p1m/6/ to test garbage collection.
28458
+
28459
+ imageBlobUrls.push(args.url);
28460
+ }
28461
+
28462
+ /*// Image data can be retrieved from a blob like this:
28463
+ if (img.src.indexOf("blob:") === 0)
28464
+ {
28465
+ var r = new Fit.Http.Request(img.src); // E.g. "blob:http://localhost:8080/0c5aa2ae-f2ea-414a-af42-53047959ad1b"
28466
+ r.RequestProperties({ responseType: "blob" });
28467
+ r.OnSuccess(function(sender)
28468
+ {
28469
+ var blob = sender.GetResponse();
28470
+
28471
+ var reader = new FileReader();
28472
+ reader.onload = function(ev)
28473
+ {
28474
+ var base64 = ev.target.result;
28475
+ console.log(base64);
28476
+ };
28477
+ reader.readAsDataURL(blob);
28478
+ });
28479
+ r.Start();
28480
+ }*/
28481
+ };
28482
+
28483
+ designEditor = CKEDITOR.replace(me.GetId() + "_DesignMode",
28484
+ {
28485
+ toolbarLocation: designEditorConfig !== null && designEditorConfig.Toolbar && designEditorConfig.Toolbar.Position === "Bottom" ? "bottom" : "top",
28486
+ uiColor: Fit._internal.Controls.Input.Editor.Skin === "moono-lisa" || Fit._internal.Controls.Input.Editor.Skin === null ? "#FFFFFF" : undefined,
28487
+ //allowedContent: true, // http://docs.ckeditor.com/#!/guide/dev_allowed_content_rules and http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-allowedContent
28488
+ 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
28489
+ language: lang,
28490
+ disableNativeSpellChecker: me.CheckSpelling() === false,
28491
+ readOnly: me.Enabled() === false,
28492
+ tabIndex: me.Enabled() === false ? -1 : 0,
28493
+ title: "",
28494
+ width: "100%", // Assume width of container
28495
+ height: me.Height().Value > -1 ? me.Height().Value + me.Height().Unit : "100%", // Height of content area - toolbar and bottom panel takes up additional space - once editor is loaded, the outer dimensions are accurately set using updateDesignEditorSize() - a height of 100% enables auto grow
28496
+ startupFocus: me.Focused() === true ? "end" : false, // Doesn't work when editor is hidden while initializing to avoid flickering - focus is set again when editor is made visible in instanceReady handler, at which point it will place cursor at the end of the editor if startupFocus is set to "end"
28497
+ extraPlugins: plugins.join(","),
28498
+ clipboard_handleImages: false, // Disable native support for image pasting - allow base64imagepaste plugin to handle image data if loaded
28499
+ base64image: // Custom property used by base64image plugin if loaded
28500
+ {
28501
+ storage: designEditorConfig !== null && designEditorConfig.Plugins && designEditorConfig.Plugins.Images && designEditorConfig.Plugins.Images.EmbedType === "blob" ? "blob" : "base64", // "base64" (default) or "blob" - base64 will always be provided by browsers not supporting blob storage
28502
+ onImageAdded: onImageAdded
28503
+ },
28504
+ base64imagepaste: // Custom property used by base64imagepaste plugin if loaded - notice that IE has native support for image pasting as base64 so plugin is not triggered in IE
28505
+ {
28506
+ storage: designEditorConfig !== null && designEditorConfig.Plugins && designEditorConfig.Plugins.Images && designEditorConfig.Plugins.Images.EmbedType === "blob" ? "blob" : "base64", // "base64" (default) or "blob" - base64 will always be provided by browsers not supporting blob storage
28507
+ onImageAdded: onImageAdded
28508
+ },
28509
+ resize_enabled: resizable !== Fit.Controls.InputResizing.Disabled,
28510
+ 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)
28511
+ toolbar: toolbar,
28512
+ removeButtons: "", // Set to empty string to prevent CKEditor from removing buttons such as Underline
28513
+ customButtons: customButtons,
28514
+ mentions: mentions,
28515
+ 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
28516
+ on:
28517
+ {
28518
+ instanceReady: function()
28519
+ {
28520
+ designEditorDom = // Object assignment will make designModeEnabledAndReady() return True, so it must be assigned immediately
28521
+ {
28522
+ OuterContainer: designEditor.container.$,
28523
+ InnerContainer: designEditor.container.$.querySelector(".cke_inner"),
28524
+ Top: designEditor.container.$.querySelector(".cke_top"), // Null if toolbar is placed at the bottom
28525
+ Content: designEditor.container.$.querySelector(".cke_contents"),
28526
+ Editable: designEditor.container.$.querySelector(".cke_editable"),
28527
+ Bottom: designEditor.container.$.querySelector(".cke_bottom") // Only exist if editor is resizable or toolbar is placed at the bottom
28528
+ }
28529
+
28530
+ // Make sure expected DOM elements are present, so we do not have to perform null checks anywhere else in the code.
28531
+ // Notice that designEditorDom.Top is null if toolbar is placed at the bottom, and designEditorDom.Bottom is null
28532
+ // unless toolbar is placed at the bottom, or editor is resizable, which adds a resize handled in the lower right corner.
28533
+ if (designEditorDom.InnerContainer === null || designEditorDom.Content === null || designEditorDom.Editable === null || (designEditorDom.Top === null && designEditorDom.Bottom === null))
28534
+ {
28535
+ throw "One or more editor DOM elements are missing"; // This should only happen if CKEditor changes its DOM structure
28536
+ }
28537
+
28538
+ if (designEditorMustDisposeWhenReady === true)
28539
+ {
28540
+ Fit.Browser.Debug("WARNING: Input control '" + me.GetId() + "' was disposed while initializing DesignMode - now resuming disposal");
28541
+ me.Dispose();
28542
+ return;
28543
+ }
28544
+
28545
+ if (designEditorMustReloadWhenReady === true)
28546
+ {
28547
+ Fit.Browser.Debug("WARNING: Editor for Input control '" + me.GetId() + "' finished loading, but properties affecting editor has changed while initializing - reloading to adjust to changes");
28548
+ reloadEditor(true);
28549
+ return;
28550
+ }
28551
+
28552
+ updateDesignEditorPlaceholder(); // Show/hide placeholder - value might have been set/removed while initializing editor
28553
+
28554
+ Fit.Dom.Data(designEditorDom.OuterContainer, "skin", CKEDITOR.config.skin); // Add e.g. data-skin="bootstrapck" to editor - used in Input.css
28555
+
28556
+ // Enabled state might have been changed while loading.
28557
+ // Unfortunately there is no API for changing the tabIndex.
28558
+ // Similar logic is found in Enabled(..) function.
28559
+ designEditor.setReadOnly(me.Enabled() === false);
28560
+ Fit.Dom.Attribute(designEditorDom.Editable, "tabindex", me.Enabled() === false ? null : "0"); // Preventing focus is only possible by nullifying DOM attribute (these does not work: delete elm.tabIndex; elm.tabIndex = null|undefined|-1)
28561
+
28562
+ if (me.Height().Value === -1 || (designEditorConfig !== null && designEditorConfig.AutoGrow && designEditorConfig.AutoGrow.Enabled === true))
28563
+ {
28564
+ // Enable auto grow - this is done late to allow an initial static height on the outer control which prevents "flickering" while loading
28565
+
28566
+ me.Height(-1); // Make sure auto grow is enabled since it is unlikely external code has done so explicitely by calling Height(-1)
28567
+
28568
+ // Make necessary adjustments to editor DOM for auto grow's min/max height to work
28569
+
28570
+ var editableDiv = designEditorDom.Editable;
28571
+ editableDiv.style.minHeight = designEditorConfig !== null && designEditorConfig.AutoGrow && designEditorConfig.AutoGrow.MinimumHeight ? designEditorConfig.AutoGrow.MinimumHeight.Value + (designEditorConfig.AutoGrow.MinimumHeight.Unit || "px") : ""; // NOTICE: Minimum height of editable area, not control
28572
+ editableDiv.style.maxHeight = designEditorConfig !== null && designEditorConfig.AutoGrow && designEditorConfig.AutoGrow.MaximumHeight ? designEditorConfig.AutoGrow.MaximumHeight.Value + (designEditorConfig.AutoGrow.MaximumHeight.Unit || "px") : ""; // NOTICE: Maximum height of editable area, not control
28573
+
28574
+ // Restrict resizing:
28575
+ // Make sure user cannot resize editor beyond max height of editable area.
28576
+ // Editable area will not "follow" since it is restricted using maxHeight set above.
28577
+ // If we do not want resizing to be restricted, then unset minHeight and maxHeight set
28578
+ // above, when resizing occur (see CKEditor's resize event handler).
28579
+ if (editableDiv.style.maxHeight !== "" && designEditorConfig.AutoGrow.PreventResizeBeyondMaximumHeight === true)
28580
+ {
28581
+ var contents = designEditorDom.Content;
28582
+ contents.style.maxHeight = editableDiv.style.maxHeight;
28583
+ }
28584
+ }
28585
+
28586
+ if (designEditorConfig !== null && designEditorConfig.InfoPanel && designEditorConfig.InfoPanel.Text)
28587
+ {
28588
+ var infoPanel = document.createElement("div");
28589
+ infoPanel.className = "FitUiControlInputInfoPanel";
28590
+ infoPanel.innerHTML = designEditorConfig.InfoPanel.Text;
28591
+ infoPanel.style.cssText = "text-align: " + (designEditorConfig.InfoPanel.Alignment ? designEditorConfig.InfoPanel.Alignment.toLowerCase() : "center");
28592
+
28593
+ if (designEditorConfig !== null && designEditorConfig.Toolbar && designEditorConfig.Toolbar.Position === "Bottom")
28594
+ {
28595
+ Fit.Dom.InsertBefore(designEditorDom.Content, infoPanel);
28596
+ }
28597
+ else
28598
+ {
28599
+ Fit.Dom.InsertAfter(designEditorDom.Content, infoPanel);
28600
+ }
28601
+ }
28602
+
28603
+ // Sticky toolbar - compensate for padding in scroll parent
28604
+
28605
+ if (me._internal.Data("toolbar-sticky") === "true")
28606
+ {
28607
+ var toolbarContainer = designEditorDom.Top || designEditorDom.Bottom;
28608
+
28609
+ if (Fit.Dom.GetComputedStyle(toolbarContainer, "position") === "sticky") // False on non-supported browsers as position:sticky is applied via the @supports CSS rule
28610
+ {
28611
+ var scrollParent = Fit.Dom.GetScrollParent(me.GetDomElement());
28612
+
28613
+ 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
28614
+ {
28615
+ var toolbarPosition = me._internal.Data("toolbar-position"); // top | bottom
28616
+
28617
+ if (toolbarPosition === "top")
28618
+ {
28619
+ var paddingOffset = Fit.Dom.GetComputedStyle(scrollParent, "padding-top"); // E.g. "28px"
28620
+ toolbarContainer.style.top = paddingOffset !== "0px" ? "-" + paddingOffset : "";
28621
+ }
28622
+ else
28623
+ {
28624
+ var paddingOffset = Fit.Dom.GetComputedStyle(scrollParent, "padding-bottom"); // E.g. "28px"
28625
+ toolbarContainer.style.bottom = paddingOffset !== "0px" ? "-" + paddingOffset : "";
28626
+ }
28627
+ }
28628
+ }
28629
+ }
28630
+
28631
+ // Register necessary events with emoji panel when opened
28632
+
28633
+ var emojiButton = designEditor.container.$.querySelector("a.cke_button__emojipanel");
28634
+
28635
+ if (emojiButton !== null) // Better safe than sorry
28636
+ {
28637
+ Fit.Events.AddHandler(emojiButton, "click", function(e)
28638
+ {
28639
+ // Make sure OnFocus fires before locking focus state
28640
+
28641
+ if (me.Focused() === false)
28642
+ {
28643
+ // Control not focused - make sure OnFocus fires when emoji button is clicked,
28644
+ // and make sure ControlBase internally considers itself focused, so there is
28645
+ // no risk of OnFocus being fired twice without OnBlur firing in between,
28646
+ // when focus state is unlocked, and focus is perhaps re-assigned to another
28647
+ // DOM element within the control, which will be the case if the design editor
28648
+ // is switched back to an ordinary input field (e.g. using DesignMode(false)).
28649
+ me.Focused(true);
28650
+ }
28651
+
28652
+ // Prevent control from firing OnBlur when emoji dialog is opened.
28653
+ // Notice that locking the focus state will also prevent OnFocus
28654
+ // from being fired automatically.
28655
+ me._internal.FocusStateLocked(true);
28656
+
28657
+ setTimeout(function() // Postpone - emoji panel is made visible after click event
28658
+ {
28659
+ // Allow light dismissable panels/callouts to prevent close/dismiss
28660
+ // when interacting with emoji widget hosted outside of panels/callouts,
28661
+ // by detecting the presence of a data-disable-light-dismiss="true" attribute.
28662
+ var emojiPanel = document.querySelector("div.cke_emoji-panel"); // Shared among instances
28663
+
28664
+ if (emojiPanel !== null) // Better safe than sorry
28665
+ {
28666
+ Fit.Dom.Data(emojiPanel, "disable-light-dismiss", "true");
28667
+
28668
+ emojiPanel._associatedFitUiControl = me;
28669
+
28670
+ designEditorActiveToolbarPanel =
28671
+ {
28672
+ DomElement: emojiPanel,
28673
+ UnlockFocusStateIfEmojiPanelIsClosed: function() // Function called regularly via interval timer while emoji panel is open to make sure focus state is unlocked when emoji panel is closed, e.g. by pressing ESC, clicking outside of emoji panel, or by choosing an emoji
28674
+ {
28675
+ if (designModeEnabledAndReady() === false /* No longer in DesignMode */ || Fit.Dom.IsVisible(emojiPanel) === false /* Emoji panel closed */ || emojiPanel._associatedFitUiControl !== me /* Emoji panel now opened from another editor */)
28676
+ {
28677
+ designEditorActiveToolbarPanel = null;
28678
+
28679
+ // Disable focus lock - let ControlBase handle OnFocus and OnBlur automatically again
28680
+ me._internal.FocusStateLocked(false);
28681
+
28682
+ // Fire OnBlur in case user changed focus while emoji panel was open.
28683
+ // OnBlur does not fire automatically when focus state is locked.
28684
+ if (me.Focused() === false)
28685
+ {
28686
+ me._internal.FireOnBlur();
28687
+ }
28688
+ }
28689
+ },
28690
+ CloseEmojiPanel: function()
28691
+ {
28692
+ if (emojiPanel._associatedFitUiControl === me && Fit.Dom.IsVisible(emojiPanel) === true && Fit.Dom.Contained(emojiPanel, Fit.Dom.GetFocused()) === true)
28693
+ {
28694
+ designEditor.focus();
28695
+ designEditorActiveToolbarPanel.UnlockFocusStateIfEmojiPanelIsClosed();
28696
+ }
28697
+ }
28698
+ }
28699
+ }
28700
+
28701
+ var checkClosedId = setInterval(function()
28702
+ {
28703
+ // Invoke cleanup function regularly to make sure
28704
+ // focus lock is relased when emoji panel is closed,
28705
+ // and to fire OnBlur if another control was focused
28706
+ // while emoji panel was open.
28707
+
28708
+ if (me === null)
28709
+ {
28710
+ clearInterval(checkClosedId);
28711
+ return;
28712
+ }
28713
+
28714
+ if (designEditorActiveToolbarPanel !== null)
28715
+ {
28716
+ designEditorActiveToolbarPanel.UnlockFocusStateIfEmojiPanelIsClosed(); // Nullfies designEditorActiveToolbarPanel if emoji panel is closed
28717
+ }
28718
+
28719
+ if (designEditorActiveToolbarPanel === null)
28720
+ {
28721
+ clearInterval(checkClosedId);
28722
+ }
28723
+ }, 250);
28724
+ }, 0);
28725
+ });
28726
+ }
28727
+
28728
+ // DISABLED: Doesn't work! Emoji panel contains an iFrame. When it is re-mounted
28729
+ // in DOM, the iframe reloads, and dynamically added content is lost. Also, this makes
28730
+ // CKEditor throw errors and the dialog never appears.
28731
+ /*if (Fit._internal.ControlBase.ReduceDocumentRootPollution === true)
28732
+ {
28733
+ // Move emoji dialog to control - otherwise placed in the root of the document where it pollutes,
28734
+ // and makes it impossible to interact with the dialog in light dismissable panels and callouts.
28735
+ // Dialog is placed alongside control and not within the control's container, to prevent Fit.UI
28736
+ // styling from affecting the dialog.
28737
+ if (config.Toolbar && config.Toolbar.Emojis === true)
28738
+ {
28739
+ var emojiButton = designEditor.container.$.querySelector("a.cke_button__emojipanel");
28740
+
28741
+ if (emojiButton !== null)
28742
+ {
28743
+ Fit.Events.AddHandler(emojiButton, "click", function(e)
28744
+ {
28745
+ setTimeout(function() // Postpone - made visible after click event
28746
+ {
28747
+ var emojiPanel = document.querySelector("div.cke_emoji-panel:not([style*='display: none'])");
28748
+
28749
+ if (emojiPanel !== null)
28750
+ {
28751
+ Fit.Dom.InsertAfter(me.GetDomElement(), emojiPanel);
28752
+ }
28753
+ }, 0);
28754
+ });
28755
+ }
28756
+ }
28757
+ }*/
28758
+
28759
+ // Make editor assume configured width and height.
28760
+ // Notice that using config.width and config.height
28761
+ // (https://ckeditor.com/docs/ckeditor4/latest/features/size.html)
28762
+ // results in editor becoming too high since the toolbar height is not
28763
+ // substracted. This problem does not occur when using updateDesignEditorSize().
28764
+ updateDesignEditorSize();
28765
+
28766
+ if (me.Focused() === false)
28767
+ {
28768
+ // Hide editor toolbar if configured to do so
28769
+ hideToolbarInDesignMode();
28770
+ }
28771
+ else
28772
+ {
28773
+ // Remove placeholder if initially focused
28774
+ updateDesignEditorPlaceholder(true);
28775
+ }
28776
+
28777
+ // Make editor visible - postpone to allow editor to first calculate auto grow height
28778
+ // so the user will not see the chrome (borders) of the editor increase its height.
28779
+ setTimeout(function()
28780
+ {
28781
+ designEditorDom.OuterContainer.style.visibility = "visible";
28782
+
28783
+ // Because editor is hidden while initializing, startupFocus
28784
+ // (https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-startupFocus)
28785
+ // won't be able to place focus in the editor. We resolve this by assigning focus again
28786
+ // once editor is visible (visibility set above). Because startupFocus was set, it will
28787
+ // place focus at the end of the editor as expected.
28788
+ if (me.Focused() === true)
28789
+ {
28790
+ 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
28791
+ }
28792
+ }, 0);
28793
+ },
28794
+ change: function() // CKEditor bug: not fired in Opera 12 (possibly other old versions as well)
28795
+ {
28796
+ if (me._internal.FireOnChangeSuppressed === true)
28797
+ {
28798
+ // Do not process event - it has been fired by CKEditor when HTML
28799
+ // value was initially assigned in Value(..) which happend through
28800
+ // me._internal.ExecuteWithNoOnChange(function() { .. }).
28801
+ // See Value(..) implementation for details.
28802
+ return;
28803
+ }
28804
+
28805
+ // Assume value was changed by user if control has focus
28806
+ if (designEditorDirty === false && me.Focused() === true)
28807
+ {
28808
+ designEditorDirty = true;
28809
+ }
28810
+
28811
+ input.onkeyup();
28812
+ },
28813
+ resize: function() // Fires when size is changed (except via auto grow), not just when resized using resize handle in lower right cornor
28814
+ {
28815
+ if (designEditorSuppressOnResize === false) // Only set data-resized="true" when resized using resize handle
28816
+ {
28817
+ // Disable Min/Max height configured with auto grow feature so user can resize it freely, unless PreventResizeBeyondMaximumHeight is enabled
28818
+ if (designEditorConfig !== null && designEditorConfig.AutoGrow && designEditorConfig.AutoGrow.Enabled === true && designEditorConfig.AutoGrow.PreventResizeBeyondMaximumHeight !== true)
28819
+ {
28820
+ var editableDiv = designEditorDom.Editable;
28821
+ editableDiv.style.minHeight = "";
28822
+ editableDiv.style.maxHeight = "";
28823
+
28824
+ var contents = designEditorDom.Content;
28825
+ contents.style.maxHeight = "";
28826
+ }
28827
+
28828
+ me._internal.Data("resized", "true");
28829
+ repaint();
28830
+ }
28831
+ },
28832
+ selectionChange: function(ev)
28833
+ {
28834
+ var elm = ev.data.selection.getStartElement().$;
28835
+
28836
+ // Allow light dismissable panels/callouts to prevent close/dismiss
28837
+ // when interacting with image resize handles hosted outside of panels/callouts,
28838
+ // by detecting the presence of a data-disable-light-dismiss="true" attribute.
28839
+
28840
+ if (elm.tagName === "IMG")
28841
+ {
28842
+ setTimeout(function() // Postpone - wait for image resize plugin to add image resize handles
28843
+ {
28844
+ var imageResizeHandlesContainer = document.querySelector("#ckimgrsz");
28845
+ if (imageResizeHandlesContainer !== null) // Better safe than sorry
28846
+ {
28847
+ Fit.Dom.Data(imageResizeHandlesContainer, "disable-light-dismiss", "true");
28848
+ }
28849
+ }, 0);
28850
+ }
28851
+
28852
+ // Disable/enable toolbar buttons, depending on whether a tag/mention is selected
28853
+
28854
+ if (elm.tagName === "A" && Fit.Dom.Data(elm, "tag-id") !== null)
28855
+ {
28856
+ // Notice that selectionChange handler is invoked while editor is loading if control was given initial focus.
28857
+ // But at this point the toolbar buttons are not yet available to be disabled, so disableDesignEditorButtons()
28858
+ // won't work. However, as soon as the editor is done loading, focus is re-assigned to the editable area
28859
+ // which will trigger selectionChange handler once again, at which point designModeEnabledAndReady() returns true.
28860
+ if (designModeEnabledAndReady() === true)
28861
+ {
28862
+ designEditorSuppressPaste = true;
28863
+ setTimeout(function() // Postpone - otherwise we won't be able to temporarily disable some of the buttons (https://jsfiddle.net/ymv56znq/14/)
28864
+ {
28865
+ disableDesignEditorButtons();
28866
+ }, 0);
28867
+ }
28868
+ }
28869
+ else
28870
+ {
28871
+ designEditorSuppressPaste = false;
28872
+ restoreDesignEditorButtons();
28873
+ }
28874
+ },
28875
+ doubleclick: function(ev)
28876
+ {
28877
+ // Suppress link dialog when double clicking. User must use link
28878
+ // button instead which triggers beforeCommandExec below - it creates
28879
+ // a focus lock to prevent control from losing focus and firing OnBlur.
28880
+ if (ev.data.element.$.tagName === "A")
24775
28881
  {
24776
28882
  ev.cancel();
24777
28883
  return;
24778
28884
  }
28885
+
28886
+ // Suppress link dialog for tags (similar code found in beforeCommandExec handler below)
28887
+ // DISABLED: No longer needed since link dialog is now suppressed for all links (see code above)
28888
+ /*if (Fit.Dom.Data(ev.data.element.$, "tag-id") !== null)
28889
+ {
28890
+ ev.cancel();
28891
+ return;
28892
+ }*/
24779
28893
  },
24780
28894
  paste: function(ev)
24781
28895
  {
@@ -25142,11 +29256,14 @@ Fit.Controls.Input = function(ctlId)
25142
29256
 
25143
29257
  updateDetachedConfiguration();
25144
29258
 
25145
- // Create dialog
29259
+ // Create dialog and buttons
25146
29260
 
25147
29261
  var dia = new Fit.Controls.Dialog();
25148
29262
  Fit.Dom.AddClass(dia.GetDomElement(), "FitUiControlInputDetached");
25149
29263
 
29264
+ var cmdOk = new Fit.Controls.Button();
29265
+ var cmdCancel = new Fit.Controls.Button();
29266
+
25150
29267
  // Create editor
25151
29268
 
25152
29269
  var de = new Fit.Controls.Input();
@@ -25171,8 +29288,14 @@ Fit.Controls.Input = function(ctlId)
25171
29288
  // so even though the timer interupts browser events such as onmousemove, onscroll, etc.,
25172
29289
  // it creates no lack at all.
25173
29290
  var height = -1
29291
+ var suspendDimensionMonitor = false;
25174
29292
  var dimMonitorId = setInterval(function()
25175
29293
  {
29294
+ if (suspendDimensionMonitor === true)
29295
+ {
29296
+ return;
29297
+ }
29298
+
25176
29299
  if (height === -1 && de._internal.DesignModeEnabledAndReady() === false)
25177
29300
  {
25178
29301
  return;
@@ -25247,6 +29370,62 @@ Fit.Controls.Input = function(ctlId)
25247
29370
  return de.Value();
25248
29371
  },
25249
29372
 
29373
+ SetVisible: function(val)
29374
+ {
29375
+ // Focus state remains locked when toggling visibility.
29376
+ // We merely make sure to invoke OnBlur and OnFocus events.
29377
+ // Focus lock is only released when detached editor is closed
29378
+ // or if control is disposed.
29379
+
29380
+ if (val === false && dia.IsOpen() === true)
29381
+ {
29382
+ dia.Close();
29383
+ me._internal.FireOnBlur();
29384
+ suspendDimensionMonitor = true;
29385
+ }
29386
+ else if (val === true && dia.IsOpen() === false)
29387
+ {
29388
+ dia.Open();
29389
+ de.Focused(true);
29390
+ me._internal.FireOnFocus();
29391
+ suspendDimensionMonitor = false;
29392
+ }
29393
+ },
29394
+
29395
+ SetEnabled: function(val)
29396
+ {
29397
+ if (val === false && de.Enabled() === true)
29398
+ {
29399
+ de.Enabled(false);
29400
+ cmdOk.Enabled(false);
29401
+ cmdCancel.Focused(true);
29402
+ }
29403
+ else if (val === true && de.Enabled() === false)
29404
+ {
29405
+ de.Enabled(true);
29406
+ cmdOk.Enabled(true);
29407
+ de.Focused(true);
29408
+ }
29409
+ },
29410
+
29411
+ Focus: function()
29412
+ {
29413
+ if (de.Enabled() === true)
29414
+ {
29415
+ de.Focused(true);
29416
+ }
29417
+ else
29418
+ {
29419
+ cmdCancel.Focused(true);
29420
+ }
29421
+ },
29422
+
29423
+ // GetFocused: function()
29424
+ // {
29425
+ // return de.Focused() === true /* also returns true if e.g. link/image dialog is open */
29426
+ // || cmdOk.Focused() === true || cmdCancel.Focused() === true;
29427
+ // },
29428
+
25250
29429
  Reload: function()
25251
29430
  {
25252
29431
  // Update configuration
@@ -25272,7 +29451,6 @@ Fit.Controls.Input = function(ctlId)
25272
29451
  }
25273
29452
  };
25274
29453
 
25275
- var cmdOk = new Fit.Controls.Button();
25276
29454
  cmdOk.Title(locale.Ok);
25277
29455
  cmdOk.Icon("check");
25278
29456
  cmdOk.Type(Fit.Controls.ButtonType.Success);
@@ -25296,7 +29474,6 @@ Fit.Controls.Input = function(ctlId)
25296
29474
  });
25297
29475
  dia.AddButton(cmdOk);
25298
29476
 
25299
- var cmdCancel = new Fit.Controls.Button();
25300
29477
  cmdCancel.Title(locale.Cancel);
25301
29478
  cmdCancel.Icon("ban");
25302
29479
  cmdCancel.Type(Fit.Controls.ButtonType.Danger);
@@ -25313,10 +29490,19 @@ Fit.Controls.Input = function(ctlId)
25313
29490
  }
25314
29491
  });
25315
29492
 
25316
- designEditorDetached.Dispose();
29493
+ var enabled = me.Enabled();
29494
+ designEditorDetached.Dispose(); // Dispose first so me.Focused(true) below does not redirect focus to detached editor
25317
29495
 
25318
- me.Focused(true);
25319
- me._internal.FocusStateLocked(false);
29496
+ if (enabled === true) // Return focus to control if it is still enabled - if not, do not return focus and fire OnBlur
29497
+ {
29498
+ me.Focused(true);
29499
+ me._internal.FocusStateLocked(false);
29500
+ }
29501
+ else
29502
+ {
29503
+ me._internal.FocusStateLocked(false);
29504
+ me._internal.FireOnBlur();
29505
+ }
25320
29506
  };
25321
29507
 
25322
29508
  if (de.IsDirty() === true)
@@ -25327,6 +29513,10 @@ Fit.Controls.Input = function(ctlId)
25327
29513
  {
25328
29514
  closeDialog();
25329
29515
  }
29516
+ else
29517
+ {
29518
+ cmdCancel.Focused(true);
29519
+ }
25330
29520
  });
25331
29521
  }
25332
29522
  else