Take the 2-minute tour ×
Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

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.

share|improve this question
    
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. –  Kuba Jul 13 '13 at 19:38

4 Answers 4

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~~__]]
    )

]
share|improve this answer
    
Best so far! Very nicely done. :) –  Pickett Dec 11 '13 at 19:52
    
@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] –  Kuba Dec 11 '13 at 20:14
    
@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 :) –  Pickett Dec 11 '13 at 20:20
    
@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. –  Kuba Dec 11 '13 at 20:54
1  
@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). –  teedr Dec 12 '13 at 0:41

Maybe overkill but it was educational to try:

DynamicModule[{},
 EventHandler[
  Overlay[{
          Dynamic@Framed[
             Row[{Style[x, Transparent, 15, Bold], Style[rest, GrayLevel@.6, 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.

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

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
 ]
share|improve this answer
    
Have you looked at wormholes? I don't have time to check it out, but it might be away to avoid a global variable. –  Michael E2 Aug 23 '13 at 17:23
    
@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). –  Liam William Aug 23 '13 at 17:35
    
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. –  Kuba Aug 23 '13 at 18:56
    
@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. –  Liam William Aug 24 '13 at 2:45
    
@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. –  Liam William Aug 25 '13 at 3:43

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[CountryData[]]
share|improve this answer
    
+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. –  Liam William Oct 19 '13 at 16:51
    
@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. –  Pickett Oct 19 '13 at 20:20
    
@LiamWilliam The new version does not have that problem, the entire framed box is now clickable! –  Pickett Oct 19 '13 at 23:20

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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