11

I have a ListView that displays some data from an API. Within my list item, I need to have two different component trees depending on the data for that row. More specifically, if the row has an associated image, I need to display the image with a label, arranged a certain way. If it does not have an image, then I want to display only a label, arranged a different way. To me, that sounds like I want to create two different components and choose which component to include, dynamically.

It currently looks something like this, in abbreviated form:

ListItem.Empty {
    id: matchItem
    property string team1Name
    property string team2Name
    property string team1Logo
    property string team2Logo

    width: parent.width

    Item {
        id: team1Info
        width: parent.width*0.3

        anchors {
            left: parent.left
            top: parent.top
            bottom: parent.bottom
        }

        Item {
            anchors.fill: parent
            anchors.margins {
                top: units.gu(2)
                bottom: units.gu(2)
            }

            Image {
                id: team1LogoImage
                source: team1Logo
                width: parent.width
                height: units.gu(5)
                fillMode: Image.PreserveAspectFit
                anchors.horizontalAlignment: parent.horizontalCenter
            }

            Label {
                text: team1Name
                anchors.horizontalAlignment: Text.Center
            }
        }
    }

    // Some more elements and a repeat of the above for the second team
}

The issue is that if team1Logo or team2Logo is not a valid URL, such as if a team doesn't have a logo, the Image component will fail.

What I would like to do is essentially:

if (team1Logo === "") {
    Label {
        // Stuff to make it look good without an image
    }
} else {
    Image {
        source: team1Logo
    }

    Label {
        // Stuff
    }
}

But as far as I know, that's not how QML works.

I've taken a look at the Loader component, which seems like it might fit the bill, since I could use conditionals when setting the source property on the loader, but I couldn't get it to work. Does anyone know how to achieve what I described?

Tommy Brunn
  • 2,520
  • 2
  • 29
  • 41
  • Do you have to consider only empty logos or also malformed ones? In the former case you can use a conditional operator over `logo1 === ""` to load inside a loader one of two different components: one for label and one for label + image. In the latter case, I think you can exploit the loading error status for a malformed URL [status discussed here](http://doc.qt.io/qt-5/qml-qtquick-loader.html#status-prop) and again load different components for the specific case. – BaCaRoZzo Dec 30 '14 at 01:40
  • 1
    What if you detected if the team didn't have a valid logo and set an empty image URL instead of an invalid one? You could even point to a placeholder image that illustrates that there is no logo, eliminating the need for conditional UIs. – Mitch Dec 30 '14 at 08:11
  • @BaCaRoZzo I think I can ignore the case of malformed URLs for the time being. You're basically describing what I tried to do, but I couldn't get the loader to actually load the components. Do you have an example I could look at? – Tommy Brunn Dec 30 '14 at 08:55
  • No code right now, got only my smartphone. What about @Mitch idea? It sounds like a very nice idea to me! Anyhow, using a loader is pretty straightforward, look at this example [here](http://doc.qt.io/qt-5/qml-qtquick-loader.html#loader-sizing-behavior). It is anchored to the parent and the `sourceComponent` is set. That's it. In your case, the assignment of the component should be conditional according to the above condition. – BaCaRoZzo Dec 30 '14 at 09:50

2 Answers2

11

Turned out to be fairly straightforward to implement a Loader. Example:

Item {
    id: team1Info

    Loader {
        id: team1ItemLoader
        property string name: model.team1Name
        property string logo: model.team1Logo

        source: (logo) ? "TeamLogoItem.qml" : "TeamItem.qml"
    }
}

In this example, name and logo then become available inside TeamLogoItem.qml or TeamItem.qml as properties.

Tommy Brunn
  • 2,520
  • 2
  • 29
  • 41
7

The answer by @TommyBrunn only works if TeamItem.qml does not define any property you want to pass in. This means that:

  • You cannot use property alias in your component
  • You cannot supply any default value for such a property

Alternatively, you can use setSource() for a Loader to pass property values in to the loaded component:

// ### TeamItem.qml (and TeamLogoItem.qml, similarly)
Label {
  property alias name: text
  property string logo: "qrc:/images/logos/dummy.png"
}    
// ### main.qml
Item {
    id: team1Info

    Loader {
        Component.onCompleted: {
            var qml = model.team1Logo ? "TeamLogoItem.qml" : "TeamItem.qml";
            setSource( qml, { name:model.team1Name, logo:model.team1Logo } )
        }
    }
 }

You could also choose to pass different property values in—e.g. not pass in a logo to TeamItem.qml—based on the QML you are loading. They do not have to have the same interface.

Phrogz
  • 296,393
  • 112
  • 651
  • 745