4

I have a ListView with a list of JavaScript objects as a model. The delegate needs to respond to click and I want to store click handler attached to model item (action property):

ListView {
    id: idListView
    model: [
        {
            name: "Item 1",
            icon: "icon1.svg",
            action: function() {
                //do stuff
            }
        },
        /* other items */
    ]
    delegate: MyDelegate {
        name: modelData.name
        icon: modelData.icon

        MouseArea {
            anchors.fill: parent
            onClicked {
                modelData.action()
            }
        }
    }
}

But when I click on an item i get

TypeError: Property 'action' of object [object Object] is not a function

What's the proper way to attach function to object and call it?

3 Answers 3

5

You should define function as a QML property. Object doesn't allow that so you could use ListModel instead:

import QtQuick 2.11
import QtQuick.Window 2.11

Window {
    id: root
    visible: true
    width:480
    height: 640
    title: qsTr("Hello World")

    ListView {
        anchors.fill: parent
        spacing: 2
        model: ListModel {
            ListElement {
                name: "Item 1"
                property var func: function(){ console.log("Item 1 clicked"); }
            }
            ListElement {
                name: "Item 2"
                property var func: function(){ console.log("Item 2 clicked"); }
            }
        }

        delegate: Rectangle {
            height: 30
            color: "#EFEFEF"
            border { width: 1; color: "#CCC" }
            width: parent.width
            Text {
                text: name
                anchors.centerIn: parent
            }
            MouseArea {
                anchors.fill: parent
                onClicked: {
                    if(typeof func === "function")
                        func();
                    else
                        console.error("Click handler not defined");
                }
            }
        }
    }
}

Another but a bit tricked solution:

import QtQuick 2.11
import QtQuick.Window 2.11

Window {
    id: root
    visible: true
    width:480
    height: 640
    title: qsTr("Hello World")

    ListView {
        anchors.fill: parent
        spacing: 2
        property list<QtObject> arr: [
            QtObject {
                property string name: "Item 1"
                property var func: function(){ console.log("Item 1 clicked"); }
            },
            QtObject {
                property string name: "Item 2"
                property var func: function(){ console.log("Item 2 clicked"); }
            }
        ]
        model: arr

        delegate: Rectangle {
            height: 30
            color: "#EFEFEF"
            border { width: 1; color: "#CCC" }
            width: parent.width
            Text {
                text: modelData.name ? modelData.name : "Undefined item"
                anchors.centerIn: parent
            }
            MouseArea {
                anchors.fill: parent
                onClicked: {
                    if(typeof modelData.func === "function")
                        modelData.func();
                    else
                        console.error("Click handler not defined");
                }
            }
        }
    }
}
Sign up to request clarification or add additional context in comments.

6 Comments

Did you test the first snippet? I don't think functions can be used in ListElement.
Yes, tested in 5.11. Fist I've posted the second example, with QtObjects then I decided to try the same thing with ListElement and was surprised to see that it works. A ListElement can have only simple types but nothing said about property :-)
Direct use of ListElement doesn't worked for me, because it doesn't handle QmlEngine dynamic retranslate. Second options looks nice (and works), despite being more verbose - I was seeking a way to attach handler to model item.
It is indeed new in Qt 5.11: doc.qt.io/qt-5/qml-qtqml-models-listelement.html - "Beginning with Qt 5.11 ListElement also allows assigning a function declaration to a role. This allows the definition of ListElements with callable actions."
First variant still return error in 5.12: ListElement: cannot use script for property value. What correct syntax should be here? No sample in the doc.
|
4

Unfortunately it is not possible to store a function inside a ListElement:

Values must be simple constants; either strings (quoted and optionally within a call to QT_TR_NOOP), boolean values (true, false), numbers, or enumeration values (such as AlignText.AlignHCenter).

A simple way to call a function from a delegate is to keep the function outside of the model and reference its name in the model:

ListView {
    id: idListView

    readonly property var actions: {
        "action1": function() {
            console.log("called action 1!");
        },
        "action2": function() {
            console.log("called action 2!");
        }
    }

    model: [
        {
            name: "Item 1",
            icon: "icon1.svg",
            action: "action1"
        },
        {
            name: "Item 2",
            icon: "icon2.svg",
            action: "action2"
        },
        /* other items */
    ]
    delegate: MyDelegate {
        name: modelData.name
        icon: modelData.icon

        MouseArea {
            anchors.fill: parent
            onClicked: {
                if (typeof idListView.actions[modelData.action] === "function") {
                    idListView.actions[modelData.action]()
                }
            }
        }
    }
}

4 Comments

Thanks for suggestion! So, model items are converted to ListElements despite regardless of model type?
No, I don't think so. I think augre was referring to folibis' answer.
I am not sure. I used the doc of ListElement because I believe it is the same behavior as using an object for the model (with same possible values). But I am not really aware of how exactly the model is managed using an object VS ListModel. Anyway I didn’t think of using properties, if you don’t mind using a ListModel I find fobilis’ answer a good alternative since you don’t have to rely on the function names!
Thanks much! Above is the only workaround for QTBUG-80041.
1

I ran into a similar issue. I wanted to have a Drawer containing a ListView of Actions. Some try and error and finally got it running (Qt 5.11). May my code is of use for someone else.

Action {
    id: aboutAction
    text: "About"
    icon.source: "icon_info.png"
}

Drawer {
    edge: Qt.LeftEdge
    width: Screen.pixelDensity * 100  // mm
    height: parent.height

    ListView {
        id: listView
        anchors.fill: parent

        model: ListModel {}

        delegate: ItemDelegate {
            width: parent.width
            action: action_
        }
    }

    Component.onCompleted: {
        listView.model.append({action_: aboutAction});
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.