19
$\begingroup$

Is there a way to define a InputField that displays a list of options as you type? More specifically I'm looking for an InputField that specifically autocompletes as you type.

The functionality is similar to an InputField and MenuView element.

Examples of such feature would be the textbox here or here.

enter image description here

EDIT: Ideally the interface should work as much like Google's homepage as possible.

Specific Features

  1. Menu is not case sensitive
  2. Clickable items in drop down menu
  3. Ideally, the menu is hidden until user types into textbox
  4. Restrict number of items in output.

Here is some example code I am working on. It currently only meets criteria #2.

EDIT2: Here is my next set of code which meets the following criteria: case insensitive, has clickable menus, and restricts the number of output. One funny thing, is that the menu isn't clickable all the way across, but only where the text is.

$\endgroup$
1
  • $\begingroup$ Probably there is something better somewhere in SE so I'm posting it as comment: Column[{InputField[Dynamic@x, String, ContinuousAction -> True], Pane[ Dynamic@Column[Names["System`" ~~ x ~~ "*"]], {220, 300}] }] You can start with this. $\endgroup$
    – Kuba
    Jul 13, 2013 at 19:38

4 Answers 4

11
$\begingroup$

My humble contribution:

(* Use this function to style list elements *)
listItemStyle[item_] := Mouseover[#, Style[#, Background -> LightBlue]] &@ MouseAppearance[Framed[item], "LinkHand"];

(* This filters the list of data and returns a clickable list *)
SetAttributes[autoComplete, HoldFirst];
autoComplete[s_, data_] := If[
  StringLength[s] > 0,
  EventHandler[#, {"MouseClicked" :> (s = #)}] & /@ Select[data, StringMatchQ[#, s ~~ __, IgnoreCase -> True] &],
  {}
  ]

(* Responsible for final output *)
SetAttributes[inputWithAutoComplete, HoldFirst];
inputWithAutoComplete[s_, data_, max_: 5] := Column[{
   InputField[Dynamic[s], String, ContinuousAction -> True],
   Dynamic[listItemStyle /@ (Take[#, Min[Length[#], max]] &@autoComplete[s, data] /. {} -> "") // TableForm]
   }, Left, 1]

Try it using:

data = CountryData[];
s = "";
inputWithAutoComplete[s, data, 10]

dropdown

Version 2.0

I started all over and refactored the code to make it better. There are many advantages so this is definitely the one to use, but I'm not deleting what I posted earlier since the new version is also substantially different, and both may be of interest.

(* Returns expr with styles off or on depending on whether the expr \
is hovered. Additionally the cursor as the mouse if hovering the \
expression can be set. *)

MouseoverStyled[expr_, off_, on_, cursor_: "LinkHand"] := Deploy[
  MouseAppearance[Mouseover[
    Style[expr, off],
    Style[expr, on]
    ], cursor]
  ]

(* Returns a list item with the given text *)

item[text_] := MouseoverStyled[Framed[text], {}, {Background -> LightBlue}]

(* Create a list of clickable items *)

items[labels_, func_] := EventHandler[
     item[#], {
      "MouseClicked" :> func[#]
      }
     ] & /@ labels;

(* Picks out the n first elements that start with input *)

filter[input_, data_, n_: 10] := Take[#, Min[Length[#], n]] &@Select[data, StringMatchQ[#, input ~~ __, IgnoreCase -> True] &]

(* Final presentation *)

inputWithAutoomplete[data_] := DynamicModule[{s = ""},
  Column[{
    InputField[Dynamic[s], String, ContinuousAction -> True],
    Dynamic@If[StringLength[s] > 0, Column[items[filter[s, data], (s = #) &]], ""]
    }]]

Test it like this:

inputWithAutoomplete[Last /@ CountryData[]]

or with just CountryData[] for more countries, but I noticed this can be quite slow (it didn't use to be - but since V10 with the EntityValue thing.)

$\endgroup$
3
  • $\begingroup$ +1 Much more compact then mine. What version of Mathematica are you using? currently in M8 the MouseClick's don't always seem to register for the drop down menu. $\endgroup$
    – William
    Oct 19, 2013 at 16:51
  • $\begingroup$ @LiamWilliam I'm using v.9, but that's not the problem; by changing the background color and the cursor, I'm tricking you to believe you're hovering the clickable area. In fact you have to click on the text. I tried to correct it with Pane and some other things but ran out of time. $\endgroup$
    – C. E.
    Oct 19, 2013 at 20:20
  • $\begingroup$ @LiamWilliam The new version does not have that problem, the entire framed box is now clickable! $\endgroup$
    – C. E.
    Oct 19, 2013 at 23:20
11
$\begingroup$

I also took a crack at this. I think I made it look pretty close to the jquery example you posted. Figuring out how to move the insertion point to the end of the word once a suggestion is selected was a bit of a struggle. As a result, there's a DynamicWrapper in there that may be unstable.

Input is the list of possible values from which you'd like to draw suggestions.

Enjoy!

autocompleteInputField[possibilities:{_String..}]:=DynamicModule[{nb,ind=0,txt,suggestions,suggestedElements,returnKeyTrigger=False},

suggestions = Dynamic[
                If[Length[suggestedElements] < 1 || StringLength[txt] <= 1,
                    "",
                (* else *)
                    Column[
                        MapIndexed[
                            Button[
                                Function[panel,Mouseover[Style[panel,Background->If[ind===First[#2],LightBlue,White]],Style[panel,Background->LightBlue]]]@Pane[#1,ImageMargins->0],
                                (
                                    txt=#1;
                                    returnKeyTrigger=True
                                ),
                                Appearance->"Frameless"
                            ]&,
                            If[!(If[Length[suggestedElements]===1,First[suggestedElements]===txt,False]) && StringLength[txt]>1,
                                suggestedElements,
                                {}
                            ]
                        ],
                        Background->White,
                        Frame->True
                    ]
                ]
            ];
Column[{
    DynamicWrapper[
        EventHandler[
            InputField[
                Dynamic[txt],
                String,
                ContinuousAction->True,
                BaseStyle->{FontFamily->"Arial"}
            ],
            {
                "DownArrowKeyDown":>(ind=Min[ind+1,Length[suggestedElements]]),
                "UpArrowKeyDown":>(ind=Max[0,ind-1]),
                "ReturnKeyDown":>
                    If[Length[suggestedElements]>0 && ind>0,
                        txt=suggestedElements[[ind]];
                        ind=0;
                        returnKeyTrigger=True
                    ]
            }
        ],
        If[returnKeyTrigger,
            returnKeyTrigger=False;
            SelectionMove[nb,Next,Word]
        ],
        SynchronousUpdating->False
    ],
    suggestions
}],

Initialization:>(
    nb = EvaluationNotebook[];
    txt="";
    suggestedElements:=Pick[possibilities,StringMatchQ[possibilities,txt~~__]]
    )

]
$\endgroup$
9
  • $\begingroup$ Best so far! Very nicely done. :) $\endgroup$
    – C. E.
    Dec 11, 2013 at 19:52
  • $\begingroup$ @Anon I agree but those solutions has this problem that are not handy in context od GUIs. Try to fit this somewhere. Panel[autocompleteInputField[Names["System`*"]], ImageSize -> {500, 200}, Alignment -> Center] $\endgroup$
    – Kuba
    Dec 11, 2013 at 20:14
  • $\begingroup$ @Kuba Yes, yours is the one that is the most useful solution, since in MMA we can't use a z-index to make the menu float above its surroundings. But taking the question on face value, this is the best answer :) $\endgroup$
    – C. E.
    Dec 11, 2013 at 20:20
  • $\begingroup$ @Anon As I've said, I agree :) I'm wondering what type of object is that floating menu, I have no experience with other languages so can't compare. $\endgroup$
    – Kuba
    Dec 11, 2013 at 20:54
  • 1
    $\begingroup$ @LiamWilliam M9. Perhaps you didn't type in enough characters? The above snippet waits until at least 2 characters are input before making suggestions (you can easily change the code to remove that logic). $\endgroup$
    – teedr
    Dec 12, 2013 at 0:41
7
$\begingroup$

Maybe overkill but it was educational to try:

DynamicModule[{},
 EventHandler[
  Overlay[{
          Dynamic@Framed[
             Row[{Style[x, Transparent, 15, Bold], Style[rest, [email protected], 15, Bold]}],
              ImageSize -> {280, 30}, Alignment -> Top, FrameMargins -> {{5, 0}, {0, 1}}],
          InputField[Dynamic@x, String, BaseStyle -> {Bold, Black, 15}, Alignment -> Top,
                     ContinuousAction -> True, ImageSize -> {280, 30}, FrameMargins -> 0]

          }, {2, 1}, 2]
         , {"ReturnKeyDown" :> (x = x ~~ rest), 
            "UpArrowKeyDown" :> (names = RotateLeft@names), 
            "DownArrowKeyDown" :> (names = RotateRight@names)}
             ]
 , Initialization :> (x = "";
     names := Names["System`" ~~ x ~~ "*"]; 
     rest := If[Length@names == 0 \[Or] x == "", "", 
                StringDrop[First[names], 
                Clip[StringLength@x, {0, StringLength@First[names]}]]]
                     )
 ]

Mathematica graphics

Please forgive me but I have no time to make it more user friendly.

There is no menu, only auto filling which you accept with Enter or you can also switch between fillings with arrows.

I have a couple of ideas, I'm going to write them tomorrow.

$\endgroup$
6
  • $\begingroup$ A little different then the original question but certainly a nice alternative. Quit similar to a MenuView element. +1 $\endgroup$
    – William
    Jul 13, 2013 at 21:52
  • $\begingroup$ @Liam Thanks, tell me more about what do You need it for. I will take this info under consideration while updating ;) $\endgroup$
    – Kuba
    Jul 13, 2013 at 22:30
  • $\begingroup$ Your comment was slightly closer to the desired interface, because displayed a list of items. I have updated the question with more explanation. $\endgroup$
    – William
    Jul 14, 2013 at 0:57
  • $\begingroup$ @Liam I know but " More specifically I'm looking for an InputField that specifically autocompletes as you type." :) $\endgroup$
    – Kuba
    Jul 14, 2013 at 8:29
  • $\begingroup$ Well both versions do autocomplete, just in different ways ;) googles homepage is a good example, or the autocomplete doc search built into mathematica 9 $\endgroup$
    – William
    Jul 14, 2013 at 13:02
7
$\begingroup$

Note: This appears to really slowly in M9, although it works well in M8. It probably is better to use teedr's until it can be figured out what is causing the slow speeds.

The following seems to work pretty well. I wrapped the options in a Pane and Framed so the entire row is clickable.

ClearAll[AutoInputField];
SetAttributes[AutoInputField, HoldAllComplete];

AutoInputField /: MakeBoxes[AutoInputField[field_,
    list_: {}, displayNum_: Infinity, imageSize_: 100], form_] := Module[{focus = False},

   With[{tag = SymbolName@Unique[StringJoin["g", ToString[$SessionID]]] },

    CurrentValue[$FrontEndSession, {TaggingRules, tag, "x"}] = "";

    field = Dynamic@DynamicModule[{Only = Function[{l, n}, Take[l, Min[Length[l], n]]]},

       If[focus ||
         Not[CurrentValue[$FrontEndSession, {TaggingRules, tag, "x"}] === ""]
        , Column[Map[Function[
           EventHandler[Framed[Pane[#,
              ImageSize -> Dynamic[(CurrentValue[EvaluationNotebook[], WindowSize][[1]] - 120)]
              ], FrameMargins -> 0, FrameStyle -> Gray], {

             "MouseClicked" :> (
               CurrentValue[$FrontEndSession, {TaggingRules, tag, "x"}] = #
               )}]],

          Select[list, StringMatchQ[#,
              CurrentValue[$FrontEndSession, {TaggingRules, tag, "x"}]
               ~~ ___, IgnoreCase -> True] &]~Only~displayNum]], ""]
       ];

    ToBoxes@EventHandler[
      InputField[
       Dynamic[
        CurrentValue[$FrontEndSession, {TaggingRules, tag, "x"}]
        ],
       String, ContinuousAction -> True, ImageSize -> imageSize], {

       "ReturnKeyDown" :> ( (* focusForcedHidden *)
         CurrentValue[$FrontEndSession, {TaggingRules, tag, "x"}] = 
              Append[
                Select[list, StringMatchQ[#,
                   CurrentValue[$FrontEndSession, {TaggingRules, tag, "x"}]
                ~~ ___, IgnoreCase -> True] &]
            , ""][[1]];
         focus = False;
         ),
       "MouseEntered" :> ( (* focusForcedDisplay *)
         focus = True;
         ),
       "MouseExited" :> ( (* focusForcedDisplay *)
         focus = False;
         ),
       "MouseUp" :> ( (* If[x===list[[1]] && Length@list===1] *)
         focusForce = True;
         ),
       "KeyDown" :> ( 
         focusForce  = False;
         )
       },
      PassEventsDown -> True
      ]
    ]];

And the following outputs an AutoCompelete textbox.

(* AutoInputField[d,Map[(ToString[#])&,Table[i^2,{i,10}]]] *)
AutoInputField[d, {"ab", "abc", "100", "1", "16"}]
Pane[d,
 ImageSize -> {200, 80},
 Scrollbars -> True
 ]
$\endgroup$
5
  • $\begingroup$ Have you looked at wormholes? I don't have time to check it out, but it might be away to avoid a global variable. $\endgroup$
    – Michael E2
    Aug 23, 2013 at 17:23
  • $\begingroup$ @MichaelE2 I hadn't seen wormwholes, those are quit cool. I managed to refactor the code to something much shorter(although I unfortunately didn't use wormwholes). $\endgroup$
    – William
    Aug 23, 2013 at 17:35
  • $\begingroup$ I'm glad you are trying too. Notice that there is a problem with a place for such menu, you have to reserve it while built-in menu is displaying over the content. It would be also nice to scroll the content of current "help list", now it's fixed now. But mine is not ideal too, +1. $\endgroup$
    – Kuba
    Aug 23, 2013 at 18:56
  • $\begingroup$ @Kuba Ideally it would be nice to hide the list except when the InputField is focused but I am not really sure how to force the input field to come into focus. $\endgroup$
    – William
    Aug 24, 2013 at 2:45
  • $\begingroup$ @Kuba I opened a question about focus and InputField. As temporary workaround I am using MouseEntered and MouseExited as a way to partially emulate focus. $\endgroup$
    – William
    Aug 25, 2013 at 3:43

Your Answer

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

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