How to dynamically change popup list on AutoCompleteTextField

671 views
Skip to first unread message

Tiago Lopes

unread,
Nov 1, 2013, 7:13:25 AM11/1/13
to codenameone...@googlegroups.com
Hello!

I want to dynamically manage popup info depending on AutoCompleteTextField getText() data, every time user types in.
Thing is I don't know how to change it. I saw that there is a setCompletionRenderer(ListCellRenderer) method, but I just don't know how to use/define it.
I want to call a web-service with getText() data and place the response on the popup. The idea is to filter data coming from server and limit it to a given number of results matching user input.

For test purpose I have the following code:
@Override
protected void beforeShareDoc(Form f) {
    final String data[] = new String[] {
        "AA A - A - ID1",
        "AB B - B - ID2",
        "ABC C - A - ID3",
        "BA B - C - ID4",
        "CA C - D - ID5", 
        "CB B - C - ID6"};
    final AutoCompleteTextField users = new AutoCompleteTextField(data);
    users.setRows(2);
    users.setHint(" Search for username...");
    users.setVerticalAlignment(TextArea.CENTER);
    users.addDataChangeListener(new DataChangedListener() {
        public void dataChanged(int type, int index) {
            System.out.println("You changed data to \""+users.getText()+"\".");
            // CALL WEBSERVICE AND UPDATE DATA
        }
    });
    Container ctn = findMainAutoContainer(f);
    ctn.addComponent(2, users);
}
 
Can you give me a example or explain me how to do it?

Thanks for your time.

Shai Almog

unread,
Nov 1, 2013, 3:48:03 PM11/1/13
to codenameone...@googlegroups.com
Hi,
the renderer doesn't matter for this.
For this constant data you don't need to do anything, it will just work.

If you want to fetch data dynamically you need to override filter() which will be invoked every time the text changes.
You will then also need to override getSuggestionModel() where you can return your own list model that the filter method can mutate as data changes/arrives.

Tiago Lopes

unread,
Nov 4, 2013, 8:17:56 AM11/4/13
to codenameone...@googlegroups.com
Hi Shai,
thanks for answer.

I've been trying to do it as you said and I guess I'm close to get the answer to my problem.
I'm not sure if I'm doing it the right way, and if I'm not, I'm already sorry for your time loss. :P
At this moment I managed to call my webservice, update my FilterProxyListModel<String> filter and getSuggestionModel() method is returning the right info. Now my problem is just to update popup. Invoking updateFilterList() shouldn't be enough to reload it? Since getSuggestionModel() gets it correct info, he should revalidate it, I guess. :S

Here's my code:

    @Override
    protected void beforeShareDoc(Form f) {
        // Get 10 first users filtered by ""
        String u[] = getUsersByName(user.getIp(), "");
        final FilterProxyListModel<String> userslm = new FilterProxyListModel<String>(new DefaultListModel<String>(u));

        final AutoCompleteTextField users = new AutoCompleteTextField(userslm){
            private FilterProxyListModel<String> filter = new FilterProxyListModel<String>(userslm);
            private Container popup = new Container(new BoxLayout(BoxLayout.Y_AXIS));
            String lastText = null;
            
            @Override
            protected ListModel<String> getSuggestionModel() {
                int i=0;

                /*System.out.print("Just returned: ");
                for(i=0; i<filter.getSize(); i++)
                {
                    System.out.print(" "+filter.getItemAt(i));
                }
                System.out.println("");*/

                return filter;
            }
            
            @Override
            protected boolean filter(String text) {
                if(filter != null)
                {
                    // if getText() method has equal data to last getText() does nothing
                    // also avoid updateFilterList() to enter into infinite loop state
                    if(lastText==null || !lastText.equalsIgnoreCase(text))
                    {
                        lastText = text;
                        int i=0;

                        /*System.out.print("I had: ");
                        for(i=0; i<filter.getSize(); i++)
                        {
                            System.out.print(" "+filter.getItemAt(i));
                        }
                        System.out.println("");*/

                        // Get 10 first users filtered by "text"
                        String u[] = getUsersByName(user.getIp(), text);
                        filter = new FilterProxyListModel<String>(new DefaultListModel<String>(u));
                        filter.filter(text);

                        /*System.out.print("I have now: ");
                        for(i=0; i<filter.getSize(); i++)
                        {
                            System.out.print(" "+filter.getItemAt(i));
                        }
                        System.out.println("");*/

                        updateFilterList();
                        System.out.println("Just updated.");
                        return true;
                    }
                }
                return false;
            }
        };
        users.setRows(2);
        users.setHint(" Search for username...");
        users.setVerticalAlignment(TextArea.CENTER);
        users.addDataChangeListener(new DataChangedListener() {

            public void dataChanged(int type, int index) {
                //System.out.println("You changed data to \""+users.getText()+"\".");
            }
        });
I noticed that AutoCompleteTextField ActionListener fires addPopup() the right way and when I click outside and then inside of TextField it really updates my list. I want to do the same thing after I receive data from webservice.

Tiago

Shai Almog

unread,
Nov 4, 2013, 11:32:56 AM11/4/13
to codenameone...@googlegroups.com
Hi,
when you change a list model you need to fire a data change event.  Rather than just change the underlying data just invoke addItem on the default list model and it will work.

Gareth Murfin

unread,
Nov 20, 2013, 8:43:54 AM11/20/13
to codenameone...@googlegroups.com
Unfortunately I have not been able to get this to work either I have to click off and on the textfield to get the live results to show up, Ive wasted far too much time on it now, I wish there was a live example out there which worked :-( it seems getSuggestionModel isnt called until you click on the textfield or something .

Shai Almog

unread,
Nov 20, 2013, 12:04:30 PM11/20/13
to codenameone...@googlegroups.com
You are mutating list model items without firing the right events so the view isn't aware of that change.

Gareth Murfin

unread,
Nov 21, 2013, 6:12:34 AM11/21/13
to codenameone...@googlegroups.com
How do you fire the right events though??? I tried additem on the list model but it still doesnt help, i need to click off and on to get it to popup, its quite integral to my app too.. any example code anywhere of this? without a live list it works fine

Chen Fishbein

unread,
Nov 21, 2013, 7:00:22 AM11/21/13
to codenameone...@googlegroups.com
if you implemented your own list model, you must trigger the data events to the view.

Notice as part of the ListModel interface there are two methods you need to implement

    public void addDataChangedListener(DataChangedListener l);
    public void removeDataChangedListener(DataChangedListener l);

The List registers itself to listen to data events using the above methods, in your model you need to fire the events to the listeners when the data has changed, take a look at the DefaultListModel src to better understand how this works, you can also extends the DefaultListModel to get this by inheritance.








Gareth Murfin

unread,
Nov 21, 2013, 8:23:01 AM11/21/13
to codenameone...@googlegroups.com
I didnt implement a list model I just extended AutoCompleteTextField replaced filter() and getSuggestionModel() and thats literally it. As I type in the list I have some messages printing out in filter() I can clearly see the auto complete working, and it returns true in the right circumstances etc.  However the popup does not appear. When I click on the form in the background instead of having focus on the textfield the popup appears as expected (and I see that it calls getSuggestionModel at this point). What am I doing wrong?


--
You received this message because you are subscribed to a topic in the Google Groups "CodenameOne Discussions" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/codenameone-discussions/hRHzjrjnWM8/unsubscribe.
To unsubscribe from this group and all its topics, send an email to codenameone-discu...@googlegroups.com.
Visit this group at http://groups.google.com/group/codenameone-discussions.
To view this discussion on the web visit https://groups.google.com/d/msgid/codenameone-discussions/90d4b86b-ed0e-409c-b3d5-ed9c32420cca%40googlegroups.com.

For more options, visit https://groups.google.com/groups/opt_out.



--
Gareth Murfin
(Android Freelancer)

Gareth Murfin

unread,
Nov 21, 2013, 8:23:44 AM11/21/13
to codenameone...@googlegroups.com
ps in getSuggestionModel I do this, maybe its too hacky or something :) 

 @Override
      public ListModel<String> getSuggestionModel() {
       // String[] s = new String[]{"arse","bandits"};
        _("/////////////////////////////////////////////getSuggestionModel");
        
       DefaultListModel dlm = null;
        
         String[] s;
        if (InfoHolder.googleDirectionsPlacePredictions==null)
        {
            _("no predictions yet");
            s = new String[] {""};
            
        }
        else
        {
            s = new String[InfoHolder.googleDirectionsPlacePredictions.size()];
            for (int i=0; i<InfoHolder.googleDirectionsPlacePredictions.size(); i++)
            {
                s[i]=(String)InfoHolder.googleDirectionsPlacePredictions.elementAt(i);
                if (dlm==null)
                {
                    dlm = new DefaultListModel<String>(s);
                }
                _("adding item to DLM");
                dlm.addItem(s[i]);//TO FIRE NOTIFY
            }
            _("will return "+s.length+" predictions");
            //repaint();
        }
        
         if (dlm==null)
         {
             return   new DefaultListModel<String>(s);
         }
        
        
        return dlm;//filter;
    }


Chen Fishbein

unread,
Nov 21, 2013, 8:42:53 AM11/21/13
to codenameone...@googlegroups.com
doing this in the getSuggestionModel() is wrong, the getSuggestionModel() is called once before the popup is shown.
You need to return your own List model from the getSuggestionModel() and implement:

getItemAt(int index);

int getSize();

(extend DefaultListModel for simplicity).


 

Gareth Murfin

unread,
Nov 21, 2013, 9:22:29 AM11/21/13
to codenameone...@googlegroups.com
I appreciate the help, I think I implemented a list model correctly:

public class MyListModel extends DefaultListModel {
    
    @Override 
    public Object getItemAt(int index)
    {
        return InfoHolder.googleDirectionsPlacePredictions.get(index);
    }
   
    @Override 
    public int getSize()
    {
        return InfoHolder.googleDirectionsPlacePredictions.size();
    }
}


This now works a lot better, however I still have to once click on the form (not on textfield) to get the popup to start working, once i do that and go back to the textfield it seems to work fine... any other things that might be causing my issue?


--
You received this message because you are subscribed to a topic in the Google Groups "CodenameOne Discussions" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/codenameone-discussions/hRHzjrjnWM8/unsubscribe.
To unsubscribe from this group and all its topics, send an email to codenameone-discu...@googlegroups.com.
Visit this group at http://groups.google.com/group/codenameone-discussions.

For more options, visit https://groups.google.com/groups/opt_out.

Shai Almog

unread,
Nov 21, 2013, 2:16:43 PM11/21/13
to codenameone...@googlegroups.com
You are modifying googleDirectionsPlacePredictions externally.
How would the List component know of your modifications?
I suggest looking at the source for DefaultListModel.

Gareth Murfin

unread,
Nov 21, 2013, 2:34:52 PM11/21/13
to codenameone...@googlegroups.com
Ahaaa thanks!


--
You received this message because you are subscribed to a topic in the Google Groups "CodenameOne Discussions" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/codenameone-discussions/hRHzjrjnWM8/unsubscribe.
To unsubscribe from this group and all its topics, send an email to codenameone-discu...@googlegroups.com.
Visit this group at http://groups.google.com/group/codenameone-discussions.

For more options, visit https://groups.google.com/groups/opt_out.

Gareth Murfin

unread,
Nov 21, 2013, 3:22:28 PM11/21/13
to codenameone...@googlegroups.com
Thanks for all the help guys! I did it!!!! Here is the code for a live autocompletetextfield, in this case I go off to get predictions from googleapis... 






SUBCLASS OF AUTO COMPLETE TEXTFIELD:
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package userclasses.silverputtyone.components;

import com.codename1.ui.AutoCompleteTextField;
import com.codename1.ui.events.DataChangedListener;
import com.codename1.ui.list.DefaultListModel;
import com.codename1.ui.list.FilterProxyListModel;
import com.codename1.ui.list.ListModel;
import java.util.Vector;
import userclasses.NetworkParserSpecifics;
import userclasses.Prefs;
import userclasses.dataobjs.InfoHolder;
import static userclasses.dataobjs.RatingsQuestion.thisclass;
import userclasses.silverputtyone.Tools;

/**
 *
 * @author user
 */
public class AutoCompleteTextFieldForPlaces extends AutoCompleteTextField  {
    
    public Tools t;
    public static MyListModel myListModel = new MyListModel();
    
    private FilterProxyListModel<String> filter;
    //@Override
    public AutoCompleteTextFieldForPlaces(String[] completion) {
        super(new DefaultListModel<String>(completion));
        
        InfoHolder.googleDirectionsPlacePredictions = new Vector(); //clear 
        
        myListModel.addDataChangedListener(new DataChangedListener(){

            public void dataChanged(int type, int index) {
                _("data changed.");
            }
        });
    }

    
    /**
     * Subclasses can override this method to perform more elaborate filter operations
     * @param text the text to filter
     * @return true if the filter has changed the list, false if it hasn't or is working asynchronously
     */
    boolean toogle;
    @Override
    public boolean filter(String text) 
    {
        _("filter received: "+text);
        _("has the list changed?");
        InfoHolder.googleDirectionsPlacePredictions = new Vector();
        if (t==null)
        {
            _("false t not ready");
            return false;
        }
       
        if (InfoHolder.googleDirectionsPlacePredictions==null)
        {
            InfoHolder.googleDirectionsPlacePredictions=new Vector();//so its empty but ready????
            _("false InfoHolder.googleDirectionsPlacePredictions not ready");
            return false;
        }
        if (text.trim().length()<=1)
        {
            _("too short");
            return false;
        }
        
       
       /* toogle=!toogle;
        if (toogle)//SO ITS NOT IN A LOOP THINK CAREFULLY ABOUT THIS LATER
        {
           _("toggle was false");
           return false;
        }*/
        
        //THIS POPULATES THE 
     t.autocomplete( text,NetworkParserSpecifics.NETWORK_DECODE_GOOGLE_PLACE_AUTO_COMPLETE,false);
         _("yes it has changed");
     //    updateFilterList();//////////GAZ
        return true;
        /*if(filter != null) {
            filter.filter(text);        
            return true;
        }
        return false;*/
        //return false;
     /*   if(filter != null) {
            filter.filter(text);     
            _("true");
            return true;
        }
        _("false");
        return false;*/
    }
    
    @Override
      public ListModel<String> getSuggestionModel() {
       // String[] s = new String[]{"arse","bandits"};
        _("/////////////////////////////////////////////getSuggestionModel");
        return myListModel;
      /* DefaultListModel dlm = null;
        
         String[] s;
        if (InfoHolder.googleDirectionsPlacePredictions==null)
        {
            _("no predictions yet");
            s = new String[] {""};
            
        }
        else
        {
            s = new String[InfoHolder.googleDirectionsPlacePredictions.size()];
            for (int i=0; i<InfoHolder.googleDirectionsPlacePredictions.size(); i++)
            {
                s[i]=(String)InfoHolder.googleDirectionsPlacePredictions.elementAt(i);
                if (dlm==null)
                {
                    dlm = new DefaultListModel<String>(s);
                }
                _("adding item to DLM");
                dlm.addItem(s[i]);//TO FIRE NOTIFY
            }
            _("will return "+s.length+" predictions");
            //repaint();
        }
        
         if (dlm==null)
         {
             return   new DefaultListModel<String>(s);
         }
        
        
        return dlm;//filter;*/
    }
    
   
    
      //a simple wrapper around system.out for ease of typing
    public static final String thisclass="AutoCompleteTextFieldForPlaces";
    public final void _(String s)
    {
        if (Prefs.DEBUG)
            System.out.println("####"+thisclass+":"+s);
        
        //if (LOG_TO_SD_CARD)
        //{
        //    log("####"+thisclass+":"+s);
        //}
        
    }
}




LIST MODEL:



package userclasses.silverputtyone.components;

import com.codename1.ui.events.DataChangedListener;
import com.codename1.ui.events.SelectionListener;
import com.codename1.ui.list.DefaultListModel;
import com.codename1.ui.list.ListModel;
import com.codename1.ui.util.EventDispatcher;
import userclasses.Prefs;
import userclasses.dataobjs.InfoHolder;
import static userclasses.silverputtyone.components.AutoCompleteTextFieldForPlaces.thisclass;

/**
 *
 * @author user
 */
public class MyListModel extends DefaultListModel implements ListModel {
    
    private EventDispatcher dataListener = new EventDispatcher();
    private EventDispatcher selectionListener = new EventDispatcher();
        
    
    @Override 
    public Object getItemAt(int index)
    {
        return InfoHolder.googleDirectionsPlacePredictions.get(index);
    }
   
    @Override 
    public int getSize()
    {
        return InfoHolder.googleDirectionsPlacePredictions.size();
    }
        
    @Override     
    public void addItem(Object item){
        _("add item ");
        InfoHolder.googleDirectionsPlacePredictions.addElement(item);
        fireDataChangedEvent(DataChangedListener.ADDED, InfoHolder.googleDirectionsPlacePredictions.size());
    }
    
    private void fireDataChangedEvent(final int status, final int index){
        _("fireDataChangedEvent");
        dataListener.fireDataChangeEvent(index, status);
    }
    @Override     
    public void removeItem(int index){
        _("fireDataChangedEvent");
        if(index < getSize() && index >= 0){
            InfoHolder.googleDirectionsPlacePredictions.removeElementAt(index);
            if(index != 0){
                setSelectedIndex(index - 1);
            }
            fireDataChangedEvent(DataChangedListener.REMOVED, index);
        }
    }
    @Override
     public void addSelectionListener(SelectionListener l) {
        selectionListener.addListener(l);
    }

    /**
     * @inheritDoc
     */
    @Override
    public void removeSelectionListener(SelectionListener l) {
        selectionListener.removeListener(l);
    }
    
    @Override
     public void addDataChangedListener(DataChangedListener l) {
        dataListener.addListener(l);
    }

    /**
     * @inheritDoc
     */
    @Override
    public void removeDataChangedListener(DataChangedListener l) {
        dataListener.removeListener(l);
    }
    
    
     //a simple wrapper around system.out for ease of typing
    public static final String thisclass="MyListModel";
    public final void _(String s)
    {
        if (Prefs.DEBUG)
            System.out.println("####"+thisclass+":"+s);
        
        //if (LOG_TO_SD_CARD)
        //{
        //    log("####"+thisclass+":"+s);
        //}
        
    }







NETWORKING FROM t.autocomplete:



  ////GOOGLE MAPS AUTO COMPLETE/////////////
    
    
    ///GET PLACES FOR AUTO COMPLETE
    private static final String LOG_TAG = "ExampleApp";

    private static final String PLACES_API_BASE = "https://maps.googleapis.com/maps/api/place";

    private static final String TYPE_AUTOCOMPLETE = "/autocomplete";
    private static final String TYPE_DETAILS = "/details";
    private static final String TYPE_SEARCH = "/search";

    private static final String OUT_JSON = "/json";

    // KEY!
   // private static final String API_KEY = "YOUR KEY";

    public ArrayList<String> autocomplete(String input, final int networkState, final boolean expectHTML) {
        _("autocomplete received {"+input+"}");
        ArrayList<String> resultList = null;

         StringBuilder sb = new StringBuilder(PLACES_API_BASE);
            sb.append(TYPE_AUTOCOMPLETE);
            sb.append(OUT_JSON);
            sb.append("?sensor=false");
            sb.append("&key=" + Prefs.GOOGLE_MAPS_KEY);
            sb.append("&input=" + input);//URLEncoder.encode(input, "utf8"));
        String url = sb.toString();
        ////////////
        ConnectionRequest r = new ConnectionRequest() 
        {
            @Override
            protected void postResponse()
            {
                __("postResponse");
            }
            
            @Override
            protected void readResponse(InputStream input) throws IOException
            {
                __("readResponse");
                nps.parse(networkState,input,expectHTML);
                
                
               
           }
               
        };
        __(""+url);
        r.setUrl(url);
        r.setPost(false);
                
        
        //THIS STOPS SUBMITTING RATINGS WORKING..
       // InfiniteProgress prog = new InfiniteProgress();
       // Dialog dlg = prog.showInifiniteBlocking();
       // r.setDisposeOnCompletion(dlg);
        
        //here we can add any specific arguments to the url
        //nps.addSpecificArguments(networkState,r);
        
        
        //_("Added to queue, now wait for response.");
        NetworkManager.getInstance().addToQueueAndWait(r);
        ///////////////
     
        return resultList;
    }






AND FINALLY THE PARSER :-) Enjoy. 




  case NETWORK_DECODE_GOOGLE_PLACE_AUTO_COMPLETE:
                       _("NETWORK_DECODE_GOOGLE_PLACE_AUTO_COMPLETE");
                       
                       //decode a list of predictiosn from google like:
                       
                       
                        responsex = (String)h.get("status");
                       _("responsex:"+responsex);
                       if(responsex.equals("REQUEST_DENIED")){
                          System.out.println("make sure to obtain a key from "
                                + "https://developers.google.com/maps/documentation/places/");
                        //progress.dispose();
                        Dialog.show("Info", "make sure to obtain an application key from "
                                + "google places api's"
                                , "Ok", null);
                        return;
                    }
                    
                    
                     response = new String[h.size()];
                    counter=0;

                    
                    
                    InfoHolder.googleDirectionsPlacePredictions = new Vector();
                    
                    
                    Vector vecc = (Vector) h.get("predictions");
                    if (vecc!=null)
                    {
                        _("vecc size is "+vecc.size());
                    }

                    for (int i = 0; i < vecc.size(); i++) {
                        Hashtable entry = (Hashtable) vecc.elementAt(i);
                        //Hashtable geo = (Hashtable) entry.get("geometry");
                       // Hashtable loc = (Hashtable) geo.get("location");
                        //Double lat = (Double) loc.get("lat");
                        //Double lng = (Double) loc.get("lng");
                        //_("lat:"+lat);
                        //_("lng:"+lng);
                        //PointLayer point = new PointLayer(new Coord(lat.doubleValue(), lng.doubleValue()),
                        //        (String) entry.get("name"), null);
                       // pl.addPoint(point);
                        
                        //GAZ ADD LINES TOO
                       // LinesLayer line = new LinesLayer(new Coord(lat.doubleValue(), lng.doubleValue()));
                        //
                        
                        String prediction = (String) entry.get("description");
                       _("prediction:"+prediction);
                      // InfoHolder.googleDirectionsPlacePredictions.add(prediction);
                       
                       AutoCompleteTextFieldForPlaces.myListModel.addItem(prediction);
                    }
                    
                    
/*
                    v = (Vector)e.nextElement();
                    _("Got Vector of size "+v.size());
                    ee = v.elements(); // get its array
                    while (ee.hasMoreElements()) 
                    {
                        Hashtable ht = (Hashtable) ee.nextElement(); 

                        String store_code           = (String) ht.get("store_code");
                      

                       // InfoHolder.googleDirectionsRoute.add(station);
                    }
                    */
                       break;

Gareth Murfin

unread,
Nov 21, 2013, 3:23:38 PM11/21/13
to codenameone...@googlegroups.com
Sorry some of that code is commented out i should have removed it, however if you paste it into your IDE you can clearly see which bits to remove :)

Gareth Murfin

unread,
Nov 21, 2013, 3:44:41 PM11/21/13
to codenameone...@googlegroups.com
One thing, the foreground colour of the autocomplete popup doesnt seem to change, so my text is always black even though im specifying for it to be white in the gui builder. Possibly a bug?

Shai Almog

unread,
Nov 22, 2013, 2:27:59 AM11/22/13
to codenameone...@googlegroups.com
The auto complete uses a renderer so should be styled based on the ListRenderer style unless you change the renderer.
Reply all
Reply to author
Forward
0 new messages