24
$\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$
4
  • $\begingroup$ How closely related is that: Autocomplete InputField? $\endgroup$
    – Kuba
    Dec 20, 2015 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$ Dec 20, 2015 at 4:57
  • $\begingroup$ @kuba please see my edit $\endgroup$ Dec 20, 2015 at 5:05
  • $\begingroup$ I've seen, thanks for clarification. :) $\endgroup$
    – Kuba
    Dec 20, 2015 at 5:19

2 Answers 2

24
$\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$
1
  • $\begingroup$ This Popup code is indeed very useful. I have a Manipulate which has two PopupMenus with a very long list. But I am having trouble incorporating your searchPopup[Dynamic[selection], list] into the Manipulate. Could you guide me in using it? $\endgroup$ Nov 25, 2021 at 23:10
16
$\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 -> [email protected], 
         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$
8
  • $\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$ Dec 20, 2015 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, 2015 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$ Dec 20, 2015 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$ Dec 20, 2015 at 10:23
  • $\begingroup$ @AlbertRetey correct, thanks :) $\endgroup$
    – Kuba
    Dec 20, 2015 at 10:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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