Lists and Grids for Displaying Data in Sailfish OS

    Michele Tameni
    Share

    This article was peer reviewed by Marc Towler. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!


    Lists and Grid are one the fundamental building blocks of any mobile application.

    In this article I’m going to introduce the design of the SilicaListView and SilicaGridView, the Silica implementation of these common components. We will see some examples on how to display types of data, handle clicks and more, keeping the code as simple as possible.

    The MVD design

    Sailfish OS UIs are based on QML, a declarative language provided by the QT Framework. To give the developer or designer control over different aspects of an application, QT uses the Model-View-Delegate design pattern to modularize the visualization of data.

    The QML Design pattern

    The model holds the data and its structure, and the View is a container that displays the data. In our case the view will be a list or a grid. Finally the delegate dictates how the single piece of data will appear in the view.

    The SilicaListView

    Let’s start with a simple example:

    import QtQuick 2.0
    import Sailfish.Silica 1.0
    
    Page {
        id: page
    
        SilicaListView {
            width: parent.width;
            height: parent.height
            model: ListModel {
            ListElement { name: "Paul"; telephone: "342342341" }
            ListElement { name: "Laura"; telephone: "343241" }
            ListElement { name: "Luca"; telephone: "6454341" }
            ListElement { name: "Daniel"; telephone: "23431231" }
            ListElement { name: "Seb"; telephone: "666342342341" }
            ListElement { name: "Carl"; telephone: "55342342341" }
            }
    
            delegate: Item {
                width: parent.width
                height: Theme.itemSizeMedium
                Label {
                    text: name
                    font.pixelSize: Theme.fontSizeMedium
                    anchors {
                                left: parent.left
                                right: parent.right
                                margins: Theme.paddingLarge
    
                            }
                }
            }
        }
    
    }

    Apart from the page components (look at this article for an introduction to Silica Components), we declared a SilicaListView with an hard coded model. The list will appear like this:

    A basic list

    Each ListElement is an object with two properties – a name and a phone number.

    The delegate is just an Item (the basic QML component) with its ‘width’ property set to fill the ListView width and the ‘height’ set using the Theme object, the actual size is calculated at runtime, adapting it to the display in use.

    The delegate contains a Label displaying the contact name. To show the phone number I can add another Label to the delegate:

    Item {
                width: parent.width
                height: Theme.itemSizeMedium
                anchors {
                            left: parent.left
                            right: parent.right
                            margins: Theme.paddingLarge
    
                        }
    
                Label {
                    id: cName
                    text: name
                    font.pixelSize: Theme.fontSizeMedium
                    anchors {
                                left: parent.left
                                right: parent.right
                            }
                }
    
                Label {
                    text: "Tel: " + telephone
                    font.pixelSize: Theme.fontSizeSmall
                    anchors {
                                left: parent.left + Theme.paddingSmall
                                right: parent.right
                                top: cName.bottom
                            }
                }

    Showing also the phone Number

    Model and Delegate can be defined inline but as models get bigger, it’s better to move it in another QML file.

    For example, you can create a ContactsModel.qml file with this content:

    import QtQuick 2.0
    
    ListModel {
            ListElement { name: "Paul"; telephone: "342342341"; team: "IT" }
            ListElement { name: "Laura"; telephone: "343241"; team: "IT"}
            ListElement { name: "Luca"; telephone: "6454341"; team: "IT" }
            ListElement { name: "Daniel"; telephone: "23431231"; team: "Sales" }
            ListElement { name: "Seb"; telephone: "666342342341"; team: "Sales" }
            ListElement { name: "Carl"; telephone: "55342342341"; team: "Sales" }
            }

    And use it as a list model:

        model: ContactsModel {}

    I added the team property to use in the next example.

    A List sometimes needs to be divided into sections. Do this using the section property to define the model key used as grouping element and the delegate to use as a section header.

    Define the section property in the SilicaListView:

        section {
            property: "team"
            criteria: ViewSection.FullString
            delegate: SectionHeader {
                    text: section
                }
            }

    List with section

    Now it’s time to handle the click events and react to them.

                MouseArea {
                    anchors.fill: parent
                    onClicked: {
                        var name = list.model.get(index).name
                        console.log(name);
                    }

    We defined a MouseArea element filling our delegate. This way we can catch and handle the click event and get the clicked element using the list model get and index property.

    Every list can have a header and footer attached to the respective list property. These are useful as you shouldn’t include a SilicaListView or GridView in another Scrollable element. If you need content before or after the list/grid then use these properties.

    
       SilicaListView {
            id:list
            width: parent.width;
            height: parent.height
            model: ContactModel {}
            header: MyHeader {}
            footer: Button {
                width: parent.width
                anchors.margins: Theme.paddingSmall
                text: "Load more..."
                  onClicked: console.log("clicked!")
              }
    
            section {
                property: "team"
                criteria: ViewSection.FullString
                delegate: SectionHeader {
                        text: section
                    }
                }
    
            delegate: Item {
                width: parent.width
                height: Theme.itemSizeMedium
                anchors {
                            left: parent.left
                            right: parent.right
                            margins: Theme.paddingLarge
    
                        }
    
                Label {
                    id: cName
                    text: name
                    font.pixelSize: Theme.fontSizeMedium
                    anchors {
                                left: parent.left
                                right: parent.right
                            }
                }
    
                Label {
                    text: "Tel: " + telephone
                    font.pixelSize: Theme.fontSizeSmall
                    anchors {
                                left: parent.left
                                right: parent.right
                                top: cName.bottom
                            }
                }
    
                MouseArea {
                    anchors.fill: parent
                    onClicked: {
                        var name = list.model.get(index).name
                        console.log(name);
                    }
                }
            }
        }

    We can choose to declare objects inline or in a separate file. As an example I created a file called MyHeader.qml with this content and declared the footer inline:

    
    import QtQuick 2.0
    import Sailfish.Silica 1.0
    
    PageHeader {
            title: "Numbers"
    }

    Our list with footer and header

    Models

    To simplify a developer’s life QML define a set of ready to use models, such as SqliteModel, XmlListModel and SparqlModel. You can defined your own models using the C++ QT API.

    As an example, we can display the latest Sitepoint news using the site’s RSS feed. Let’s define our model using the predefined XmlListModel:

    
    XmlListModel {
         id: feedModel
         source: "https://www.sitepoint.com/feed/"
         query: "/rss/channel/item"
         XmlRole { name: "title"; query: "title/string()" }
         XmlRole { name: "link"; query: "link/string()" }
         XmlRole { name: "description"; query: "description/string()" }
         XmlRole { name: "pubDate"; query: "pubDate/string()" }
    }

    Here we define the source of the feed and the query property that set the scope for the model. Only <item> children will be used, and the three XmlRoles act as a property inside the ListElement.

    Now we can make our list view using this model, the complete example looks like this:

    
    import QtQuick 2.0
    import Sailfish.Silica 1.0
    import QtQuick.XmlListModel 2.0
    Page {
        id: page
    
    
        SilicaListView {
            id:list
            width: parent.width;
            height: parent.height
            model: XmlListModel {
                id: feedModel
                source: "https://www.sitepoint.com/feed/"
                query: "/rss/channel/item"
                XmlRole { name: "title"; query: "title/string()" }
                XmlRole { name: "link"; query: "link/string()" }
                XmlRole { name: "description"; query: "description/string()" }
                XmlRole { name: "pubDate"; query: "pubDate/string()" }
           }
    
            header: MyHeader {}
            footer: Button {
                width: parent.width
                anchors.margins: Theme.paddingSmall
                text: "Load more..."
                  onClicked: console.log("clicked!")
              }
    
            delegate: Item {
                width: parent.width
                height: Theme.itemSizeMedium
                anchors {
                            left: parent.left
                            right: parent.right
                            margins: Theme.paddingLarge
    
                        }
    
                Label {
                    id: cName
                    text: title
                    font.pixelSize: Theme.fontSizeSmall
                    anchors {
                                left: parent.left
                                right: parent.right
                            }
                }
    
                MouseArea {
                    anchors.fill: parent
                    onClicked: {
                        var name = list.model.get(index).name
                        console.log(title);
                    }
                }
            }
        }
    
    }

    XmlList Model

    We need to address situations where we have no items to display, for example when the feed is still loading.

    We can use the ViewPlaceholder element to handle this, binding its visibility to the count property of the list view:

    ViewPlaceholder {
    enabled: list.count == 0
    text: "Loading..."
    hintText: "Be patient my friend"
    }

    Empty list

    Models in Js

    If you need to generate a just in time model, you can use javascript. We obtain and display a list of numbers from 1 to 100 like this:

    import QtQuick 2.0
    import Sailfish.Silica 1.0
    
    
    Page {
        id: page
    
        SilicaListView {
            id:list
            width: parent.width;
            height: parent.height
            model: ListModel {
               id: myJSModel
    
            }
            header: MyHeader {}
            footer: Button {
                width: parent.width
                anchors.margins: Theme.paddingSmall
                text: "Load more..."
                  onClicked: console.log("clicked!")
              }
            delegate: Item {
    
                width: parent.width
                height: Theme.itemSizeMedium
                Label {
                    text: value
                    font.pixelSize: Theme.fontSizeMedium
                    anchors {
                                left: parent.left
                                right: parent.right
                                margins: Theme.paddingLarge
    
                            }
                }
    
                MouseArea {
                    anchors.fill: parent
                    onClicked: {
                        var value = list.model.get(index).value
                        console.log(value);
                    }
                }
            }
    
            Component.onCompleted: {
                for(var i=0;i<=100;i++) {
                    var myElement = { "value" : i }
                    myJSModel.append(element)
                }
            }
        }
    
    }

    The myElement could be an arbitrary javascript object and we can add as many properties as needed and use them in our delegate.

    We defined the JavaScript model in the Component.onCompleted event. By doing this our code is called when the associated components are ready and displayed.

    Gridviews

    The GridView acts in a similar way to the list view, sharing most of the properties.

    import QtQuick 2.0
    import Sailfish.Silica 1.0
    
    
    Page {
        id: page
    
        SilicaGridView {
            id:list
            width: parent.width;
            height: parent.height
    
            cellWidth: width / 2
            cellHeight: width / 2
    
            model: ListModel {
               id: myJSModel
    
            }
            header: MyHeader {}
            delegate: Item {
                width: list.cellWidth
                height: list.cellHeight
    
    
    
                Label {
                    text: value
                    font.pixelSize: Theme.fontSizeMedium
                    anchors {
                                left: parent.left
                                right: parent.right
                                margins: Theme.paddingLarge
    
                            }
                }
    
                MouseArea {
                    anchors.fill: parent
                    onClicked: {
                        var value = list.model.get(index).value
                        console.log(value);
                    }
                }
            }
    
            Component.onCompleted: {
                for(var i=0;i<=100;i++) {
                    var element = { "value" : i }
                    myJSModel.append(element)
                }
            }
        }
    
    }

    JS List

    JS Grid

    We have to define two more properties that the list view, cellWidth and cellHeight that are self explanatory.

    Conclusion

    Lists and grids are popular but not always easy to implement.

    Sailfish, thanks to QML, has one of the cleanest and most convenient APIs regarding these two crucial components, allowing a developer to achieve a usable UI without much effort.

    I’d love to hear your thoughts and comments and if you’ve tried any Sailfish development yet.

    CSS Master, 3rd Edition