QML conditional Binding not working as expected - qt

If I have a simple Binding object of the form:
Rectangle {
height: 400
width: 500
property var someObj: null
Binding on color {
when: someObj
value: someObj.color
}
}
Then I would expect that when someObj is not null, someObj's color property is bound to this object's color property. What I actually get is a runtime error message:
TypeError: Cannot read property 'color' of null
Any reason why this doesn't work?
Doing the almost equivalent JavaScript expression:
color: {
if ( someObj != null ) {
return someObj.color;
} else {
return "black";
}
}
Works as expected.

As mentioned in the comment by BaCaRoZzo, the minimal example given in the question does not work because it gives a ReferenceError: someObj is not defined. However, after fixing this with an id for the Rectangle, then the example actually works despite the TypeError:
Rectangle {
id: rect
height: 400
width: 500
property var someObj: null
Binding on color {
when: rect.someObj
value: rect.someObj.color
}
}
This correctly sets the color as expected when rect.someObj is set and contains a color property.
The reason for the TypeError is that the expression rect.someObj.color is evaluated already when the Binding is created (see QTBUG-22005).
So to prevent the TypeError, one can simply check for rect.someObj to be set in the value expression of the Binding:
Binding on color {
when: rect.someObj
value: rect.someObj ? rect.someObj.color : undefined
}

I would do it in the following way:
import QtQuick 2.0
import QtQuick.Controls 1.4
Rectangle {
height: 400
width: 500
property var someObj
color: someObj ? someObj.color : "black"
Button {
id: buttonTest
text: "test"
onClicked: parent.someObj = test
}
Button {
id: buttonTest2
anchors.left: buttonTest.right
text: "test2"
onClicked: parent.someObj = test2
}
QtObject {
id: test
property color color: "red"
}
QtObject {
id: test2
property color color: "blue"
}
}
If someObj is undefined the color of the rectangle is black, if someObj is defined, the Value of the color property is chosen.
Edit: I've seen to late, that that's only what mlvljr suggested in the comments, sorry.

The QML syntax defines that curly braces on the right-hand-side of a property value initialization assignment denote a binding assignment. This can be confusing when initializing a var property, as empty curly braces in JavaScript can denote either an expression block or an empty object declaration. If you wish to initialize a var property to an empty object value, you should wrap the curly braces in parentheses.
For example:
Item {
property var first: {} // nothing = undefined
property var second: {{}} // empty expression block = undefined
property var third: ({}) // empty object
}
In the previous example, the first property is bound to an empty expression, whose result is undefined. The second property is bound to an expression which contains a single, empty expression block ("{}"), which similarly has an undefined result. The third property is bound to an expression which is evaluated as an empty object declaration, and thus the property will be initialized with that empty object value.
Similarly, a colon in JavaScript can be either an object property value assignment, or a code label. Thus, initializing a var property with an object declaration can also require parentheses:
Item {
property var first: { example: 'true' } // example is interpreted as a label
property var second: ({ example: 'true' }) // example is interpreted as a property
property var third: { 'example': 'true' } // example is interpreted as a property
Component.onCompleted: {
console.log(first.example) // prints 'undefined', as "first" was assigned a string
console.log(second.example) // prints 'true'
console.log(third.example) // prints 'true'
}
}
So the code should be as follow:
Rectangle {
height: 400
width: 500
property var someObj: ({color: ''})
Binding on color {
when: someObj.color
value: someObj.color
}
}

Related

Computed Value not undefined

i want to filter out the prop in computed, the prop value is available, but computed always shows undefined. Following is my code :
export default {
name: "Validation",
props: {
validationResult: {
type: Object,
required: true,
},
},
computed: {
filteredInvalidRules() {
return this.validationResult.sss.rules.filter((rule) => rule.isValid === false);
},
},
'Validation Results' is aavailable , and has value in dev Tools.
The computed property 'filteredInvalidRules' is always undefined. How to fix it?
TIA
I guess it's undefined because of the component creating steps.
I think it goes something like this (please correct me if I'm wrong):
the child component is created.
your computed property looks after validationResult.
validationResult is required, but has no value yet.
filteredInvalidRules returns undefined
your parent component is created and you pass the data (and you see them in your devTools).
Solution 1: add default values to your props
props: {
validationResult: {
type: Object,
default() {
return {
sss: {
rules: {
isValid: false
}
}
};
},
required: true,
},
},
so when your child component is created, your computed property got access to the default property values.
Solution 2: return in computed property
filteredInvalidRules() {
const isUndefined = obj2?.sss?.rules === undefined
if (isUndefined) return []
return this.validationResult.sss.rules.filter((rule) => rule.isValid === false);
},
this approach will return an empty array on creating the child component. I think it's better to use default values, to keep the computed property clean and to prevent the isUndefinedcheck each time a value changes.
There could be more solutions to it.

Can I Replace SimpleRow with Image if no modelData avaliable

I have a ListView, where the currently displayed modelData changes as a button cycles through several department options. If one of these departments has no data, my delegate continues showing the previous list data until it reaches a new section of modelData with data.
What I want to do, is when the model is 'empty' (being undefined, which may happen when the key it's looking for is yet to be created in my Firebase Database, or no items are currently visible), text/image is shown instead; i.e, "Move along now, nothing to see here".
My model is drawn from JSON, an example is below. and my calendarUserItems is the root node of multiple children within my Firebase Database, the aim of my AppButton.groupCycle was too add a further direction to each child node, filtering the data by this to view and edit within the page.
A sample of my code is:
Page {
id: adminPage
property var departments: [1,2,3,4]
property int currGroupIndex: 0
AppButton {
id: groupCycle
text: "Viewing: " + departments[currGroupIndex]
onClicked: {
if (currGroupIndex == departments.length - 1)
currGroupIndex = 0;
else
currGroupIndex++;
}
}
ListView {
model: Object.keys(dataModel.calendarUserItems[departments[currGroupIndex]])
delegate: modelData.visible ? currentGroupList : emptyHol
Component {
id: emptyHol
AppText {
text: "nothing to see here move along now!"
}
}
Component {
id: currentGroupList
SimpleRow {
id: container
readonly property var calendarUserItem: dataModel.calendarUserItems[departments[currGroupIndex]][modelData] || {}
visible: container.calendarUserItem.status === "pending" ? true : false
// only pending items visible
// remaining code for simple row
}
}
}
}
an example of JSON within my dataModel.calendarUserItems is:
"groupName": [
{ "department1":
{ "1555111624727" : {
"creationDate" : 1555111624727,
"date" : "2019-03-15T12:00:00.000",
"name" : "Edward Lawrence",
"status": "pending"
},
//several of these entries within department1
},
},
{ "department2":
{ "1555111624727" : {
"creationDate" : 1555111624456,
"date" : "2019-05-1T12:00:00.000",
"name" : "Katie P",
"status": 1
},
//several of these entries within department2
},
}
//departments 3 & 4 as the same
]
If departments 2 and 3 have modelData, yet 1 and 4 do not, I want the text to display instead, and the ListView emptied, instead of showing the previous modelData.
I have tried playing with the image/text visibility but the issue lays more with clearing the modelData and I'm unsure where to begin?
Any help is greatly appreciated!
I have achieved the display by using the following as my delegate:
delegate: {
if (!(departments[currGroupIndex] in dataModel.calendarUserItems) ) {
return emptyHol;
}
var subgroups = Object.keys(dataModel.calendarUserItems[departments[currGroupIndex]]);
for (var i in subgroups) {
var subgroup = dataModel.calendarUserItems[departments[currGroupIndex]][subgroups[i]];
modelArr.push(subgroup);
}
var modelObect = modelArr.find( function(obj) { return obj.status === "pending"; } );
if (modelObect === undefined) {
return emptyHol;
}
return currentGroupList;
}
Then when my AppButton.groupCycle is pressed, I have added modelArr = [] to clear the array on each press, this works as intended.
Thanks!

How to add an item to an array qml?

I have:
MyItem.qml
import QtQuick 2.0
Item {
property string info1: "info"
property int info2: 1
}
How to add an item to an array qml during initialization?
It's work:
import QtQuick 2.0
Item {
property var arr: [{ info1: "test", info2: 1}, { info1: "info" }]
}
But, it's not work:
import QtQuick 2.0
Item {
property var arr: [MyItem { info1: "test", info2: 1}, MyItem { info1: "info" }]
}
If you use a newer QML version you can use the new list<Item>-type for properties. There you can easily add Items in the syntax you want - just like when you were using a property alias someProperty: someItem.somePropertyList (e.g. children) for example.
include QtQuick 2.10 // I think 2.9 is sufficient
ApplicationWindow {
...
property list<Item> myItemList: [
Item { Component.onCreated: console.log(parent) }, // parent will be null! Set it explicitly if needed.
Item {},
Item {},
...
]
}
http://doc.qt.io/qt-5/qml-list.html
Small side note from the documentation:
Note: The list type is not recommended as a type for custom properties. The var type should be used instead for this purpose as lists stored by the var type can be manipulated with greater flexibility from within QML.
IMHO, you can ignore that as long as you don't need the "greater flexibility"
In older versions this is not possible but you could hack your way around, if you have a type "ArrayObject.qml"
import QtQuick 2.0
QtObject {
id: root
default property QtObject store
readonly property var arr: (arr === undefined ? [] : arr) // This will give you a binding loop, but ensures that after filling the array, it is not reset to [].
onStoreChanged: {
if (store) {
console.log(store)
arr[arr.length] = store
console.log(arr, arr.length)
}
store = null
}
}
You can then use it:
ArrayObject {
id: tst
Item {
}
Item {
}
Item {
}
}
Of course, there might be other workarounds.
E.g:
Item {
property var myArray: [
itemId1, itemId2, itemId3, itemId4
]
Item { id: itemId1 }
...
}
You can try to use Dynamic QML Object Creation:
import QtQuick 2.0
Item {
property var arr: {
component = Qt.createComponent("MyItem.qml");
item1 = component.createObject(null, {"info1": "test", "info2": 1});
item2 = component.createObject(null, {"info1": "info"});
return [item1, item2];
}
}
See Qt.createComponent and createObject for more information.

Read/Callback multiple firebase values from query in Qt

I'm trying to read all children of part of my database from one command, so I can update Firebase and it will automatically display in my app as the various titles.
the part of my database that I want to read is as follows:
public
bigqueryobject
title1
title2
title3
title4
I am working in Qt and have tried different combinations using orderByKey, orderByChild and orderByValue with the following code:
firebaseDb.getValue("public/bigqueryobject",{
orderByKeys: true
}, function(success, key, value) {
if(success) {
console.debug("Read user value for key", key, "fromFBDB: ", value);
myArray.push(value); combobox.model = myArray
}
})
when doing the above my log states:
"Read user value for key bigqueryobject fromFBDB: [object Object]
Read Value [object Object] for keybigqueryobject"
yet no responses are displayed, what could be the issue here?!?
So after previously trying to push the read value to an array to add to my combobox I was only getting one dropdown option with all read values in one row; simply removing the array worked perfectly, code below!
onFirebaseReady: {
firebaseDb.getValue("locationsDepartments/locations", {
orderByValue: true
}, function(success, key, value) {
if(success) {
combobox.model = value
}
})
}
Quick2.ComboBox {
id: combobox
model: []
delegate: Quick2.ItemDelegate {
width: combobox.width
height: combobox.height
contentItem: AppText {
text: modelData
}
highlighted: combobox.highlightedIndex == index
}
contentItem: AppText {
width: combobox.width - combobox.indicator.width - combobox2.spacing
text: combobox.displayText
wrapMode: Text.NoWrap
}
}

What is the correct way to check url type value?

I've a simple component like this one:
SimpleComponent.qml
Image {
id: root
property url selectedImage: ""
property bool selected: false
states: State {
name: 'selected'
when: selectedImage !== "" && selected
PropertyChanges { target: root; source: selectedImage; }
}
}
If I try to do something like the following, the image source will be replaced by selectedImage even if the condition should not be true.
SimpleImplementation.qml
Item {
id: root
SimpleComponent {
id: simpleSwitchImage
source: "/path/to/image.png"
selected: true
}
}
Attaching the following to Component.onCompleted I've got the commented results:
console.log(
selectedImage, // empty string
selectedImage === "", // false
selectedImage === undefined, // false
selectedImage === null, // false
selectedImage === Qt.resolvedUrl(""), // false
selectedImage.toString(), // empty string
selectedImage.toString() === "", // true
selectedImage.isEmpty, // undefined
selectedImage.empty // undefined
)
According to the documentation the only true I've got is the absolute path to the resource, is this the correct way to do such a simple check for an empty property of type url?
In the SimpleComponent.qml example code you are using strict inequality comparison between url QML type and zero length JavaScript string which is wrong:
when: selectedImage !== "" && selected
You could use url QML type's toString() method to get the url contents as a string
when: selectedImage.toString() !== "" && selected
or use JavaScript string's length property for checking:
when: selectedImage.toString().length>0 && selected
More specific answer:
I don't think there is any better way to check the "emptyness" of the url QML type.

Resources