What is the best "decoupled" way to give focus to an Aurelia component? - aurelia

Let's say I've built some kind of Aurelia component. For this example, let's say I've built a hypothetical component called ui-money.
Let's say that the ui-money component comprises a text input element, and another element (eg. span) alongside the input that shows an exchange rate. In essence, something like:
<template>
<input value.bind="amountStr" ... >
<span>${exchange_rate}</span>
</template>
I then build an Aurelia view (page) which includes the <ui-money> element.
My question is this: let's say I want to put focus onto the "ui-money" element.
Practically speaking, I don't want to know the internal makeup of the ui-money element (white-box knowledge), nor should I want it. But clearly I need the focus to go to the INPUT element WITHIN the ui-money element.
So, it would seem that I need to ask the ui-money element to perform the act of setting focus, for me.
Now the most obvious first option would be to supply a ref to the ui-money element as such <ui-money ref="purchasePriceUx"> and have the ui-money view-model expose some kind of takeFocus() method. We could then invoke
purchasePriceUx.takeFocus().
But I'm interesting in knowing if there is a better way to achieve this, whilst still retaining the same level of decoupling.

You can use bindable properties and the focus attribute that ships with the standard configuration of the framework: https://gist.run/?id=7587f1453cb2632fa09b6fe542d9717c
The important stuff is here:
app.html
<template>
<require from="./some-element"></require>
<label for="hasFocus">Has Focus:</label>
<input id="hasFocus" type="checkbox" checked.bind="focus" />
<div>
Custom Element:
<some-element has-focus.bind="focus" text.bind="text"></some-element>
</div>
<div>
Regular text box:
<input type="text" value.bind="text" />
</div>
</template>
some-element.html
<template>
<input ref="textbox" type="text" value.bind="text" focus.bind="hasFocus" />
</template>
some-element.js
import {bindable, bindingMode} from 'aurelia-framework';
export class SomeElement {
#bindable({ defaultBindingMode: bindingMode.twoWay }) text;
// the bound property cannot be named focus as it interferes with
// the focus custom attribute
#bindable({ defaultBindingMode: bindingMode.twoWay }) hasFocus;
}

Related

VueJS 2 - Re-useable parent component that can accept different children

I have a parent component, SearchComponent:
<template>
<div>
<div class="relative pl-8 pr-10 rounded bg-white border focus-within:bg-white focus-within:ring-1">
<input v-focus #keyup.escape="clearSearch" #keyup="doSearch" v-model="searchTerm"
class="w-full ml-4 h-12 pl-1 text-gray-700 text-lg rounded-full border-0 bg-transparent focus:outline-none placeholder-gray-400"
placeholder="Search..." autocomplete="off">
</div>
<ul class="bg-white mt-4">
<quick-search-item v-for="item in searchResults" :key="item.id" :item-data="item.itemData">
</quick-search-item>
</ul>
</div>
</template>
This is responsible for receiving user input and getting results from an ajax call, handling errors etc. and generating the result list.
What I'd like to do is to make this generic so that instead of having a quick-search-item child component I can pass in different types of child component (like car-search-item, person-search-item etc.) depending on the context of where the user is in the app and what they're searching for
I've read a number of tutorials and I couldn't find quite what I'm trying to do. This may mean I'm approaching this in the wrong way - but if anyone could point me in the right direction, or has a better approach, I'd be very grateful.
Thanks,
Lenny.
I would try to make use of the <slot> element. Check out the documentation here
<parent-component>
<slot></slot>
</parent-component>
Hope this can put you on the right path.
Schalk Pretorius was quite right: slots are the answer to this, specifically scoped slots. I found the Vue docs a little confusing as it refers to getting data from the child component and I wanted to do it the other way around, so as an aide memoire for myself and to help anyone else here's what I did:
In my parent component I defined the slot like this:
<slot name="results" v-bind:items="searchResults">
</slot>
The v-bind binds searchResults (a data item in the parent component) to the value 'items'. 'items' then becomes available in the slot.
In my child component I have a simple property setup called items:
props: {
items: {type: Array},
},
Then to hook it all together in my Blade file I did this:
<search-component endpoint="{{ route('quick_search.index') }}">
<template v-slot:results="props">
<food-results :items="props.items">
</food-results>
</template>
</search-component>
This creates the search-component. Inside that -as I'm using named slots - we need a template and use v-slot to tell it which slot to use (results), then the ="props" exposes all the properties we've defined on that slot (in this case just one, 'items').
Inside the template we put our child component and then we can bind items to props.items which will be the searchResults in our parent component.
I'm happy to have this working and I can now create lots of different results components while reusing the search component - and at least I learnt something today!
Cheers,
Lenny.

Conditional wrapper rendering in vue

I'm making a link/button component which either can have a button or an anchor wrapper, a text and an optional icon. My template code below is currently rendering either an anchor or a button (with the exact same content) based on an if statement on the wrapper element, resulting in duplicate code.
<template>
<a v-if="link" v-bind:href="url" class="btn" :class="modifier" :id="id" role="button" :disabled="disabled">
{{buttonText}}
<svg class="icon" v-if="icon" :class="iconModifier">
<use v-bind="{ 'xlink:href':'#sprite-' + icon }"></use>
</svg>
</a>
<button v-else type="button" class="btn" :class="modifier" :id="id" :disabled="disabled">
{{buttonText}}
<svg class="icon" v-if="icon" :class="iconModifier">
<use v-bind="{ 'xlink:href':'#sprite-' + icon }"></use>
</svg>
</button>
</template>
Is there a more clean way for wrapping my buttonText and icon inside either an anchor or button?
I've solved my issue by intensive Google-ing! Found this issue regarding Vue on Github which pointed me in the right direction.
Small piece of backstory
I'm using Vue in combination with Storybook to build a component library in which a button can either be a button or an anchor. All buttons look alike (apart from color) and can be used for submitting or linking. To keep my folder structure ordered, I would like a solution that generates a multiple buttons types (with or without link) from one single file.
Solution
Using computed properties I'm able to "calculate" the necessary tag, based on the url property of my component. When a url is passed, I know that my button has to link to another page. If there is no url property it should submit something or preform a custom click handler (not in the sample code below).
I've created the returnComponentTag computed property to avoid placing any complex or bulky logic (like my original solution) in my template. This returns either an a or a button tag based on the existence of the url property.
Next, as suggested by ajobi, using the :is attribute I'm able to define the component tag based on the result of my computed property. Below a stripped sample of my final (and working) solution:
<template>
<component :is="returnComponentTag" v-bind:href="url ? url : ''" class="btn" :class="modifier" :id="id">
{{buttonText}}
</component>
</template>
<script>
export default {
name: "Button",
props: {
id: {
type: Number
},
buttonText: {
type: String,
required: true,
default: "Button"
},
modifier: {
type: String,
default: "btn-cta-01"
},
url: {
type: String,
default: ""
}
},
computed: {
returnComponentTag() {
return this.url ? "a" : "button"
}
}
};
</script>
You could extract the wrapping element into a dedicated component.
<template>
<a v-if="link" v-bind:href="url" class="btn" :class="modifier" :id="id" role="button" :disabled="disabled">
<slot></slot>
</a>
<button v-else type="button" class="btn" :class="modifier" :id="id" :disabled="disabled">
<slot></slot>
</button>
</template>
// You would use it like this
<SomeComponent /* your props here */ >
{{buttonText}}
<svg class="icon" v-if="icon" :class="iconModifier">
<use v-bind="{ 'xlink:href':'#sprite-' + icon }"></use>
</svg>
</SomeComponent>
There are multiple ways of doing this. Two examples would be the following based on the point of view:
You are defining two different components (Button or Anchor) and want to use a wrapper to render either one of them.
You could seperate the Wrapper Content into two components so that the wrapper only decides on which of the components to render (either the Button or the Anchor).
The problem with this approach could be you will have doubled code for methods and styling for the button and anchor component.
You are defining the content as a component and use the wrapper to define what to wrap the content in.
See Answer of https://stackoverflow.com/a/60052780/11930769
It would be great to know, why you would want to achive this. Maybe there are better solutions for your usecase. Cheers!

What are inheritAttrs: false and $attrs used for in Vue?

As the question suggests, I can't figure out their meaning and why I should use it. It's said that it can be used so that when we have many components and we want to pass data from parent to the child's child's child's component, we don't have to use props. Is this true?
It'd be nice If you could provide an easier example. Vue.js docs don't mention much on it.
Have a look at the "Disabling Attribute Inheritance" section of the docs and the api description for the full details.
It's main usage is to define so-called "transparent" components that pass-through attributes. The example given in the doc is a component wrapping an input element:
// Component
Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
template: `
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
</label>
`
})
// Usage
<base-input
v-model="username"
required
placeholder="Enter your username"
/>
The required and placeholder attributes are then set on the input instead of the wrapping label.
It doesn't really have anything to do with children of children of components but it can be used in such a hierarchy.
I hope that clears things up for you.
For me talking about $attrs and inheritAttrs together, makes it harder to understand. So I will show a bit more observations.
I will call my base-inp from HTML like below
<base-inp label="Username:" v-model="username"
required placeholder="Enter your username"></base-inp>
The HTML for base-inp is like below
<label>
{{ label }}
<input v-bind="$attrs" :value="value" #input="$emit('input', $event.target.value)" />
</label>
v-bind="$attrs" is used to bind attributes.
If you set v-bind="$attrs" and inheritAttrs: true (default) and render your base-inp component. It will render
So basically we are passing the attributes entered into base-inp component in the HTML template. And also into 'input' inside the base-inp. You can see 'placeholder' in both 'label' and 'input'.
If we delete v-bind="$attrs", the placeholder won't be an attribute in inner 'input'
If you set v-bind="$attrs" and inheritAttrs: false, you will see attributes are not on the root element (label) but on inner input.

Vue, change native event not working, input native does

I have a question and maybe a Vue bugg.
I have a custom component that needs a #change.native event. But it does not trigger anything and I could not find anything about this issue myself.
So i tried some different stuff and like #click.native and #input.native does work. Even tho #input.native works and do the trick i want to, i still want to know why the change event does not work.
Anybody? Else I should report this.
Vue version: 2.5.2
<custom-input type="search"
placeholder="search"
v-model="search"
#input.native="change" />
If the <input /> inside the custom component is a child element of another element, then the event listener bound by the .native modifier will not be reached, since it is listening to the event of a different element.
custom-input.vue:
<template>
<div>
<input :value="someValue" />
</div>
</template>
<script>
export default {
props: ['value']
}
</script>
so if you have this scenario, then the #change.native will be bound on the <div> (the wrapper).
(sadly) You need to manually propagate the event manually.

Remove validation in angular 4 when it's not render in NgIf

I want to remove validation for which control is not rendered by using NgIf. I was try to use directive to remove with hidden control but cannot do the same because it not render in template. So I can not check formControlName with ElementRef in directive. Here is ts file
this.form = this._fb.group({
text1: ['', Validators.required],
text2: ['', Validators.required]
});
and template
<form[formGroup]="form">
<input type="text" formControlName="text1">
<div *ngIf="false">
<input type="text" formControlName="text2">
</div>
I want to remove Validation of text2 dynamically and globally. Not remove validator in ts file.
This Angular source GitHub Issue comment by Kara seems extremely relevant, and illustrates how you might solve the problem by treating the reactive model as "source of truth" and create your ngIf expression off of that source of truth, instead of the reverse. This shows it's by design and you have to make some effort not to mix up template-driven and reactive form ideas.
https://github.com/angular/angular/issues/7970#issuecomment-228624899
Thanks for taking the time to describe the problem. I took a look at
your example code, and it seems that you are using the reactive form
directives (a.k.a "model-driven" directives: ngFormModel, etc), but a
template-driven strategy. The fact that the ngIf does not remove the
control from the form's serialization and validation is actually by
design, and here's why.
In each form paradigm - template-driven and reactive - there can only
be one source of truth for the list of active controls. In the
template-driven paradigm, the source of truth is the template. In the
reactive equivalent, the source of truth is the form model created in
the parent component. The DOM does not dictate the state of your form.
For this reason, if you remove form control elements from the DOM
while using a reactive approach, the form controls are not necessarily
changed in the source of truth unless you want them to be. You can
choose to update the controls imperatively by calling
this.form.removeControl('controlName'), or you can choose to keep the
controls in your form. This flexibility allows you to add or remove
inputs from the DOM temporarily while keeping their form values
serialized (e.g. if you have a number of collapsible sections to your
form, you can remove sections on collapse without impacting the value
of your form). We don't want to restrict this flexibility and
complicate ownership by forcing the model to always match the DOM.
So in your case, if you choose a reactive strategy, you'll want to
invert your logic to rely on the source of truth - the model.
Specifically, this means removing the control imperatively in the
model by calling this.form.removeControl('name') when the button is
clicked. Then, the ngIf should depend on the control's presence in the
model with *ngIf="form.contains('name')", rather than the other way
around. See example plunker here:
http://plnkr.co/edit/V7bCFLSIEKTuxU9jcp6v?p=preview
It's worth noting that if you're still using beta.14 (as in your
plunker), you'll need to call this.form.updateValueAndValidity()
manually. This requirement was removed in #9097, so versions after
RC.2 don't require the call.
Another option is to convert to a template-driven strategy (no
ngFormModel), which will remove the control from the form when it's
destroyed from the template. Example:
http://plnkr.co/edit/s9QWy9T8azQoTZKdm7uI?p=preview
I'm going to close this issue as it works as intended, but I think we
could make the experience a lot friendlier. A good start would be some
more cookbooks and guides in the documentation.
When the condition property is changed then call the method dynamically to set and remove the validation. for example,
whenConditionChanges(condition:boolean){
if(!condition){
this.form.controls["text2"].setValidators([Validators.required]);
this.form.controls["text2"].updateValueAndValidity();
} else {
this.form.controls["text2"].setValidators(null);
this.form.controls["text2"].updateValueAndValidity();
}
}
Since, your formcontrol text2 is dependent on some condition. it should not be as required control. So you reactive form control should be
this.form = this._fb.group({
text1: ['', Validators.required],
text2: ['',]
});
If there is scenario, where you want to ensure that text should be required whenever it's present in dom then use custom validators in angular. Refer documentation of the same for your implementation.
Here the Example: on runtime you can update validators based on checkbox value.you can set field as required and remove also.
http://plnkr.co/edit/YMh0H61LxPGCFtm9Yl13?p=preview
What i did (and work for me), create an alternative formgroupcontrol with another button [disabled], manage the *ngIf for the button and for the form.
<mat-step [stepControl]="listBrandFormGroup">
<form [formGroup]="listBrandFormGroup">
<ng-template matStepLabel>Define tu marca</ng-template>
<div class="heading">¡ Haber ! Definamos tu marca</div>
<div class="subheading">Estamos a punto de hacer magia, solo necesitamos lo siguiente:</div>
<div class="content" fxLayout="column" fxLayoutGap="8px" *ngIf="listBrand.length > 0">
<mat-form-field fxFlex="auto">
<mat-select name="brand_id" formControlName="brand_id" placeholder="Selecciona una marca existente" (selectionChange)="setBrand($event.value);">
<mat-option [value]="0">Crear una nueva marca</mat-option>
<mat-option *ngFor="let marca of listBrand" [value]="marca.id">{{marca.display_name}}</mat-option>
</mat-select>
<mat-hint>{{descripBrand}}</mat-hint>
</mat-form-field>
</div>
</form>
<form [formGroup]="brandFormGroup">
<div class="content" fxLayout="column" fxLayoutGap="8px" *ngIf="idBrand === 0">
<mat-form-field>
<mat-label>Marca</mat-label>
<input matInput formControlName="display_name" required>
<mat-hint>Ese increíble y único nombre, ¡ tú sabes !</mat-hint>
</mat-form-field>
<mat-form-field fxFlex="grow">
<mat-label>Descripción</mat-label>
<textarea matInput formControlName="descrip" required></textarea>
<mat-hint>¿ Cuéntanos de que se trata ?</mat-hint>
</mat-form-field>
<mat-label>Logo</mat-label>
<input type="file" name="photo" ng2FileSelect required formControlName="display_logo" />
</div>
<div class="actions" fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="8px">
<button mat-button type="button" (click)="stepper.reset()" [disabled]="brandFormGroup.pristine"
color="primary">RESET
</button>
<button mat-raised-button matStepperNext color="primary" [disabled]="brandFormGroup.invalid" *ngIf="idBrand === 0">SIGUIENTE</button>
<button mat-raised-button matStepperNext color="primary" [disabled]="listBrandFormGroup.invalid" *ngIf="idBrand > 0">SIGUIENTE</button>
<button mat-raised-button matStepperNext color="primary" [disabled]="listBrandFormGroup.invalid" *ngIf="idBrand > 0" (click)="launch();"><i class="material-icons">launch</i>LANCÉMONOS</button>
</div>
</form>
</mat-step>