Custom QML element - Item or Component - qml

When declaring custom QML element in separate file (to be reusable across project(s)), which option is better, to declare it as Item or as Component and what are pros and cons for them?

Once I had the same doubts and the following example helped me. I put it there, hoping it can help you.
Here is the component defined in a file somewhere:
// FooBar.qml
// import whatever.you.need
Rectangle { }
Here a possible use as involved as main item of another component definition:
// ...
Component {
id: myFooBar
FooBar { }
}
// ...
Well, what about if the first one was as it follows?
// FooBar.qml
// import whatever.you.need
Component {
Rectangle { }
}
Actually it doesn't make much sense, besides the official documentation. Does it?
That's why I've never tried it... But I've also read the documentation, of course, as kindly pointed out by someone else!! It's far more helpful. :-)

Related

Best practice to write minimal code when using vuejs vee-validate with composition api in vuejs3

I have gone through the composition api docs for vee-validate, and I can definitely get my validation working on my forms if I follow the pattern described in their docs.
I don't feel comfortable however doing it as documented there, as I just feel I'm writing too much code for each form, and I do not want to repeat myself.
So I've been experimeting a bit with how I can optimise this, and this is what we currently came up with, but I'm a bit stuck now.
You can see the code example on https://codesandbox.io/s/restless-star-1qwgb, but I'll walk you through.
Consider we want to create a form for creating a new Invoice.
An invoice is typically composed of the Invoice model with a number of InvoiceElements attached to it. (the invoice lines)
In our vueJs codebase, we have javascript classes representing each model that we need to work with, so you'll find a class Invoice and a class InvoiceElement, both extending BaseModel which already providers some basic functionality.
On each mode, we have defined a static method returning a yup validation schema, e.g.:
import * as yup from "yup";
import BaseModel from "./BaseModel";
import InvoiceElement from "./InvoiceElement";
export default class Invoice extends BaseModel {
static get validationSchema() {
return yup.object().shape({
due_date: yup.date().min(new Date()).default("2021-09-30"),
reference: yup.string(),
elements: yup
.array()
.of(InvoiceElement.validationSchema)
.default([InvoiceElement.validationSchema.getDefault()])
});
}
}
As you can see, we also define default values for each of the schema fields.
This allows us to do the following in our form component:
setup() {
let { handleSubmit, errors, values } = useForm({
validationSchema: Invoice.validationSchema,
initialValues: Invoice.validationSchema.getDefault(),
validateOnMount: false,
});
let addLine = () => {
values.elements.push(InvoiceElement.validationSchema.getDefault());
};
let submitForm = handleSubmit((values) => {
alert("form was valid and we submit data here");
});
return {
values,
errors,
addLine,
submitForm,
};
}
Okay - I'm very happy with this as we now have:
values which is reactive and I can just bind to my inputs using ```
errors which has my errors
When I submit the form, it correctly triggers vee-validate's handleSubmit() method and my errors get correctly updated.
My main issue with this approach now is how to trigger the validation of the fields when they get updated. The main goal is to avoid having to write too much code using the useField() composable.
I know I'm not following the proposed pattern, but it just kept feeling as if we were writing too much code, and we seem quite close to a good pattern, but I just don't get the last bits...
Maybe someone on here does though :-)
Generally, the docs are only meant to show you "how it works", but I do agree that useField is very verbose if used incorrectly. Using it for models isn't the intended use-case.
For example:
// Very verbose and not the intended use-case
const { value: email } = useField(...);
const { value: password } = useField(...);
The main purpose of useField is to help create input components that can be validated, that if you are willing to couple your form components with vee-validate which I find reasonable in 80% of situations.
To try to answer your issue, since you want to avoid using useField you could actually make use of the other useXXX functions called composition helpers. Like useValidateField which creates a function for you that can validate any field given it was created in a child of a useForm component.
const validate = useValidateField('fieldName');
validate(); // triggers validation on the field

How do I test bootstrap-vue toasts

I have been instructed to assert that our errors have been surfaced in bootstrap-vue toasts. How can I assert that the toast has been made on my tests. I was hoping to mock out ‘this.$bvToast.toast’ however I have been unable to find anything salient so far.
I stumbled upon the same problem and the way I solved it was to wrap the this.$bvToast.toast() around a component method showToast():
// component
{
...
showToast(title: string, content: string) {
this.$bvToast.toast(
content,
{
title,
...
}
}
}
}
Then, in my jest file I just use a spy and make sure the parameters are the ones I expect
// component.spec
const mySpy = jest.spyOn(myComponent, 'showToast');
...
your code here
...
expect(mySpy).toHaveBeenCalledWith(expectedTitle, expectedContent)
Of course, this doesn't test the actual text inside the bootstrap-vue toast component, but this defeats also the purpose of the unit test: you assume that the toast works as it should :)

The purpose of StyleSheet.create in React Native

I wanted to ask the community about the changes in StyleSheet.create in React Native.
Before:
I have reviewed the past questions about this topic, such as this question, but they all have been answered pretty a long time ago (apart from this answer, but I wanted to have something definite) and a lot has changed since.
Before StyleSheet was creating a unique id for the styles, mainly for performance optimisations. If you wanted to get the styles out of the created styles object, you should have used the flatten method. The majority of the answers reference this flatten method and you could not access styles property as if it was a normal object.
E.g.
const styles = StyleSheet.create({
modalContainer: {
width: 100,
backgroundColor: 'white',
padding: 5,
},
You could not access the padding styles like styles.modalContainer.padding;
Currently:
However, the behaviour of this has changed. This is the source code of StyleSheet from the React Native team. Just copying the create method:
create<+S: ____Styles_Internal>(obj: S): $ObjMap<S, (Object) => any> {
// TODO: This should return S as the return type. But first,
// we need to codemod all the callsites that are typing this
// return value as a number (even though it was opaque).
if (__DEV__) {
for (const key in obj) {
StyleSheetValidation.validateStyle(key, obj);
if (obj[key]) {
Object.freeze(obj[key]);
}
}
}
return obj;
},
};
Which is just returning the object passed to create without doing anything to it. So you can actually access the styles as styles.modalContainer.padding;
Maybe I don't understand clearly the TODO, but this method has been coded this way at least since RN 0.57 and I don't know whether they are going to change it back.
My question:
Is there any sense in using StyleSheet.create anymore?
Thanks in advance for sharing your opinions!
Stylesheet is generally used to create a global style in react native and add it to the respective views which requires to style the objects.
Some widgets like TextInput, Text, Button cannot apply almost all the css styles in the react native.
So, in those cases what you can do is you can wrap those widgets with and then can create global StyleSheets using StyleSheet.create() method to globally use and reduce your headache.
So the conclusion for your question can be summarized as the Stylesheet.create() can be helpful to improve the performance while styling your multiple views using the same style will create a new object every time for each one.
While Stylesheet.create() will act as a single global object for all the views which are using it to style themselves resulting performance/memory optimisation.
I've never heard of this flatten() being necessary like you described. In fact, in the React Native repo in the very first commit, there was an example provided:
Examples/Movies/MovieCell.js:
https://github.com/facebook/react-native/commit/a15603d8f1ecdd673d80be318293cee53eb4475d#diff-4712aeb2165b3c0ce812bef903be3464
In this example, you can see var styles = StyleSheet.create({..}); being used in its present flavor and at that moment in 2016 you can see styles being referenced in the components as styles.styleName.
Additionally in the StyleSheet class here is create from the initial commit:
class StyleSheet {
static create(obj) {
var result = {};
for (var key in obj) {
StyleSheet.validateStyle(key, obj);
result[key] = StyleSheetRegistry.registerStyle(obj[key]);
}
return result;
}
// ...
}
As you see, no call to flatten on the initial commit, neither inside the create method, nor from the user using create.
In summary it seems this never changed and you could always access the styles using the dot operator.
As for whether to use it I don't think you have a choice. It clearly has some sort of validation code inside of it, it's also using type checking and the react team recommends using it. I don't see any other methods that do what it does. How could you use the class without create, just using some sort of init or constructor method? I don't see one on the class. There is no StyleSheet({...}); To get obj returned you need to call create.
Your editor could not give you IntelliSense if you strip away the validation behavior and make it a plain object. You won't know when you're making typos or referencing styles that don't exist, you won't have autocompletion. You'd need to create your own interfaces and use TypeScript. Thus you should use create because otherwise at a minimum you're breaking your IDE.

How to use ionViewDidEnter in directives

I am trying to create a directive where I animate a fab-button when the view is shown.
The animation works if it is inside ngOnInit, but due to ionic route strategy the animation doesn't work when I leave the page and go back. Putting it in ionViewDidEnter didn't work because I presume that ionViewDidEnter doesn't work inside the directive. So is there any approach I can take to solve this?
<ion-fab vertical="bottom" horizontal="end" slot="fixed">
<ion-fab-button mode="md" appAnimateFab>
<ion-icon name="create" mode="md"></ion-icon>
</ion-fab-button>
</ion-fab>`
#Directive({
selector: 'ion-fab-button[appAnimateFab]'
})
export class AnimateFabDirective implements OnInit {
constructor(
private animationBuilder: AnimationBuilder,
private element: ElementRef
) { }
ngOnInit() {
}
ionViewDidEnter() {
console.log(this.element);
const factory = this.animationBuilder.build([
style({transform: 'rotate(-45deg)'}),
animate('5s ease-in', style({transform: 'rotate(0deg)'}))
]);
const anim = factory.create(this.element.nativeElement);
anim.play();
}
}
This is an interesting question. I got halfway through writing out a detailed reply yesterday when I realised that you were actually asking about directives and not custom components... so all my research was wrong haha.
Today I have had another look. The tutorials all seem to conveniently miss having a requirement to deal with pages changing backwards and forwards and just lean on ngOnInit.
After scratching my head for a bit I started to wonder how else it could be triggered and I'm thinking: what about the Intersection Observer API?
I really like the way Alligator.io explain things:
Using the Intersection Observer API to Trigger Animations and Transitions
Their example shows the animation being triggered every time you scroll down to view.
If you are flipping pages then it feels like it should trigger as coming into view, but I haven't tested this out with code.
For a more Ionic-focused example with Intersection Observer API, Josh has a tutorial:
Animating List Items in Ionic with the Intersection Observer API | joshmorony - Learn Ionic & Build Mobile Apps with Web Tech
Maybe you can adapt this to use your animation code?

QT5: Instantiate the same QML components multiple times

I want to create component templates, meaning that I define my own MyButton type in a separate QML file, and I also want to define several singleton instances, like:
Predefined.qml:
pragma Singleton
[...]
property MyButton quitButton : quitButtonItem
MyButton {
id: quitButtonItem
text: qsTr("Quit")
imagesource : "qrc:/icons/quit.png"
}
then use it as:
Predefined { id: predefined }
Rectangle {
predefined.quitButton {
onClicked: console.log ("quit pressed.");
anchors.bottom : parent.bottom
anchors.horizontalCenter : parent.horizontalCenter
}
}
a.) I don't want to use Loaders for this -> overkill
b.) Don't really want to define as my QML files as many components I want to
clone (eg QuitButton.qml, BackButtonQml, etc.)
Any idea how to have it done?
Thanks
It can't be done.
The only way to instantiate an object declaratively from QML without a Loader is to create a new file for each component.
My suggestion for your usecase would simply be to create the files.
Alternatively, it appears you are doing some sort of navigation bar. What about unifying this in a single component?
I see two ways: have a single global nav bar for all your app, for example in ApplicationWindow's header, or have a commonbase type like YourPage.qml where you implement your bar, and then simply inherit from it for your actual content.
Personally, I adopted the first solution.