fit-ui 2.4.1 → 2.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/Fit.UI.js CHANGED
@@ -648,7 +648,7 @@ Fit._internal =
648
648
  {
649
649
  Core:
650
650
  {
651
- VersionInfo: { Major: 2, Minor: 4, Patch: 1 } // Do NOT modify format - version numbers are programmatically changed when releasing new versions - MUST be on a separate line!
651
+ VersionInfo: { Major: 2, Minor: 5, Patch: 3 } // Do NOT modify format - version numbers are programmatically changed when releasing new versions - MUST be on a separate line!
652
652
  }
653
653
  };
654
654
 
@@ -3048,6 +3048,8 @@ Fit.Controls.Component = function(controlId)
3048
3048
  container = document.createElement("div");
3049
3049
  container.id = id;
3050
3050
  container._internal = { Instance: me };
3051
+
3052
+ Fit.Dom.Data(container, "device", ((Fit.Browser.GetInfo().IsMobile === false) ? "Desktop" : (Fit.Browser.GetInfo().IsPhone === true) ? "Phone" : "Tablet"));
3051
3053
  }
3052
3054
 
3053
3055
  /// <function container="Fit.Controls.Component" name="GetId" access="public" returns="string">
@@ -3278,8 +3280,6 @@ Fit.Controls.ControlBase = function(controlId)
3278
3280
  me._internal.Data("dirty", "false");
3279
3281
  me._internal.Data("enabled", "true");
3280
3282
 
3281
- me._internal.Data("device", ((Fit.Browser.GetInfo().IsMobile === false) ? "Desktop" : (Fit.Browser.GetInfo().IsPhone === true) ? "Phone" : "Tablet"));
3282
-
3283
3283
  // Add hidden inputs which are automatically populated with
3284
3284
  // control value and state information when control is updated.
3285
3285
 
@@ -15924,8 +15924,15 @@ Fit.Controls.DropDown = function(ctlId)
15924
15924
  {
15925
15925
  var target = Fit.Events.GetTarget(e);
15926
15926
 
15927
+ if (target !== document.documentElement && target !== document.body && Fit.Dom.IsRooted(target) === false)
15928
+ {
15929
+ return; // Do not close DropDown if target no longer exists - this may happen if something is removed within DropDown (e.g. an item in the WSDropDown's action menu)
15930
+ }
15931
+
15927
15932
  if (me.IsDropDownOpen() === true && target !== me.GetDomElement() && Fit.Dom.Contained(me.GetDomElement(), target) === false)
15933
+ {
15928
15934
  me.CloseDropDown();
15935
+ }
15929
15936
  });
15930
15937
  Fit.Array.Add(closeHandlers, eventId);
15931
15938
  }
@@ -15942,6 +15949,11 @@ Fit.Controls.DropDown = function(ctlId)
15942
15949
 
15943
15950
  coords = null;
15944
15951
 
15952
+ if (target !== document.documentElement && target !== document.body && Fit.Dom.IsRooted(target) === false)
15953
+ {
15954
+ return; // Do not close DropDown if target no longer exists - this may happen if something is removed within DropDown (e.g. an item in the WSDropDown's action menu)
15955
+ }
15956
+
15945
15957
  if (me.IsDropDownOpen() === true && target !== me.GetDomElement() && Fit.Dom.Contained(me.GetDomElement(), target) === false)
15946
15958
  {
15947
15959
  coords = Fit.Events.GetPointerState().Coordinates.Document;
@@ -16532,6 +16544,19 @@ Fit.Controls.DropDown = function(ctlId)
16532
16544
  if (txt.parentElement.parentElement === null)
16533
16545
  txt = txtPrimary;
16534
16546
  }
16547
+ else
16548
+ {
16549
+ // User selected an item which is already selected
16550
+
16551
+ if (me.TextSelectionMode() === true)
16552
+ {
16553
+ updateTextSelection(); // Make sure any search value is removed and text selection is restored
16554
+ }
16555
+ else
16556
+ {
16557
+ me.ClearInput(); // Make sure any search value is removed
16558
+ }
16559
+ }
16535
16560
 
16536
16561
  // DISABLED: Now handled using picker.OnFocus(..) handler further down - see https://github.com/Jemt/Fit.UI/issues/86 for details
16537
16562
  // if (eventArgs.ProgrammaticallyChanged === false && (isMobile === false || focusInputOnMobile === true))
@@ -17152,9 +17177,7 @@ Fit.Controls.DropDown = function(ctlId)
17152
17177
 
17153
17178
  inp.value = "";
17154
17179
 
17155
- Fit.Dom.Data(inp.parentElement, "editing", null);
17156
- Fit.Dom.Data(inp, "editing", null);
17157
- me._internal.Repaint();
17180
+ setInputEditing(inp, false);
17158
17181
 
17159
17182
  if (inp === txtActive)
17160
17183
  prevValue = "";
@@ -17174,22 +17197,31 @@ Fit.Controls.DropDown = function(ctlId)
17174
17197
  {
17175
17198
  Fit.Validation.ExpectString(val);
17176
17199
 
17177
- if (txtActive.value === val)
17178
- return;
17200
+ var txt = txtActive;
17201
+ var fireOnChange = txt.value !== val;
17179
17202
 
17180
- txtActive.value = "";
17203
+ if (focusAssigned === false && txt !== txtPrimary)
17204
+ {
17205
+ // DropDown does not have focus, and the currently active input field
17206
+ // is not the primary one. Change it to the primary input field instead.
17181
17207
 
17182
- Fit.Dom.Data(txtActive.parentElement, "editing", val !== "" ? "true" : null);
17183
- Fit.Dom.Data(txtActive, "editing", val !== "" ? "true" : null);
17184
- me._internal.Repaint();
17208
+ // Clear active input field in case it has a value
17209
+ txtActive.value = "";
17210
+ setInputEditing(txtActive, false);
17211
+
17212
+ txt = txtPrimary;
17213
+ txtActive = txtPrimary;
17214
+ }
17185
17215
 
17186
- var txt = ((focusAssigned === true) ? txtActive : txtPrimary);
17216
+ setInputEditing(txt, val !== "");
17187
17217
 
17188
17218
  txt.value = val;
17189
17219
  prevValue = val;
17190
- txtActive = txt;
17191
17220
 
17192
- fireOnInputChanged(txt.value);
17221
+ if (fireOnChange === true)
17222
+ {
17223
+ fireOnInputChanged(txt.value);
17224
+ }
17193
17225
  }
17194
17226
 
17195
17227
  /// <function container="Fit.Controls.DropDown" name="GetInputValue" access="public" returns="string">
@@ -17229,6 +17261,11 @@ Fit.Controls.DropDown = function(ctlId)
17229
17261
  if (Fit._internal.DropDown.Current !== null && Fit._internal.DropDown.Current !== me)
17230
17262
  Fit._internal.DropDown.Current.CloseDropDown();
17231
17263
 
17264
+ if (txtActive === txtPrimary && me.GetInputValue() === "")
17265
+ {
17266
+ me._internal.UndoClearInputForSearch();
17267
+ }
17268
+
17232
17269
  // Do this before displaying drop down to prevent dropdown with position:absolute
17233
17270
  // from changing height of document which may cause page to temporarily scroll, hence
17234
17271
  // result in incorrect measurement of control position in optimizeDropDownPosition().
@@ -17403,6 +17440,53 @@ Fit.Controls.DropDown = function(ctlId)
17403
17440
  Fit.Array.Add(onCloseHandlers, cb);
17404
17441
  }
17405
17442
 
17443
+ // ============================================
17444
+ // Protected
17445
+ // ============================================
17446
+
17447
+ this._internal = (this._internal ? this._internal : {});
17448
+
17449
+ this._internal.ClearInputForSearch = function()
17450
+ {
17451
+ if (me.TextSelectionMode() === true)
17452
+ {
17453
+ forceFocusInput(txtPrimary);
17454
+ txtPrimary.value = "";
17455
+ updatePlaceholder(true);
17456
+ }
17457
+ else
17458
+ {
17459
+ forceFocusInput(txtPrimary);
17460
+ me.ClearInput();
17461
+ setInputEditing(txtPrimary, true);
17462
+ }
17463
+
17464
+ // If DropDown is in Visual Selection Mode, the search field is now displayed
17465
+ // on a separate "line" (see code above where setInputEditing(..) is called).
17466
+ // When that input field lose focus, it will immediately return to its normal
17467
+ // state. This results in the control's height changing which in turn results
17468
+ // in the pulldown menu moving up. So if a user clicks an item in the pulldown
17469
+ // menu, the input lose focus, the position of the pulldown menu changes, and
17470
+ // nothing happens from the click because the picker uses a traditional onclick
17471
+ // event to register interactions, which doesn't fire unless the mouse button is released
17472
+ // on the same object it was pressed down on. We avoid this by closing the control.
17473
+ // We close it in both Text Selection Mode and Visual Selection Mode for consistency.
17474
+ me.CloseDropDown();
17475
+ }
17476
+
17477
+ this._internal.UndoClearInputForSearch = function()
17478
+ {
17479
+ if (me.TextSelectionMode() === true)
17480
+ {
17481
+ updateTextSelection(); // Also removes placeholder by calling updatePlaceholder(true)
17482
+ }
17483
+ else
17484
+ {
17485
+ me.ClearInput();
17486
+ setInputEditing(txtPrimary, false);
17487
+ }
17488
+ }
17489
+
17406
17490
  // ============================================
17407
17491
  // Private
17408
17492
  // ============================================
@@ -17439,9 +17523,7 @@ Fit.Controls.DropDown = function(ctlId)
17439
17523
  prevValue = txt.value;
17440
17524
  var pastedValue = txt.value;
17441
17525
 
17442
- Fit.Dom.Data(txt.parentElement, "editing", "true");
17443
- Fit.Dom.Data(txt, "editing", "true");
17444
- me._internal.Repaint();
17526
+ setInputEditing(txt, true);
17445
17527
 
17446
17528
  if (fireOnPaste(txt.value) === true)
17447
17529
  {
@@ -17521,9 +17603,7 @@ Fit.Controls.DropDown = function(ctlId)
17521
17603
 
17522
17604
  if (txt.value === "")
17523
17605
  {
17524
- Fit.Dom.Data(txt.parentElement, "editing", null);
17525
- Fit.Dom.Data(txt, "editing", null);
17526
- me._internal.Repaint();
17606
+ setInputEditing(txt, false);
17527
17607
  }
17528
17608
  }
17529
17609
 
@@ -17654,6 +17734,11 @@ Fit.Controls.DropDown = function(ctlId)
17654
17734
 
17655
17735
  if (txt.value.length === 0)
17656
17736
  {
17737
+ if (txt === txtPrimary)
17738
+ {
17739
+ me._internal.UndoClearInputForSearch();
17740
+ }
17741
+
17657
17742
  if (Fit.Browser.GetInfo().Name === "MSIE")
17658
17743
  Fit.Events.PreventDefault(ev); // Do not navigate back on IE when backspace is pressed within input being removed
17659
17744
 
@@ -17723,9 +17808,7 @@ Fit.Controls.DropDown = function(ctlId)
17723
17808
  {
17724
17809
  updatePlaceholder(true, true); // Make sure placeholder is removed immediately on keystroke
17725
17810
 
17726
- Fit.Dom.Data(txt.parentElement, "editing", "true");
17727
- Fit.Dom.Data(txt, "editing", "true");
17728
- me._internal.Repaint();
17811
+ setInputEditing(txt, true);
17729
17812
  }
17730
17813
 
17731
17814
  txt.onkeyup = function(e) // Fires only once when a key is released (unless suppressed in OnKeyDown)
@@ -17842,6 +17925,44 @@ Fit.Controls.DropDown = function(ctlId)
17842
17925
  txtActive.focus(); // Notice: Input field's onfocus handler will move focus to txtPrimary if this is the last selection's right side input field - on mobile it might redirect focus to the arrow icon to avoid bringing up the virtual keyboard
17843
17926
  }
17844
17927
 
17928
+ function forceFocusInput(input)
17929
+ {
17930
+ Fit.Validation.ExpectInstance(input, HTMLInputElement);
17931
+
17932
+ // By default focus is never assigned to input on mobile if user opened the
17933
+ // DropDown by clicking the arrow icon. ForceFocusInput allows us to ignore this
17934
+ // aspect. On desktop the input field always remains focused as it automatically
17935
+ // steals back focus immediately after interacting with a picker control. See
17936
+ // picker.OnFocusIn handler registered in SetPicker(..)
17937
+ var orgFocusInputOnMobile = focusInputOnMobile;
17938
+ focusInputOnMobile = true;
17939
+ focusInput(input);
17940
+ focusInputOnMobile = orgFocusInputOnMobile;
17941
+ }
17942
+
17943
+ function setInputEditing(input, val, keepStateOnParent)
17944
+ {
17945
+ Fit.Validation.ExpectInstance(input, HTMLInputElement);
17946
+ Fit.Validation.ExpectBoolean(val);
17947
+ Fit.Validation.ExpectBoolean(keepStateOnParent, true);
17948
+
17949
+ if (keepStateOnParent !== true)
17950
+ {
17951
+ Fit.Dom.Data(input.parentElement, "editing", val === true ? "true" : null);
17952
+ }
17953
+
17954
+ Fit.Dom.Data(input, "editing", val === true ? "true" : null);
17955
+
17956
+ if (input === txtPrimary)
17957
+ {
17958
+ // DropDown's placeholder is only displayed when no items are selected.
17959
+ // Therefore, use native placeholder for input if items are selected.
17960
+ txtPrimary.placeholder = val === true && me.GetSelections().length > 0 ? me.Placeholder() : "";
17961
+ }
17962
+
17963
+ me._internal.Repaint();
17964
+ }
17965
+
17845
17966
  function clearAllInputsButActive()
17846
17967
  {
17847
17968
  var inputs = itemContainer.getElementsByTagName("input");
@@ -17851,9 +17972,16 @@ Fit.Controls.DropDown = function(ctlId)
17851
17972
  if (input === txtActive)
17852
17973
  return;
17853
17974
 
17854
- if ((txtActive.parentElement === itemContainer && input === txtPrimary) || (txtActive.parentElement !== itemContainer && Fit.Dom.Contained(txtActive.parentElement, input) === false))
17855
- Fit.Dom.Data(input.parentElement, "editing", null);
17856
- Fit.Dom.Data(input, "editing", null);
17975
+ if (txtActive.parentElement === input.parentElement)
17976
+ {
17977
+ // Input is contained in a selected object whose other input is txtActive.
17978
+ // Do not remove editing state from parent element in this case - keep it!
17979
+ setInputEditing(input, false, true);
17980
+ }
17981
+ else
17982
+ {
17983
+ setInputEditing(input, false);
17984
+ }
17857
17985
 
17858
17986
  me.ClearInput(input);
17859
17987
  });
@@ -18182,7 +18310,7 @@ Fit.Controls.DropDown = function(ctlId)
18182
18310
 
18183
18311
  if (placeholder !== "" || force === true)
18184
18312
  {
18185
- var setPlaceholder = placeholder !== "" && itemCollectionOrdered.length === 0 && me.GetInputValue() === "" && willAssumeInputValue !== true;
18313
+ var setPlaceholder = placeholder !== "" && (me.TextSelectionMode() === true || itemCollectionOrdered.length === 0) && me.GetInputValue() === "" && willAssumeInputValue !== true;
18186
18314
 
18187
18315
  Fit.Dom.Data(itemContainer, "placeholder", setPlaceholder === true ? placeholder : null);
18188
18316
  Fit.Dom.Data(itemContainer, "placeholder-autoclear", setPlaceholder === true ? Fit.Browser.GetBrowser() === "MSIE" ? "true" : "false" : null);
@@ -18396,6 +18524,12 @@ Fit.Controls.DropDown = function(ctlId)
18396
18524
  Fit.Dom.SetCaretPosition(txtPrimary, 0);
18397
18525
  }
18398
18526
 
18527
+ // Remove placeholder in case it was added using this._internal.ClearInputForSearch()
18528
+
18529
+ updatePlaceholder(true);
18530
+
18531
+ // Have TextSelection removed when input is changed
18532
+
18399
18533
  clearTextSelectionOnInputChange = true;
18400
18534
  }
18401
18535
 
@@ -19027,12 +19161,14 @@ Fit.Controls.WSDropDown = function(ctlId)
19027
19161
  var me = this;
19028
19162
  var list = null;
19029
19163
  var tree = null;
19164
+ var actionMenu = null;
19030
19165
 
19031
19166
  var search = "";
19032
19167
  var forceNewSearch = false;
19033
19168
  var hideLinesForFlatData = true;
19034
19169
  var dataRequested = false; // Flag indicating whether TreeView data has been requested or not - determines whether a call to ensureTreeViewData() actually loads data or not
19035
19170
  var dataLoading = false; // Flag indicating whether TreeView data is currently being loaded by WSDropDown internals (awaiting response) - will not be True when user expand nodes to load children, or when invoking e.g. dd.GetTreeView.Reload()
19171
+ var nodesPopulated = false; // Flag indicating whether TreeView root nodes have been populated - contrary to dataLoading this flag is set when a potentially partial portion of the data has been loaded
19036
19172
  var requestCount = 0; // Counter to keep track of nodes for which data is currently being loaded, no matter how it was being loaded (via WSDropDown internals, programmatically on WSTreeView from external code, or by user expanding nodes)
19037
19173
  var onDataLoadedCallback = [];
19038
19174
  var suppressTreeOnOpen = false;
@@ -19040,6 +19176,10 @@ Fit.Controls.WSDropDown = function(ctlId)
19040
19176
  var currentRequest = null;
19041
19177
  var classes = null;
19042
19178
  var autoUpdatedSelections = null; // Cached result from AutoUpdateSelected: [{ Title:string, Value:string, Exists:boolean }, ...]
19179
+ var useActionMenu = false;
19180
+ var useActionMenuForced = false;
19181
+ var useActionMenuAfterLoad = true;
19182
+ var translations = null;
19043
19183
 
19044
19184
  var onRequestHandlers = [];
19045
19185
  var onResponseHandlers = [];
@@ -19063,7 +19203,7 @@ Fit.Controls.WSDropDown = function(ctlId)
19063
19203
 
19064
19204
  // Create ListView
19065
19205
 
19066
- list = new Fit.Controls.WSListView(ctlId + "__WSListView");
19206
+ list = new Fit.Controls.WSListView(me.GetId() + "__WSListView");
19067
19207
  list.OnRequest(function(sender, eventArgs)
19068
19208
  {
19069
19209
  if (fireEventHandlers(onRequestHandlers, list, eventArgs) === false)
@@ -19115,7 +19255,7 @@ Fit.Controls.WSDropDown = function(ctlId)
19115
19255
 
19116
19256
  // Create TreeView
19117
19257
 
19118
- tree = new Fit.Controls.WSTreeView(ctlId + "__WSTreeView");
19258
+ tree = new Fit.Controls.WSTreeView(me.GetId() + "__WSTreeView");
19119
19259
  tree.Selectable(true); // Make nodes selectable by default when added
19120
19260
  tree.Width(100, "%");
19121
19261
  //tree.Lines(true); // DISABLED - lines do not scale with font size
@@ -19175,6 +19315,30 @@ Fit.Controls.WSDropDown = function(ctlId)
19175
19315
  });
19176
19316
  tree.OnPopulated(function(sender, eventArgs)
19177
19317
  {
19318
+ nodesPopulated = true;
19319
+
19320
+ // If no data is returned and DropDown is in TextSelectionMode, the user will
19321
+ // not be able to remove objects from the DropDown, unless SelectionModeToggle
19322
+ // is true. Therefore we allow for items to be removed using an action menu.
19323
+ // EDIT: Now displays action menu in both Visual and Text Selection Mode for consistency.
19324
+
19325
+ if (useActionMenuForced === false)
19326
+ {
19327
+ useActionMenu = tree.GetChildren().length === 0;
19328
+ }
19329
+
19330
+ if (/*me.TextSelectionMode() === true &&*/ useActionMenu === true)
19331
+ {
19332
+ updateActionMenu();
19333
+
19334
+ if (useActionMenuAfterLoad === true || tree.GetChildren().length === 0)
19335
+ {
19336
+ me.SetPicker(actionMenu);
19337
+ }
19338
+ }
19339
+
19340
+ // Helper lines
19341
+
19178
19342
  if (hideLinesForFlatData === true && tree.Lines() === true) // Lines are off by default but might have been enabled like so: dd.GetTreeView().Lines(true)
19179
19343
  {
19180
19344
  // Disable helper lines if no children are contained
@@ -19245,6 +19409,109 @@ Fit.Controls.WSDropDown = function(ctlId)
19245
19409
  me.SetPicker(tree);
19246
19410
  });
19247
19411
 
19412
+ // Create action menu
19413
+
19414
+ var skipUpdateActionMenuOnChange = false;
19415
+
19416
+ actionMenu = new Fit.Controls.ListView(me.GetId() + "__ActionsListView");
19417
+ actionMenu.OnSelect(function(sender, item) // Using OnSelect instead of OnItemSelectionChanging since DropDown fires OnItemSelectionChanging when selection is changed, which would result in OnItemSelectionChanging being executed multiple times
19418
+ {
19419
+ if (item.Value === "SearchMore")
19420
+ {
19421
+ me._internal.ClearInputForSearch();
19422
+ }
19423
+ else if (item.Value === "ShowAll")
19424
+ {
19425
+ me._internal.UndoClearInputForSearch(); // In case user first picked SearchMore, changed their mind, and then selected ShowAll
19426
+
19427
+ useActionMenuAfterLoad = false;
19428
+
19429
+ me.SetPicker(tree);
19430
+ ensureTreeViewData();
19431
+ }
19432
+ else if (item.Value === "RemoveAll")
19433
+ {
19434
+ skipUpdateActionMenuOnChange = true;
19435
+ me.ClearSelections(); // Fires OnChange
19436
+ skipUpdateActionMenuOnChange = false;
19437
+
19438
+ updateActionMenu(); // Update action menu (remove all "Remove: xyz" options)
19439
+ }
19440
+ else if (item.Value.indexOf("Remove:") === 0)
19441
+ {
19442
+ var dataValue = item.Value.replace("Remove:", "");
19443
+
19444
+ // Find item below if using keyboard to remove item
19445
+
19446
+ var above = null;
19447
+ var below = null;
19448
+
19449
+ if (Fit.Browser.GetInfo().IsMobile === false && Fit.Events.GetPointerState().Buttons.Primary === false) // Do not find and highlight item below on touch devices, or if using the mouse
19450
+ {
19451
+ var items = actionMenu.GetItems();
19452
+ var stopNext = false;
19453
+
19454
+ for (var i = 0 ; i < items.length ; i++)
19455
+ {
19456
+ if (items[i].Value.indexOf("Remove:") !== 0)
19457
+ {
19458
+ continue; // Skip items that do not remove individual selections
19459
+ }
19460
+
19461
+ if (items[i].Value.replace("Remove:", "") === dataValue)
19462
+ {
19463
+ stopNext = true;
19464
+ continue;
19465
+ }
19466
+
19467
+ if (stopNext === false)
19468
+ {
19469
+ above = items[i].Value;
19470
+ }
19471
+ else
19472
+ {
19473
+ below = items[i].Value;
19474
+ break;
19475
+ }
19476
+ }
19477
+ }
19478
+
19479
+ // Remove item
19480
+
19481
+ skipUpdateActionMenuOnChange = true;
19482
+ me.RemoveSelection(dataValue); // Fires OnChange
19483
+ skipUpdateActionMenuOnChange = false;
19484
+
19485
+ // Update list of removable items
19486
+
19487
+ actionMenu.RemoveItem(item.Value);
19488
+
19489
+ if (me.GetSelections().length === 1)
19490
+ {
19491
+ updateActionMenu(); // Update action menu to get rid of "Remove all" item when only one item is left
19492
+ }
19493
+
19494
+ // Highlight item below (or above) the item that was just removed
19495
+
19496
+ if (above !== null || below !== null)
19497
+ {
19498
+ actionMenu.RevealItemInView(below !== null ? below : above);
19499
+ }
19500
+ }
19501
+
19502
+ return false; // Prevent selection of behavioural item
19503
+ });
19504
+
19505
+ me.OnChange(function(sender)
19506
+ {
19507
+ if (skipUpdateActionMenuOnChange === false)
19508
+ {
19509
+ updateActionMenu();
19510
+ }
19511
+ });
19512
+
19513
+ // Misc
19514
+
19248
19515
  me.OnOpen(function()
19249
19516
  {
19250
19517
  if (suppressTreeOnOpen === true)
@@ -19253,9 +19520,29 @@ Fit.Controls.WSDropDown = function(ctlId)
19253
19520
  return;
19254
19521
  }
19255
19522
 
19256
- me.SetPicker(tree);
19257
- ensureTreeViewData();
19523
+ // Do not show action menu if the only option available is ShowAll.
19524
+ // In this case the user will not be able to select SeachMore, and
19525
+ // there is no selected items that can be removed from the control.
19526
+ var onlyShowAllOptionDisplayedInActionMenu = actionMenu.GetItems().length === 1 && actionMenu.HasItem("ShowAll") === true;
19527
+
19528
+ if (useActionMenu === true && onlyShowAllOptionDisplayedInActionMenu === false)
19529
+ {
19530
+ me.SetPicker(actionMenu);
19531
+ }
19532
+ else
19533
+ {
19534
+ if (onlyShowAllOptionDisplayedInActionMenu === true)
19535
+ {
19536
+ useActionMenuAfterLoad = false;
19537
+ }
19538
+
19539
+ me.SetPicker(tree);
19540
+ ensureTreeViewData();
19541
+ }
19258
19542
  });
19543
+
19544
+ Fit.Internationalization.OnLocaleChanged(localize);
19545
+ localize();
19259
19546
  }
19260
19547
 
19261
19548
  // ============================================
@@ -19319,6 +19606,7 @@ Fit.Controls.WSDropDown = function(ctlId)
19319
19606
  }
19320
19607
 
19321
19608
  dataLoading = true;
19609
+ nodesPopulated = false;
19322
19610
 
19323
19611
  var ensure = function()
19324
19612
  {
@@ -19464,10 +19752,6 @@ Fit.Controls.WSDropDown = function(ctlId)
19464
19752
  }
19465
19753
  }
19466
19754
 
19467
- /// <function container="Fit.Controls.WSDropDown" name="MultiSelectionMode" access="public" returns="boolean">
19468
- /// <description> Get/set value indicating whether control allows for multiple selections simultaneously </description>
19469
- /// <param name="val" type="boolean" default="undefined"> If defined, True enables support for multiple selections, False disables it </param>
19470
- /// </function>
19471
19755
  this.MultiSelectionMode = Fit.Core.CreateOverride(this.MultiSelectionMode, function(val)
19472
19756
  {
19473
19757
  Fit.Validation.ExpectBoolean(val, true);
@@ -19480,6 +19764,18 @@ Fit.Controls.WSDropDown = function(ctlId)
19480
19764
  return base(val);
19481
19765
  });
19482
19766
 
19767
+ this.InputEnabled = Fit.Core.CreateOverride(this.InputEnabled, function(val)
19768
+ {
19769
+ Fit.Validation.ExpectBoolean(val, true);
19770
+
19771
+ if (Fit.Validation.IsSet(val) === true && base() !== val)
19772
+ {
19773
+ base(val);
19774
+ updateActionMenu(); // Update action menu to have SearchMore action added/removed depending on whether input is allowed or not
19775
+ }
19776
+
19777
+ return base();
19778
+ });
19483
19779
 
19484
19780
  /// <function container="Fit.Controls.WSDropDown" name="GetListView" access="public" returns="Fit.Controls.WSListView">
19485
19781
  /// <description> Get WSListView control used to display data in a flat list view </description>
@@ -19497,6 +19793,44 @@ Fit.Controls.WSDropDown = function(ctlId)
19497
19793
  return tree;
19498
19794
  }
19499
19795
 
19796
+ /// <function container="Fit.Controls.WSDropDown" name="UseActionMenu" access="public">
19797
+ /// <description> Get/set value indicating whether control uses the built-in action menu to ease addition and removal of items </description>
19798
+ /// <param name="val" type="boolean" default="undefined"> If defined, True enables the action menu, False disables it </param>
19799
+ /// </function>
19800
+ this.UseActionMenu = function(val)
19801
+ {
19802
+ Fit.Validation.ExpectBoolean(val, true);
19803
+
19804
+ if (Fit.Validation.IsSet(val) === true)
19805
+ {
19806
+ useActionMenuForced = true;
19807
+
19808
+ if (val !== useActionMenu)
19809
+ {
19810
+ useActionMenu = val;
19811
+
19812
+ if (val === true)
19813
+ {
19814
+ updateActionMenu();
19815
+
19816
+ if (me.IsDropDownOpen() === true)
19817
+ {
19818
+ me.SetPicker(actionMenu);
19819
+ }
19820
+ }
19821
+ else
19822
+ {
19823
+ if (me.GetPicker() === actionMenu)
19824
+ {
19825
+ me.SetPicker(tree.GetChildren().length > 0 ? tree : list);
19826
+ }
19827
+ }
19828
+ }
19829
+ }
19830
+
19831
+ return useActionMenu;
19832
+ }
19833
+
19500
19834
  // See documentation on ControlBase
19501
19835
  this.Dispose = Fit.Core.CreateOverride(this.Dispose, function()
19502
19836
  {
@@ -19504,10 +19838,13 @@ Fit.Controls.WSDropDown = function(ctlId)
19504
19838
 
19505
19839
  list.Destroy();
19506
19840
  tree.Destroy();
19841
+ actionMenu.Destroy();
19507
19842
 
19508
19843
  cancelSearch();
19509
19844
 
19510
- me = list = tree = search = forceNewSearch = hideLinesForFlatData = dataRequested = dataLoading = requestCount = onDataLoadedCallback = suppressTreeOnOpen = timeOut = currentRequest = classes = autoUpdatedSelections = onRequestHandlers = onResponseHandlers = null;
19845
+ Fit.Internationalization.RemoveOnLocaleChanged(localize);
19846
+
19847
+ me = list = tree = actionMenu = search = forceNewSearch = hideLinesForFlatData = dataRequested = dataLoading = nodesPopulated = requestCount = onDataLoadedCallback = suppressTreeOnOpen = timeOut = currentRequest = classes = autoUpdatedSelections = useActionMenu = useActionMenuForced = useActionMenuAfterLoad = translations = onRequestHandlers = onResponseHandlers = null;
19511
19848
 
19512
19849
  base();
19513
19850
  });
@@ -19634,6 +19971,7 @@ Fit.Controls.WSDropDown = function(ctlId)
19634
19971
  if (dataRequested === false)
19635
19972
  {
19636
19973
  dataLoading = true;
19974
+ nodesPopulated = false;
19637
19975
 
19638
19976
  tree.Reload(true, function(sender)
19639
19977
  {
@@ -19698,6 +20036,55 @@ Fit.Controls.WSDropDown = function(ctlId)
19698
20036
  }
19699
20037
  }
19700
20038
 
20039
+ function updateActionMenu()
20040
+ {
20041
+ if (useActionMenu === false)
20042
+ {
20043
+ return;
20044
+ }
20045
+
20046
+ var searchIcon = "<span class='FitUiControlDropDownActionMenuItemIcon FitUiControlDropDownActionMenuItemIconSearch fa fa-search'></span> ";
20047
+ var showAllIcon = "<span class='FitUiControlDropDownActionMenuItemIcon FitUiControlDropDownActionMenuItemIconShowAll fa fa-sitemap'></span> ";
20048
+ var delIcon = "<span class='FitUiControlDropDownActionMenuItemIcon FitUiControlDropDownActionMenuItemIconDelete fa fa-times'></span> ";
20049
+
20050
+ var selectedItems = me.GetSelections();
20051
+ var addRemoveAll = selectedItems.length > 1;
20052
+
20053
+ actionMenu.RemoveItems();
20054
+
20055
+ if (me.InputEnabled() === true)
20056
+ {
20057
+ actionMenu.AddItem(searchIcon + translations.SearchMore, "SearchMore");
20058
+ }
20059
+
20060
+ if (nodesPopulated === false || tree.GetChildren().length > 0)
20061
+ {
20062
+ actionMenu.AddItem(showAllIcon + translations.ShowAllOptions, "ShowAll");
20063
+ }
20064
+ else //if (nodesPopulated === true && tree.GetChildren().length === 0)
20065
+ {
20066
+ actionMenu.AddItem(showAllIcon + "<i>" + translations.NoneAvailable + ": " + translations.ShowAllOptions + "</i>", "ShowAllNoneFound");
20067
+ }
20068
+
20069
+ if (addRemoveAll === true)
20070
+ {
20071
+ actionMenu.AddItem(delIcon + translations.RemoveAll, "RemoveAll");
20072
+ }
20073
+
20074
+ Fit.Array.ForEach(selectedItems, function(item)
20075
+ {
20076
+ actionMenu.AddItem((addRemoveAll === true ? " &nbsp; &nbsp; &nbsp; " : "") + delIcon + translations.Remove + " " + item.Title, "Remove:" + item.Value);
20077
+ });
20078
+ }
20079
+
20080
+ function localize()
20081
+ {
20082
+ var locale = Fit.Internationalization.GetLocale(me);
20083
+ translations = locale.Translations;
20084
+
20085
+ updateActionMenu();
20086
+ }
20087
+
19701
20088
  function onDataLoaded(cb)
19702
20089
  {
19703
20090
  Fit.Validation.ExpectFunction(cb);
@@ -19775,31 +20162,49 @@ Fit.Controls.WSDropDown = function(ctlId)
19775
20162
  // All locales inherit from en. All country specific overrides inherit
19776
20163
  // from their primary locale (e.g. de_AT inherits from de).
19777
20164
  // English (en) MUST be defined!
19778
-
20165
+
19779
20166
  "en": // US
19780
20167
  {
19781
20168
  Translations:
19782
20169
  {
19783
- InvalidSelection : "Invalid selection"
20170
+ InvalidSelection : "Invalid selection",
20171
+
20172
+ SearchMore : "Search for more options",
20173
+ ShowAllOptions : "Show all available options",
20174
+ RemoveAll : "Remove all selected",
20175
+ Remove : "Remove",
20176
+ NoneAvailable : "None available"
19784
20177
  }
19785
20178
  },
19786
20179
  "da":
19787
20180
  {
19788
20181
  Translations:
19789
20182
  {
19790
- InvalidSelection : "Ugyldigt valg"
20183
+ InvalidSelection : "Ugyldigt valg",
20184
+
20185
+ SearchMore : "Søg efter flere valgmuligheder",
20186
+ ShowAllOptions : "Vis alle tilgængelige valgmuligheder",
20187
+ RemoveAll : "Fjern alle valgte",
20188
+ Remove : "Fjern",
20189
+ NoneAvailable : "Ingen tilgængelige"
19791
20190
  }
19792
20191
  },
19793
20192
  "de":
19794
20193
  {
19795
20194
  Translations:
19796
20195
  {
19797
- InvalidSelection : "Ungültige Auswahl"
20196
+ InvalidSelection : "Ungültige Auswahl",
20197
+
20198
+ SearchMore : "Nach weiteren Optionen suchen",
20199
+ ShowAllOptions : "Alle verfügbaren Optionen anzeigen",
20200
+ RemoveAll : "Alle ausgewählten entfernen",
20201
+ Remove : "Entfernen",
20202
+ NoneAvailable : "Keine verfügbar"
19798
20203
  }
19799
20204
  }
19800
20205
  }
19801
20206
  Fit.Internationalization.AddLocalization(Fit.Controls.DropDown, locale);
19802
- Fit.Internationalization.AddLocalization(Fit.Controls.WSDropDown, locale);
20207
+ Fit.Internationalization.AddLocalization(Fit.Controls.WSDropDown, locale);
19803
20208
  })();
19804
20209
  /// <container name="Fit.Controls.FilePicker" extends="Fit.Controls.ControlBase">
19805
20210
  /// Control allowing for files to be selected locally and uploaded asynchronously.
@@ -22710,6 +23115,9 @@ Fit.Controls.ListView = function(controlId)
22710
23115
  var firstWasHighlighted = false;
22711
23116
  var isIe8 = (Fit.Browser.GetInfo().Name === "MSIE" && Fit.Browser.GetInfo().Version === 8);
22712
23117
 
23118
+ var onSelectHandlers = [];
23119
+ var onSelectedHandlers = [];
23120
+
22713
23121
  function init()
22714
23122
  {
22715
23123
  list.tabIndex = "0";
@@ -22763,12 +23171,17 @@ Fit.Controls.ListView = function(controlId)
22763
23171
 
22764
23172
  // Fire OnChanging and OnChange events
22765
23173
 
23174
+ var item = convertItemElementToObject(elm);
23175
+ var selectionCanceled = fireOnSelectHandlers(item) === false;
23176
+
22766
23177
  // Notice: We always pass False as current selection state to OnItemSelectionChanging since ListView does
22767
23178
  // not keep track of selection state. In theory item could very well already be selected in host control.
22768
23179
  // Event handlers should not trust boolean to reveal selection in host control, only in picker.
22769
- if (me._internal.FireOnItemSelectionChanging(Fit.Dom.Text(elm), decode(Fit.Dom.Data(elm, "value")), false, false) === true)
23180
+ if (selectionCanceled === false && me._internal.FireOnItemSelectionChanging(item.Title, item.Value, false, false) === true)
22770
23181
  {
22771
- me._internal.FireOnItemSelectionChanged(Fit.Dom.Text(elm), decode(Fit.Dom.Data(elm, "value")), true, false);
23182
+ fireOnSelectedHandlers(item);
23183
+
23184
+ me._internal.FireOnItemSelectionChanged(item.Title, item.Value, true, false);
22772
23185
  me._internal.FireOnItemSelectionComplete();
22773
23186
  }
22774
23187
  }
@@ -22888,6 +23301,21 @@ Fit.Controls.ListView = function(controlId)
22888
23301
  return null;
22889
23302
  }
22890
23303
 
23304
+ /// <function container="Fit.Controls.ListView" name="GetItems" access="public" returns="Fit.Controls.ListViewTypeDefs.ListViewItem[]">
23305
+ /// <description> Get all items - returns array containing objects with Title (string) and Value (string) properties </description>
23306
+ /// </function>
23307
+ this.GetItems = function()
23308
+ {
23309
+ var items = [];
23310
+
23311
+ Fit.Array.ForEach(list.children, function(child)
23312
+ {
23313
+ Fit.Array.Add(items, convertItemElementToObject(child));
23314
+ });
23315
+
23316
+ return items;
23317
+ }
23318
+
22891
23319
  /// <function container="Fit.Controls.ListView" name="HasItem" access="public" returns="boolean">
22892
23320
  /// <description> Returns value indicating whether control contains item with specified value </description>
22893
23321
  /// <param name="value" type="string"> Value of item to check for </param>
@@ -22911,6 +23339,11 @@ Fit.Controls.ListView = function(controlId)
22911
23339
  if (item !== null)
22912
23340
  {
22913
23341
  Fit.Dom.Remove(item);
23342
+
23343
+ if (item === active)
23344
+ {
23345
+ active = null;
23346
+ }
22914
23347
  }
22915
23348
  }
22916
23349
 
@@ -22974,12 +23407,17 @@ Fit.Controls.ListView = function(controlId)
22974
23407
 
22975
23408
  if (active !== null)
22976
23409
  {
23410
+ var item = convertItemElementToObject(active);
23411
+ var selectionCanceled = fireOnSelectHandlers(item) === false;
23412
+
22977
23413
  // Notice: We always pass False as current selection state to OnItemSelectionChanging since ListView does
22978
23414
  // not keep track of selection state. In theory item could very well already be selected in host control.
22979
23415
  // Event handlers should not trust boolean to reveal selection in host control, only in picker.
22980
- if (me._internal.FireOnItemSelectionChanging(Fit.Dom.Text(active), decode(Fit.Dom.Data(active, "value")), false, false) === true)
23416
+ if (selectionCanceled === false && me._internal.FireOnItemSelectionChanging(item.Title, item.Value, false, false) === true)
22981
23417
  {
22982
- me._internal.FireOnItemSelectionChanged(Fit.Dom.Text(active), decode(Fit.Dom.Data(active, "value")), true, false);
23418
+ fireOnSelectedHandlers(item);
23419
+
23420
+ me._internal.FireOnItemSelectionChanged(item.Title, item.Value, true, false);
22983
23421
  me._internal.FireOnItemSelectionComplete();
22984
23422
  }
22985
23423
  }
@@ -23038,6 +23476,47 @@ Fit.Controls.ListView = function(controlId)
23038
23476
  return highlightFirst;
23039
23477
  }
23040
23478
 
23479
+ /// <function container="Fit.Controls.ListViewTypeDefs" name="OnSelectEventHandler" returns="boolean | void">
23480
+ /// <description> OnSelect event handler </description>
23481
+ /// <param name="sender" type="$TypeOfThis"> Instance of control </param>
23482
+ /// <param name="item" type="{ Title: string, Value: string }"> Selected item </param>
23483
+ /// </function>
23484
+
23485
+ /// <function container="Fit.Controls.ListView" name="OnSelect" access="public">
23486
+ /// <description>
23487
+ /// Register event handler fired when item is being selected.
23488
+ /// Selection can be canceled by returning False.
23489
+ /// The following arguments are passed to event handler function:
23490
+ /// Sender (ListView) and Item (with Title (string) and Value (string) properties).
23491
+ /// </description>
23492
+ /// <param name="cb" type="Fit.Controls.ListViewTypeDefs.OnSelectEventHandler"> Event handler function </param>
23493
+ /// </function>
23494
+ this.OnSelect = function(cb)
23495
+ {
23496
+ Fit.Validation.ExpectFunction(cb);
23497
+ Fit.Array.Add(onSelectHandlers, cb);
23498
+ }
23499
+
23500
+ /// <function container="Fit.Controls.ListViewTypeDefs" name="OnSelectedEventHandler">
23501
+ /// <description> OnSelected event handler </description>
23502
+ /// <param name="sender" type="$TypeOfThis"> Instance of control </param>
23503
+ /// <param name="item" type="{ Title: string, Value: string }"> Selected item </param>
23504
+ /// </function>
23505
+
23506
+ /// <function container="Fit.Controls.ListView" name="OnSelected" access="public">
23507
+ /// <description>
23508
+ /// Register event handler fired when item is selected.
23509
+ /// The following arguments are passed to event handler function:
23510
+ /// Sender (ListView) and Item (with Title (string) and Value (string) properties).
23511
+ /// </description>
23512
+ /// <param name="cb" type="Fit.Controls.ListViewTypeDefs.OnSelectedEventHandler"> Event handler function </param>
23513
+ /// </function>
23514
+ this.OnSelected = function(cb)
23515
+ {
23516
+ Fit.Validation.ExpectFunction(cb);
23517
+ Fit.Array.Add(onSelectedHandlers, cb);
23518
+ }
23519
+
23041
23520
  this.Destroy = Fit.Core.CreateOverride(this.Destroy, function(calledInternally)
23042
23521
  {
23043
23522
  Fit.Validation.ExpectBoolean(calledInternally, true);
@@ -23063,7 +23542,7 @@ Fit.Controls.ListView = function(controlId)
23063
23542
  me.Destroy(true); // PickerBase.Destroy()
23064
23543
  }
23065
23544
 
23066
- me = list = active = persistView = scrollPositionTop = highlightFirst = firstWasHighlighted = isIe8 = null;
23545
+ me = list = active = persistView = scrollPositionTop = highlightFirst = firstWasHighlighted = isIe8 = onSelectHandlers = onSelectedHandlers = null;
23067
23546
  });
23068
23547
 
23069
23548
  // ============================================
@@ -23175,6 +23654,37 @@ Fit.Controls.ListView = function(controlId)
23175
23654
  }
23176
23655
  }
23177
23656
 
23657
+ function fireOnSelectHandlers(item)
23658
+ {
23659
+ Fit.Validation.ExpectObject(item);
23660
+ Fit.Validation.ExpectString(item.Title);
23661
+ Fit.Validation.ExpectString(item.Value);
23662
+
23663
+ var selectionCanceled = false;
23664
+
23665
+ Fit.Array.ForEach(onSelectHandlers, function(handler)
23666
+ {
23667
+ if (handler(me, item) === false)
23668
+ {
23669
+ selectionCanceled = true;
23670
+ }
23671
+ });
23672
+
23673
+ return selectionCanceled === true ? false : true; // Return False if canceled
23674
+ }
23675
+
23676
+ function fireOnSelectedHandlers(item)
23677
+ {
23678
+ Fit.Validation.ExpectObject(item);
23679
+ Fit.Validation.ExpectString(item.Title);
23680
+ Fit.Validation.ExpectString(item.Value);
23681
+
23682
+ Fit.Array.ForEach(onSelectedHandlers, function(handler)
23683
+ {
23684
+ handler(me, item);
23685
+ });
23686
+ }
23687
+
23178
23688
  function repaint()
23179
23689
  {
23180
23690
  if (isIe8 === true)