In QML, could a component property be the component itself? - javascript

I'm trying to assing a Component itself to one of its property, and then passing that property to a function inside an external file (import "Scripts.js" as Scripts)
The first thing that came to my mind was (in my opinion) the most obvious method:
//MyComponent.qml
Item {
id: comp
// property var target: comp
// doesn't work either
property var target: this
...
onActiveFocusChanged: {
// Scripts.foo(this)
// and Scripts.foo(tf)
// don't work either
if(this.activeFocus) {
Scripts.foo(target)
}
}
}
But that doesn't work (the window crashes after giving activeFocus to the Component).
So, I assigned undefined to the target as default:
...
property var target: undefined
...
and then assigned the Component itself when declared:
MyComponent {
id: myComponent
...
target: this
// target: myComponent
// also works
...
}
When the activeFocus is triggered, everything works fine. Can someone tell me why / what I'm doing wrong?
It's impossible to assing to a component property the component itself?
And why it's not impossible to do this after declared?

As folibis already commented you should use ids instead of this keyword. Have a look at the following SO post.
import QtQuick
Window {
id: root
width: 800
height: 600
visible: true
title: qsTr("Hello Component")
function foo(parameter) { console.log(parameter) }
component MyComponent : Item {
id: component
property var target: component
Component.onCompleted: {
root.foo(component.target)
root.foo(component)
}
}
MyComponent {}
MyComponent { target: root }
}
This works without an issue.
import QtQuick
Window {
id: root
width: 800
height: 600
visible: true
title: qsTr("Hello Component")
function foo(parameter) { console.log(parameter) }
component MyComponent : Item {
id: component
property var target: component
onActiveFocusChanged: {
if(component.activeFocus)
root.foo(component.target)
}
}
MyComponent { id: test }
Component.onCompleted: test.forceActiveFocus()
}

The this keyword has special meaning in Javascript and can change it's meaning depending on context. Since you declared it as target: this you made it property bind so that every change to this will trigger a new value in target. To work around that problem, you can ensure that you capture this exactly once with:
// MyComponent.qml
import QtQuick
import QtQuick.Controls
Item {
property Item target
Component.onCompleted: target = this
}
Alternatively, if you want to keep your code clean of imperative code, you can make use of parent in the following way:
// MyComponent.qml
import QtQuick
import QtQuick.Controls
Item {
readonly property alias target: internal.target
Item {
id: internal
property Item target: parent
}
}
In the above, internal is clearly a subitem of Item and therefore, there can only be one meaning for parent which is your original item itself.

Related

Problem with acces to function with react javascript child component

They would like to function from the component from the child component. This method call: TypeError: Pizza__WEBPACK_IMPORTED_MODULE_2_.default.valid is not a function.
I try to add static function but it will not get the value.
I can add code of pizza to orders, but this not I will.
Can anyone help?
I want to get dish_details from Pizza and Show Pizza form underneath.
In .js no .tsx
Parend class:
class Orders extends React.Component {
constructor(props) {
super(props);
this.order = {
name: "",
preparation_time: "00:00:00",
type: "",
}
}
kind(){
switch (this.order.type) {
case 'pizza':
return <Pizza/>;
}
}
submit(){
console.log(Pizza.dishDetails()); // return error
}
render() {
return (<div>
<div>{this.state.selected ? this.kind() : ""}</div>
<button className={styles.order_submit} onClick={this.submit.bind(this)}>Submit</button>
</div>
);
}
Kids class:
class Pizza extends React.Component{
constructor(props) {
super(props);
this.state = {
noOfSlices : 0,
diameter : 0
}
}
dishDetails(){
return this.state;
}
noOfSlices(e){
this.setState({noOfSlices : e.target.value});
}
If you want your components to have a state you need to declare it with
this.state.[the name of the variable]
That's how react knows that you want to store state inside a component. The error you get probably is because you declared the state of the pizza component wrongly
dish_details = { //Not correct
noOfSlices : 0,
diameter : 0
}
Here you declare it inside the constructor, and that is correct, but in order to work you need to use the component state.
constructor(props) { /
super(props);
this.state.order = {
name: "",
preparation_time: "00:00:00",
type: "",
}
}
Check out the docs on state.
You have several issues here
you say something is static, but you have not created a static function!
submit(){
console.log(Pizza.dishDetails()); // return error or undefined when static
}
This is a call to a static function. To create a static function you would do this:
// ES5
Pizza.dishDetails = function(){ /* do something that does not touch `this` */ }
or in modern ES2015+:
class Pizza {
static dishDetailsfunction(){
/* do something that does not touch `this` */
}
}
The dishDetailsfunction function is not static, but more importantly, it cannot be static, since it uses this. A static function has no reference to this - that's the definition of static.
So you need to reorganize a bit ...
You are not allowed to access the inner state of a component from an outer component, so you need to either do your data and external actions handling outside of your components (like Redux), use some kind of callback logic, or delegate the logic for handling submits down to Pizza.
Here is one way to do it using a callback:
In the order component
renderPizza() {
// the `this` in the callback references the Orders (parent) component
return <Pizza onChange={(pizzaOrder) => this.setState({order: pizzaOrder}) }/>} />
}
In the pizza component:
updateNoOfSlices(e){
this.setState({noOfSlices : e.target.value});
this.prop.onChange({this.state});
}
I removed all the logic that is not necessary for the point, but you should see how a callback solves this easily.
P.S. If you centralize your data handling in one place (for instance a component) you will get simpler and more easily testable code. For instance, you can remove all state from the Pizza class and just let it have noOfSlices and diameter passed to it as props from the Orders class.
I have answer. I create clas Static with static value, and this is working for me.
static
class Static {
static defaultProps = {}
}
export default Static;
order
submit(){
console.log(Static.defaultProps)
pizza
noOfSlices(e){
Static.defaultProps = {noOfSlices : e.target.value};
}

Vue Prop Sync on multiple levels (TypeScript & JavaScript & ClassStyle)

I just started programming with Vue and ts/js so please forgive me if I miss something obvious :)
I am trying to pass a prop down two levels. If level 3 modifies the prop it also changes on level 2 but not on level 1.
Level 1 is a component I wrote and it is written in ts.
<template>
<Child :varr.sync="obj.attr" />
</template>
export default Parent class extends Vue {
obj: Object = {
attr: [1, 2, 3]
};
}
Level 2 is a component I wrote and it is written in ts.
<template>
<ChildsChild :layout.sync="arr" />
</template>
export default Child class extends Vue {
#PropSync("varr", { type: Array }) arr!: number[];
}
Level 3 is a component I DID NOT write and it is written in js.
export default {
props: {
layout: {
type: Array,
required: true,
}
}
}
When you pass something down to a child, you need to be careful to not update the prop directly on the child, because this is actually re-rendered every time the parent updates.
In your case, I'm not really sure why the PropSync isn't working. The similar pattern I've seen before is separating out the prop and adding a watcher as below
i.e.
Parent
<template>
<Child :varr.sync="obj.attr" />
</template>
export default Parent class extends Vue {
obj: Object = {
attr: [1, 2, 3]
};
}
Child:
<template>
<ChildsChild :layout.sync="passed_varr" />
</template>
export default Child class extends Vue {
props {
varr: {
type: Object
}
}
data: instance => ({
passed_varr: instance.varr,
}
watch: {
passed_varr: {
handler() {
this.$emit('update:varr', this.passed_varr);
},
deep: true,
},
},
}
In this case, the prop varr is synced with the parent component, so when layout is updated in the grandchild, it syncs with child.passed_varr which has a watcher to emit an update to parent.varr
Looking at the docs here it seems like PropSync is doing a similar thing, so not sure on the particular differences

How can I, in Vue, define a local data property that uses a prop as its initial value, in Typescript syntax?

When passing a prop to a child component in Vue, the documentation says:
In addition, every time the parent component is updated, all props in the child component will be refreshed with the latest value. This means you should not attempt to mutate a prop inside a child component. If you do, Vue will warn you in the console.
The prop is used to pass in an initial value; the child component wants to use it as a local data property afterwards. In this case, it’s best to define a local data property that uses the prop as its initial value:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
We are using typescript. The syntax for "defining a local data property" is as follows (to my understanding):
<script lang="ts">
import Vue from 'vue'
import { Component } from 'vue-property-decorator'
#Component
export default class App extends Vue {
// Data property
myDataProperty: string;
</script>
And the syntax for a prop is:
#Component
export default class App extends Vue {
// Makes a "exampleProperty" a component prop with the default value of 'Example'
#Prop({default: 'Example'})
exampleProperty: string
}
So, we tried to follow the documentation, and ended up with:
parentComponent.vue
<template>
<childComponent testProperty='test' />
</template>
childComponent.vue
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
#Component
export default class childComponent extends Vue {
#Prop(
{
default: 'notTest',
validator: (component) => {
return [
'notTest',
'test',
].indexOf(component) > -1;
},
},
)
testProperty!: string;
testProperty = this.testProperty;
</script>
That, predictably, errored with `Duplicate identifier testProperty.
So, we tried
...
testProperty!: this.testProperty;
...
which resulted in
Duplicate identifier 'testProperty'.
Property 'testProperty' has no initializer and is not definitely assigned in the constructor.
Subsequent property declarations must have the same type. Property 'testProperty' must be of type 'this', but here has type 'any'.
So, I decided to try the "vue-class-component" decorator.
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
#Component({
data: function(){
return {
testProperty: this.testProperty,
}
}
})
export default class childComponent extends Vue {
#Prop(
{
default: 'notTest',
validator: (component) => {
return [
'notTest',
'test',
].indexOf(component) > -1;
},
},
)
testProperty!: string;
testProperty = this.testProperty;
</script>
This resulted in the error Property 'testProperty' does not exist on type 'Vue'.
I would like to, in a handler, do this.testProperty = 'newProperty' at some point, but cannot, because that would be directly modifying a prop.
How can I define a local data property that uses a prop as its initial value in Typescript?
EDIT:
If I do none of the above, and simply define the prop, with no attempt to define a local data property that uses the prop as its initial value, and then do
this.testProperty = 'test'
in a handler, this error is displayed in the chrome console:
vue.runtime.esm.js[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "testProperty"
I will summarise my comments into a single coherent answer: the problem you are seeing is that you have already defined this.testProperty by declaring it as a prop: doing testProperty = this.testProperty is a circular reference at best. Using the #Prop decorator alone will do the mapping of the attribute in the template to the variable.
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
#Component
export default class childComponent extends Vue {
#Prop(
{
default: 'notTest',
validator: (component) => {
return [
'notTest',
'test',
].indexOf(component) > -1;
},
},
)
testProperty!: string;
// Map prop to local data property
testPropertyLocal = this.testProperty;
</script>
Also, remember this caveat: VueJS properties must be kebab-case in templates and camelCase in JS. So, you need to update your child component reference to:
<template>
<childComponent test-property='test' />
</template>

typescript + react - mixed variable in state

I have this code in my react application, as per interface, a variable items should be an array.
But, in my initial state, there it is initialized as null (for I need as null as initial state).
Within the interface declaration I could have written ?Array, but it would mean, that the key items may not be in the state at all - and provided that this key is present within the object, it is going to be an array.
What are options? Is my architecture bad? Or how to declare required variable as mixed array|null?
declare interface StateInterface {
items: Array
}
class MyComponent extends Component {
state: StateInterface = {
items: null
};
}
You could initialize it to an empty array:
class MyComponent extends Component<{}, StateInterface> {
constructor() {
this.state = { items: [] };
}
}

QML: How to trigger onChanged in custom Component?

Assume we have the following custom QML Components.
MyComponent.qml
//Contents for MyComponent.qml
import QtQuick 2.0
QtObject{
property real myProperty
...
}
Test.qml
import QtQuick 2.0
Item {
property var myComponent: MyComponent
onMyComponentChanged: console.log("MyComponent changed!");
}
When changing any of the properties in myComponent, I want onMyComponentChanged() to be triggered. What is the best way to accomplish this?
In QML, most of the properties have onChanged events. For example;
MyComponent {
property string name: "Super"
}
i.e, on + Property + Changed signal will be emitted (first letter of the property will be Upper case) - resulting in onNameChanged
Item {
id: mainItem
property MyComponent myCustomComponent: MyComponent {
onNameChanged: mainItem.handleMyComponentChange()
}
function handleMyComponentChange() {
-----
}
myCustomComponent.name="Duper" // triggers handleMyComponentChange()
}
There is kind of a limitation in QML in this regard.
Since your property is itself an object rather than a primitive, its changed signal will emit only when the property is changed to be assigned to another object, it WILL NOT reflect internal changes to this object. Also, you cannot manually emit the signal either, it will only automatically emit when the property is changed.
myComponent.myProperty = 1.1 // this will not trigger onMyComponentChanged
myComponent = anotherObject // this will trigger onMyComponentChanged
Since your component has only a single property and it already has a change notification, you can use that:
property var myComponent : myComp // you can omit that if you don't need it as a property
MyComponent {
id: myComp
myProperty: 13.37
onMyPropertyChanged: console.log("changed")
}
or...
property var myComponent : MyComponent {
myProperty: 13.37
}
Connections {
target: myComponent
onMyPropertyChanged: console.log("changed")
}
If your component has multiple properties, you should implement a signal changed() in it and emit it on every property changed and use that signal to reflect internal changes instead of the one automatically generated by the QML property, which will not reflect them:
QtObject {
property real myProperty
property bool otherProperty
onMyPropertyChanged: changed()
onOtherPropertyChanged: changed()
signal changed()
}
...
property var myComponent : MyComponent {
myProperty: 13.37
}
Connections {
target: myComponent
onChanged: console.log("changed")
}
You can connect the signal myPropertyChanged to the signal myComponentChanged, the same of c++.
to do that :
import QtQuick 2.2
Item {
id:idRoot
width: 800
height: 480
property var myComponent: Item
{
id:item
property int myProperty: 13
// timer to change myProperty
Timer{
running: true
interval: 2000
repeat: true
onTriggered: item.myProperty += 1
}
}
onMyComponentChanged: console.log("MyComponent changed!");
Component.onCompleted:{
// connect signals
// info: you can connect signal to signal or signal to function
item.myPropertyChanged.connect(idRoot.myComponentChanged)
// disconnect signals
//item.myPropertyChanged.disconnect(idRoot.myComponentChanged)
}
}
Edit for ddriver:
try this, you can see that I emit the signal without changing the property:
import QtQuick 2.2
Item
{
id:idRoot
width: 800
height: 480
// when you create a property its signal myPropertyChanged()
// and its slot onMyPropertyChanged are created
// you can emit this signal when you want
property int myProperty: 13
// timer to emit myPropertyChanged()
Timer{
running: true
interval: 2000
repeat: true
onTriggered: myPropertyChanged()
}
onMyPropertyChanged: console.log("MyProperty changed!",myProperty);
}

Categories