23
$\begingroup$

Imagine that you have a PopupMenu's list that requires a lot of scrolling through the selections. Is there a way to create a control that will only display a subsection of the complete list?

$\endgroup$
  • $\begingroup$ How closely related is that: Autocomplete InputField? $\endgroup$ – Kuba Dec 20 '15 at 4:34
  • 1
    $\begingroup$ @Kuba closely related. In this present case the usage requirement was minimise the choices in a large popup but the autocomplete input field looks like an alternative solution to the same, or similar, problem. The reason why the solution you have linked is not useful to me is that is inserts a column of menu choices into your interface whereas a popup is overlayed on top. Therefore the popup does not disrupt your interface design layout. It would be nice if the popup could automatically open when you type in the input field $\endgroup$ – Mike Honeychurch Dec 20 '15 at 4:57
  • $\begingroup$ @kuba please see my edit $\endgroup$ – Mike Honeychurch Dec 20 '15 at 5:05
  • $\begingroup$ I've seen, thanks for clarification. :) $\endgroup$ – Kuba Dec 20 '15 at 5:19
23
$\begingroup$

I have finished a project where widgets like this have been useful so I thought I would share. By combining InputField and PopupMenu a searchable popup selection table can be created.

searchPopup[Dynamic[selection_], list_List] := 
 DynamicModule[{x = ""},
  Grid[{{
     InputField[Dynamic[x], String,
      Appearance -> "Frameless",
      ContinuousAction -> True,
      FieldHint -> "Search",
      FieldSize -> 10],
     Spacer[5],
     Dynamic[
      PopupMenu[Dynamic[selection,(selection = #; x = #) &], 
       Cases[list, 
        z_String /; StringMatchQ[z, x ~~ ___, IgnoreCase -> True]], 
       Null,
       Opener[True, Appearance -> Small]],
      TrackedSymbols :> {x}]
     }},
   Alignment -> {{Right, Left}, Center},
   Background -> White,
   Frame -> True,
   FrameStyle -> GrayLevel[0.8]]
  ]

list = {"Ajax", "admixes", "Acrux", "Alex", "affix", "admixtures", 
   "Alexei", "affixed", "admixing", "ambidextrously", "admixed", 
   "admix", "affixing", "ambidextrous", "Alexandrians", "Alexandria", 
   "Anaxagoras", "Alexandrian", "annexation", "Alexandra", 
   "admixture", "Alexis", "Alexanders", "annex", "affixes", 
   "ambidexterity", "Alexander"};
selection = "";

searchPopup[Dynamic[selection], list]

enter image description here

Edit

To address @kuba's comment by way of example in the GIF we have a toy interface. Normally you would not want your interface elements moving around all over the place. Therefore using popup menu is a good way to achieve this search menu objective.

enter image description here

$\endgroup$
15
$\begingroup$

Based on undocumented FrontEnd`AttachCell:

And Experimental`Autocompletebut this can be replaced with Mike's approach.

Need to work on style, automatic scrolling and some edge cases, but already works quite well. Feedback appreciated.

Column[{
  searchPopup2[Dynamic[selection], list],
  Graphics@Disk[]
  }]

enter image description here

Code

    searchPopup2[Dynamic[var_], list_] := 
 DynamicModule[{thisBox, currentList = {}, autocompleteF, 
   attachedCell, dynamicFunction, whatToDo, lastLength, cPos = 1, 
   itemWrapper, attachTo, dropMenu, menu}, 
   EventHandler[
     InputField[Dynamic[First@List@var, dynamicFunction], String, 
      FieldHint -> "Search", 
      ContinuousAction -> 
       True], {"MouseClicked" :> (If[ Length@currentList > 0, attachedCell = attachTo[thisBox, menu[currentList]]]), 
      "DownArrowKeyDown" :> (cPos = 
         Mod[cPos + 1, Length@currentList, 1]), 
      "UpArrowKeyDown" :> (cPos = 
         Mod[cPos - 1, Length@currentList, 1]), "ReturnKeyDown" :> (
        lastLength = Length@currentList;
        currentList = autocompleteF[var];
        If[Length[currentList] > 0, var = currentList[[cPos]]; 
         FinishDynamic[]]; dropMenu[])}, PassEventsDown -> True], 
  SynchronousInitialization -> False, 
  Initialization :> (thisBox = EvaluationBox[];
    autocompleteF = Autocomplete[list];
    currentList = autocompleteF@var;

    dynamicFunction = (var = #;
       lastLength = Length@currentList;
       currentList = autocompleteF[var];

       whatToDo[]) &;

    whatToDo[] := 
     Which[lastLength != 0 && Length[currentList] == 0, dropMenu[], 
      Or[lastLength == 0 && Length[currentList] > 0, 
       And[lastLength != 0 && Length[currentList] != 0, 
        Not@MatchQ[attachedCell, _CellObject]]], 
      attachedCell = attachTo[thisBox, menu[currentList]]];

    attachTo[parentbox_, what_] := 
     MathLink`CallFrontEnd[
      FrontEnd`AttachCell[parentbox, 
       ToBoxes[ExpressionCell[what, StripOnInput -> True, 
         Background -> White, CellFrameColor -> GrayLevel@.8, 
         CellFrameMargins -> 0, CellFrame -> 2]], {Automatic, {Left, 
         Bottom}}, {Left, Top}, 
       "ClosingActions" -> {"ParentChanged", "EvaluatorQuit", 
         "OutsideMouseClick"}]];

    SetAttributes[menu, HoldFirst];
    menu[currentList_] := 
     EventHandler[
      Pane[Dynamic[
        Column[MapIndexed[itemWrapper, currentList], Spacings -> 0], 
        TrackedSymbols :> {currentList}], {200, {All, 300}}, 
       AppearanceElements -> None, Scrollbars -> {False, True}], 
      "MouseExited" :> (dropMenu[];)];
    itemWrapper[item_, {pos_}] := 
     MouseAppearance[#, "LinkHand"] &@
      EventHandler[
       Framed[item, ImageSize -> {Full, Automatic}, 
        FrameStyle -> None, 
        Background -> 
         Dynamic[If[pos == cPos, CurrentValue@"PanelBackground", 
           White]]], {"MouseClicked" :> (pos == cPos;
          var = item;
          dropMenu[];
          lastLength = Length@currentList;
          currentList = autocompleteF[var];),
        "MouseEntered" :> (cPos = pos)}, PassEventsDown -> True];
    dropMenu[] := (
      If[MatchQ[attachedCell, _CellObject],
       NotebookDelete[attachedCell];
       attachedCell =.
       ])
    )]
$\endgroup$
  • $\begingroup$ I did not know about FrontEnd`AttachCell. That could be useful. Did you learn about it by spelunking something? Do you know of anyway to have a branched pulldown or popup menu. For example, instead of using the second argument of dynamic to populate a menu in another popup could the second menu appear next to the first menu and be populated based on the selection in the first menu? We see this on web pages. If you know how to do this i can ask this as a question and you can post the answer. $\endgroup$ – Mike Honeychurch Dec 20 '15 at 5:48
  • $\begingroup$ @MikeHoneychurch could you link where I can take a look at such functionality? I will answer when take a closer look. All I know about AttachCell is posted in the topic I've linked on top. p.s. have to go now, it's quite late(early) :P Will be in touch. $\endgroup$ – Kuba Dec 20 '15 at 5:51
  • $\begingroup$ I am finishing now too. I'll se if I can find a pull down menu that shows the functionality. $\endgroup$ – Mike Honeychurch Dec 20 '15 at 5:54
  • $\begingroup$ very nice, but I think there is a typo which made my mathematica complain: there is a double ';;' after EvaluationBox[];; in the Initialization which makes Mathematica complain and the widget not work. $\endgroup$ – Albert Retey Dec 20 '15 at 10:23
  • $\begingroup$ @AlbertRetey correct, thanks :) $\endgroup$ – Kuba Dec 20 '15 at 10:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.