Wednesday, April 17, 2013

Creating QML ListView with Search support

In last post I posted initial implementation for my Audiobook Reader app for Ubuntu-Touch, I was able to add some more features to it then after. I added support for Adding and removing custom bookmark and play the custom bookmark.

I also added support for searching the books by title in book list view. Similar to email application in Nokia N9. In this post I will show how similar feature can be implemented in QML application.

In my implementation if you pull down the book list, then it will show the search box. You can type in that search box and it will try to show books that matches that typed text. If you don't type for some time, search box will gets disappear.

Here is demo,



Following is my code. First I created TextField where user can type search text. If user type some text then I am applying the filter to my list's data model using typed text and also resetting the timer, which is responsible in hiding the search field.
    ....
    TextField{
        id: searchField
        width: parent.width
        visible: false

        onTextChanged: {
            timer.restart();
            if(text.length > 0 ) {
                model.applyFilter(text);
            } else {
                model.reload();
            }
        }

        onVisibleChanged: {
            if( visible) focus = true
        }

        Behavior on visible {
            NumberAnimation{ duration: 200 }
        }
    }
Following is my list view, Here I am setting its y property based on visibility of search field. Data model implements method for showing filtered data or all the data. Other important function is onContentYChanged, this function makes search field visible if user pull down the book list view.

    ListView {
        id:listView
        clip: true
        width: parent.width
        height: parent.height
        y: searchField.visible ? searchField.height : 0

        Behavior on y {
            NumberAnimation{ duration: 200 }
        }

        model: ListModel {
            id: model
            Component.onCompleted: {
                reload();
            }

            function reload() {
                var bookList = DB.getAllBooks();
                model.clear();
                for( var i=0; i < bookList.length ; ++i ) {
                    model.append(bookList[i]);
                }
            }

            function applyFilter(bookName) {
                var bookList = DB.getBooksByName(bookName);
                model.clear();
                for( var i=0; i < bookList.length ; ++i ) {
                    model.append(bookList[i]);
                }
            }
        }

        delegate: listDelegate

        onContentYChanged: {
            if( contentY < -100 ) {
                searchField.visible = true;
                timer.running = true;
            }
        }
    }

Lastly the timer, which is responsible for hiding the search field if user don't type anything for certain duration.
    Timer{
        id: timer; running: false; interval: 7000; repeat: false
        onTriggered: {
            searchField.visible = false;
        }
    }
That's all needed to add search support to QML ListView.

15 comments:

  1. Hi,

    Nice article, and nice blog, it's helped me a lot with QML.
    Just one question, do you know if this could be done with a WebView ?

    Eg, to find text on a page ?

    Regards.

    ReplyDelete
    Replies
    1. You mean searching the text and putting search box with WebView ? I think same UI logic can be used with WebView. For searching text should also be easy once you get hold of its html content.

      Delete
  2. Cool article, the getBooksByName function is looking for the passed string in every entry right ...

    ReplyDelete
    Replies
    1. Could you give the implementation of getBooksByName and getAllBooks ?

      Delete
  3. Can this method use on listmodel ?.Can you please provide some example.I can't see how the code works cause your listview get data from database while my app get data from listmodel and foldermodel.

    ReplyDelete
  4. how to work on list item component
    can u please tell me how to implement on json data

    ReplyDelete
  5. this is my JSON data
    {
    "atm_locations": [
    {
    "id": 1234,
    "name": "HDFC bank ATM",
    "address": "Gachbowli, Hyderabad",
    "latitude": "17.434656800000000000",
    "longitude": "78.363600700000000000"
    },
    {
    "id": 5821,
    "name": "ICICI Bank ATM",
    "address": "Hitech City, Hyderabad",
    "latitude": "17.434656800000000000",
    "longitude": "78.363600700000000000"
    },
    {
    "id": 6325,
    "name": "SBI Bank ATM",
    "address": "Kondapur, Hyderabad",
    "latitude": "17.434656800000000000",
    "longitude": "78.363600700000000000"
    },
    {
    "id": 2458,
    "name": "HDFC bank ATM",
    "address": "DLF, Gachibowli, Hyderabad",
    "latitude": "17.434656800000000000",
    "longitude": "78.363600700000000000"
    }
    ]
    }

    ReplyDelete
  6. my qml page
    Container {
    Container {
    preferredHeight: 650.0
    ListView {
    id: locationlistview
    dataModel: locationModel
    listItemComponents: [
    ListItemComponent {
    type: ""
    Container {
    horizontalAlignment: HorizontalAlignment.Fill
    verticalAlignment: VerticalAlignment.Fill
    Container {
    layout: StackLayout {

    }
    horizontalAlignment: HorizontalAlignment.Fill
    verticalAlignment: VerticalAlignment.Fill
    translationY: 20.0
    Label {
    text: ListItemData.name
    horizontalAlignment: HorizontalAlignment.Fill
    verticalAlignment: VerticalAlignment.Fill
    textStyle.color: Color.create("#f74848")
    translationX: 20.0
    textStyle.fontSize: FontSize.Small

    }
    Label {
    text: ListItemData.address
    horizontalAlignment: HorizontalAlignment.Fill
    verticalAlignment: VerticalAlignment.Fill
    textStyle.color: Color.create("#727272")
    textStyle.fontSize: FontSize.XXSmall
    translationX: 20.0
    translationY: -20.0

    }
    Divider {
    translationY: -20.0
    }

    }

    }
    }

    ]
    onTriggered: {
    locationlistview.clearSelection();
    locationlistview.toggleSelection(indexPath);
    console.log("the indexpath===" + indexPath)

    }
    attachedObjects: [
    // Definition of the second Page, used to dynamically create the Page above.

    ArrayDataModel {
    id: locationModel
    },
    DataSource {
    id: dataSource
    source: "asset:///model/atmLocations.json"
    onDataLoaded: {
    for (var i = 0; i < data.atm_locations.length; i ++) {
    locationModel .append(data.atm_locations[i]);}
    }
    }
    ]
    }
    }
    TextField {
    id: search
    preferredWidth: 650.0
    horizontalAlignment: HorizontalAlignment.Center
    hintText: "enter bank name"
    onTextChanged: {
    //todo search techinque

    }
    }
    onCreationCompleted: {
    dataSource.load();
    }
    }

    now i have to search in array please give solution

    ReplyDelete
  7. same designing can u tell me in html5

    ReplyDelete
  8. am doing a audio player for ubuntu desktop and want put searchbox over playlist but am not using listmodel am using foldermodel to get list of Audio files help me implimenting it . i tried to do it but i dont know whats wrong happening with it my project is herre http://launchpad.net/kmusicplay thanks

    ReplyDelete
    Replies
    1. Hi, I checked your code, I did not seen code for search box or filtering. But I have following suggestion. You can use code similar to this post, in applyFilter function of Model, you will need to manipulate the filter applied to FolderListModel (nameFilters: [ "*.mp3" ]), here in case of search, you need to make filter to show file and folder names matching to searched text.

      Delete
    2. This comment has been removed by the author.

      Delete
  9. can you implement the same search function having FolderModelList as listview model ???

    ReplyDelete
  10. import QtQuick 2.3
    import QtQuick.Controls 1.2
    import Qt.labs.folderlistmodel 2.1

    Item {
    width: 300
    height: 300

    FolderListModel
    {
    id: folderListModel
    }

    function updateFilter()
    {
    var text = filterField.text
    var filter = "*"
    for(var i = 0; i<text.length; i++)
    if(!caseSensitiveCheckbox.checked)
    filter+= "[%1%2]".arg(text[i].toUpperCase()).arg(text[i].toLowerCase())
    else
    filter+= text[i]
    filter+="*"
    print(filter)
    folderListModel.nameFilters = [filter]
    }

    Row
    {
    spacing: 5
    Text {text:"Filter"}
    TextField
    {
    id: filterField
    onTextChanged: updateFilter()
    }

    Text {text:"Case Sensitive"}
    CheckBox
    {
    id: caseSensitiveCheckbox
    checked: false
    onCheckedChanged:updateFilter()
    }
    }

    ListView
    {
    anchors.fill: parent
    anchors.topMargin: 30
    model:folderListModel
    delegate: Text{text: model.fileName}
    }

    }

    ReplyDelete