[SOLVED] How to create delegates and ListModel dynamically in JavaScript?



  • Given I have the following QML, which creates a ListView, defines a simple Button delegate and a model:

    ListView {
        anchors.fill: parent
        snapMode: ListView.SnapToItem
        delegate: Component {
            Button {
                height: 32; width: parent.width;
                text: "Button index " + index + ", " + model.first + ", " + model.second; } }
        model: ListModel {
            ListElement { first: "first 1"; second: "second 1"; }
            ListElement { first: "first 2"; second: "second 2"; } } }
    

    [solved] How can I create the same purely (or mostly) in JavaScript (or C++)?

    I have a JavaScript function like this. However, it exhibits several problems, as annotated:

    function createListView (parent) {
     var listView = Qt.createQmlObject ('import QtQuick 2.3; import QtQuick.Controls 1.2; PathView {}', parent);
     var model = Qt.createQmlObject('import QtQuick 2.2; ListModel {}', parent);
    // Problem 1
     var delegate = Qt.createQmlObject ('import QtQuick 2.3; import QtQml 2.2; import QtQuick.Controls 1.2; Component { }', parent);
     var button = Qt.createQmlObject ('import QtQuick 2.3; import QtQuick.Controls 1.2; Button { }', delegate);
    // Problem 2
        listView.snapMode = ListView.SnapToItem;
        listView.anchors.fill = parent;  // This may fail, depending on what parent was given
        model.append ({"first": "first 1", "second": "second 1"});
        model.append ({"first": "first 2", "second": "second 2"});
        listView.model = model
        button.width = parent.width;
        button.height = 32;
    // Problem 3 and problem 4
        button.text = "Button index " + index + ", " + model.first + ", " + model.second;
    // Problem 5
        delegate.??? = button;
        return listView;
    }
    

    Problem 1: trying to create an empty Component fails with:
    Error: Qt.createQmlObject(): failed to create object:
    inline:1:68: Cannot create empty component specification
    I could solve this one by specifying for instance "Component { Item { id: item } }". Then I could add the delegate as child to item. However: how can I reference item within delegate from JavaScript then?

    Problem 2: how do I get access to enum values of ListView (and other components) from JavaScript?

    Problem 3: how to access index in JavaScript, which is available to delegates in QML?

    Problem 4: how to access model in javaScript, which is available to delegates in QML?

    Problem 5: how to populate a delegate Component with custom children?

    I'd be thankful for help on any of these problems. Alternatively, I'd also appreciate a C++ version of this JavaScript function createListView(), if any one has one and these problems are easier to solve in C++ than in JavaScript.


  • Moderators

    Hi @Quteroid,
    For the above scenario I would go with C++ models probably QAbstractItemModel. The model can have Q_INVOKABLE functions which can be accessed from QML directly for operations if required.



  • @p3c0
    Creating and maintaining the data model in C++ rather than JavaScript is something I will eventually do, indeed. However, this does not answer any of the particular problems I listed. Creating a data model in JavaScript is one of the aspects which actually have not been a big problem. Creating and handling the delegate in JavaScript is the main problem. Any clues on that?



  • @p3c0
    To clarify my motivation: I want to create delegates completely dynamically. Based upon both data model, user preferences and other factors. It may be all sorts like:

    • one simple Button (as in the given example),
    • one Text component
    • three Text components
    • one Rectangle, one Button, one Text
    • two Texts, one Image and one Button

    ...and all combinations thereof (and more). You see that providing static QML for all cases is not feasible for this.

    I know that one way would be to synthesize appropriate, complex QML snipplets that compose the requested delegate structures at run time and hand it to Qt.createQmlObject(). However, synthesizing 10 or 20 (or more) lines of QML in JavaScript (or even C++) seems pretty much ugly and inefficient to me and I'd prefer a more direct, API-oriented way, if possible. Is it possible?


  • Moderators

    @Quteroid
    Problem 1: The only way AFAIK is to add it statically.
    Problem 2: You can't access list of enum values it takes but only what has been assigned by directly querying property.
    Problem 3: Since it is attached property it is accessible only in delegate not outside of it.
    Problem 4: Since you have model object, access it using get() function.
    Problem 5: IMO Not possible to completely add it dynamically in JavaScript as you require.

    import QtQuick 2.4
    import QtQuick.Controls 1.3
    
    Rectangle {
        id: root
        width: 200
        height: 200
    
        function listViewC() {
            var listView = Qt.createQmlObject ('import QtQuick 2.4; ListView {anchors.fill: parent}', root);
            var model = Qt.createQmlObject('import QtQuick 2.4; ListModel {}', parent);
            var comp = Qt.createQmlObject('import QtQuick 2.4; Component { Text { text: "(" + index + ") " +name + " " + cost } }', parent); //Problem 1 same as you said, Problem 3: index only in delgate, Problem 4: Just the only way I know
            model.append({"cost": 15.95, "name":"Pizza"});
            model.append({"cost": 3.95, "name":"Doughnut"});
            listView.model = model;
            listView.delegate = comp;
    
            console.log(listView.model.get(1).name) //Problem 4: I hope :)
            console.log(listView.headerPositioning) //Problem 2
        }
    
        Button {
            anchors.bottom: parent.bottom
            text: "Click"
            onClicked: listViewC()
        }
    }
    

    There could be limitations as to what can be achieved in JavaScript and also it seems to be painful ;)



  • I seem to have found a way to dynamically create delegates for data models via JavaScript. It's not quite straightforward. The key to success is using a JavaScript callback within the Component definition. Like this:

    listView.delegate = Qt.createQmlObject (
            'import QtQuick 2.3; ' +
            'import "Init.js" as Init; ' + // Important to import even self!
            'Component { ' +
                'Item { ' + // Dummy item
                    'Component.onCompleted: Init.populateDelegate (this, model, index); }}', parent);
    

    Description:

    • the delegate property of a ListView (or PathView or TableView etc.) is assigned a Component that is created via Qt.createQmlObject()
    • the JavaScript file that contains the callback must be imported, too. Even if it is in the same file as the code above! Otherwise QtQuick will throw a ReferenceError. Here, the code is contained in file "Init.js".
    • since no empty Components can be created by Qt.createQmlObject, a dummy Item is added. This is actually a small waste of resources, but hopefully not too expensive.
    • Callback Init.populateDelegate() is assigned to the dummy Item. It is invoked once per instantiated record of the data model used.
    • this, model and index seem reasonable parameters for all sorts of delegates created this way. this is the dummy Item. It should be used as parent in the callback.
    • parent is the parent component in which this listView is to be created.

    The callback JavaScript function that constructs the delegate may then look like this:

    // In a file called "Init.js", as given in above's example
    function populateDelegate (parent, model, index) {
     var button= Qt.createQmlObject ('import QtQuick 2.3; import QtQuick.Controls 1.2; Button { }', parent);
        parent.height = 32;
        button.width = parent.width = 256;
        button.height = parent.height = 32;
        button.text = "Button index " + index + ", " + model.first + ", " + model.second;
        console.log ("Model: " + model + ", index: " + index + ", button: " + button);
    }
    

    Note that parent.width and parent.height must be set explicitly! They are the Item's dimensions and determine the dimension of one row / cell in the View. In this simple example they are identical to the only element (Button). Item's dimensions may differ if you either want padding (extra space) or overlapping between rows / cells. Generally they should be the sums of the widths (if you use Row or RowLayout) or heights (if you use Column or ColumnLayout) of all contained items.

    Also note that this approach allows you to use altering delegate constructs for each row. For instance, a single button in the first row, two Texts in the 2nd, an Image in the 3rd, and so on. For instance with code like this in the populateDelegate() function:

    var buildFn;
    switch (index % 3) {
        case 0: buildFn = createJustOneButton; break;
        case 1: buildFn = createTwoTexts; break;
        case 2: buildFn = createImageOrWhateverElse; break;
        default: { console.assert (false, "Impossible case!"); Qt.quit (); } }
    buildFn (parent, model, index); // Build delegate contents for one row
    

    or even much more compact and fancier, as a single statement:

    [createJustOneButton, createTwoTexts, createImageOrWhateverElse][index % 3] (parent, model, index);
    

    Quite a gain of flexibility; isn't it?


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.