Radiobutton-like behaving Vue.js components - javascript

I have the following Vue.js components, which basically are supposed to have a radiobutton-like behaviour:
// Parent Component
<template>
<child-component
v-for="element in elements"
</child-component>
</template>
<script>
import ChildComponent from './Child.vue'
export default {
components: {
ChildComponent
},
props: {
elements: Array
},
methods: {
activate(e) {
for (let i of this.$children) {
i.active = false;
}
if (e < this.$children.length) {
this.$children[e].active = true;
}
}
}
}
</script>
and
// Child Component
<template>
{{active}}
</template>
<script>
export default {
props: {
active: Boolean
}
}
</script>
This works fine but only the parent can decide to activate one of the children (and thus deactivate all others).
I want however also be able to allow each child to activate itself (and by a magic property of its parent, deactivate all other siblings).
Obviously I do not want each child to know about its siblings and mess with their .active prop (super bad design).
I would rather not have a children communicate back up to the parent and call some method (still bad design as I could only reuse the child components in parents that have activate() method).
Instead I would like the parent to listen to changes to all children active props and take action when one of them changes. That way the parent entirely encapsulates the radio button behavior.
How can this interaction be implemented in Vue.js?

Take a look at two-way binding: http://vuejs.org/guide/components.html#Prop_Binding_Types
This allows you to sync a property's value in both directions, meaning the parent or the child has access to change the variable. Then you can watch for changes on the parent and update accordingly.
I think a better option would be to create a RadioSet component, which then would house a number of radio buttons. This would eliminate your concern about a parent having to have the activate() method. You could simply pass in an object with a series of id and values that could be used to generate the buttons.

Related

Vue3 - pass multiple event listeners along with their handlers to child component

in Vue3 I can do the following:
Parent Component:
<ChildComponent :props="{ id: 'the-id', class: 'the-class' }" />
ChildComponent:
<template>
<input v-bind="props" />
</template>
<script>
export default {
props: {
props: { type: Object }
}
}
</script>
This will result in HTML like this:
<input id="the-id" class="the-class" />
I'm still learning Vue and I was wondering if I could do the same thing with event listeners / handlers.
With reusable components I might need different event listeners / handlers, depending on where I use the component. In one form I might need only an #input="..." in the child component, in another form I might also need an #blur="..." or I might not need an event listener at all.
Is it possible to do something similar to this?
ParentComponent:
<ChildComponent :events="{ input: function() { alert('input!'); }, blur: function() { alert('blur!'); } } />
ChildComponent:
<template>
<input #events />
</template>
<script>
export default {
props: {
props: { type: Object }
}
}
</script>
Thank you ;)
You can also do something similar like you did with the props, by passing an object to v-on:
<input v-on="{ input: doThis, blur: doThat }"></button>
See here: https://v3.vuejs.org/api/directives.html#v-on
The root element of a component automatically inherits all non-prop attributes (including event listeners) applied to the component from the parent. So if <input> were really at the root in ChildComponent, you wouldn't need to declare anything, and you could simply apply attributes to ChildComponent as if it were the input itself:
<ChildComponent id="the-id" class="the-class" #blur="onBlur" />
demo 1
However, this will break as soon as you add another element to ChildComponent because <input> would no longer be the root. It also would be a problem if you wanted to switch the root element (e.g., a <label> that wraps the <input>).
To disable automatic attribute inheritance, allowing control of where to apply the inherited attributes, set inheritAttrs: false in component options. (This doesn't need to be disabled when there are multiple root nodes, as it only applies for single-root components.) Then manually v-bind the $attrs prop to any element within:
<template>
<label>My input: <input v-bind="$attrs"></label>
</template>
<script>
export default {
inheritAttrs: false,
}
</script>
demo 2
My recommendation would be to define the different events though Vue's Custom Events; The custom events would allow the users of the component to choose which event to subscribe to and handle it accordingly.
https://v2.vuejs.org/v2/guide/components-custom-events.html

Angular get status of all children components from parent component

I have components 'Parent' and 'Child'.From Parent we can add or remove child. so children are dynamic. in Parent I have rendered Child component in loop like below
Parent.component.html
<child *ngFor="let child of children" [data]="child"></child>
Now in child component I have added a function called IsValid() to check child is valid or not
Child.component.ts
IsValid()
{
//check validity of component and return true if valid else false
}
in parent component I have a button called 'Save' I have to enable that button if all child's are valid else need to disable that button.
So I need a way to call Child components IsValid function for each child component from Parent and then determine the validity result and apply it to Save button to enable or disable
What I have tried
1.
I have emited valid or invalid result from child to parent and if any childs result is invalid i have disabled save button.
but problem here is : if I have added one child, make it valid, save button will be enabled. now I have added another child which is invalid so save button will be disabled but if I remove invalid child save button will be disabled though we have only one child which is valid.. since IsValid event get emmited only if current child get change.
2.
I can use something like this
<child #varName></child>
#ViewChild('varName') childElement;
and then from parent I can call
childElement.IsValid()
but since I have rendered childrens in loop how to give unique name in loop and how to add reference to that unique HTML tag in ts file.
I have created case here SlackBlitz
Any help will be appreciated. Thanks
You might want to use #ViewChildren
In your parent component:
#ViewChildren(ChildComponent) children: QueryList<ChildComponent>;
areChildrenValid(): boolean {
const invalid = this.children.some(c => !c.IsValid());
return !invalid;
}
Note that children will be defined after AfterViewInit hook.
#angular/core provides ViewChildren and QueryList, probably that should help you.
<child #varName></child>
import { ViewChildren, QueryList } from '#angular/core';
#ViewChildren("varName") customComponentChildren: QueryList<YourComponent>;
this.customComponentChildren.forEach((child) => { let retult = child.IsValid(); })
You could use Component selector:
#ViewChildren(ChildComponent) childrenList: QueryList<ChildComponent>;
and run a loop through it and determine the validity.

Unknown Prop Warning when React.cloneElement used

I have a React component I built for a popup. PopupContent will receive a DOM element or another React component as a child.
class PopupContent extends React.Component {
render() {
return(
<div>
{React.cloneElement(this.props.children, {closePopup:this.props.closePopup})}
</div>
);
}
}
The closePopup prop sets a flag to show/hide the popup
closePopup(event){
event.preventDefault();
this.setState({
popupInView: false
})
}
The reason to pass closePopup to child is to close the popup from the child component.
This setup works well if the child is a custom React component:
<PopupContent>
<ContentOfThePopup />
</PopupContent>
But I get the Unknown Prop Warning if the child is a DOM element.
Warning: React does not recognize the closePopup prop on a DOM
element.
<PopupContent>
<div>Content Of The Popup </div>
</PopupContent>
I could use techniques explained here to distinguish between a DOM element and a React component. But I wanted to check with the community if there is a better way
what does this.props.children contain?
Shouldn't you be iterating over it?
render() {
return React
.Children
// this is the jsx version of cloneElenemnt,
// better to use in a render function
.map(Child => <Child.type ...Child.props ...this.props>)
}
Clone Element
React.Children.map
The unknown-prop warning will fire if you attempt to render a DOM element with a prop that is not recognized by React as a legal DOM attribute/property. You should ensure that your DOM elements do not have spurious props floating around.
You should ensure that you are not accidentally forwarding props that were intended to be interpreted by the parent component.
Also you can try {...this.props} format to pass your data instead of using cloneElement(element, this.props)
I've ended up checking the type of the child and conditionally removing the prop closePopup
Following condition will be true if the child is a HTML DOM element.
typeof this.props.children.type === 'string

Aurelia conditionally wrapping slots in components

I'm creating Aurelia components which wrap material-components-web, cards specifically right now and am wondering what's the correct way of implementing multiple content sections (actions, etc.).
Slots seem to be the right choice but I cannot just put the actions div on the template at all times, but only if any actions are actually present.
Simply put I need to check if a slot has been defined inside the component template.
<template>
<div class="card">
<div class="content">
<slot></slot>
</div>
<!-- somehow check here if the slot has been defined -->
<div class="actions">
<slot name="actions"></slot>
</div>
</div>
</template>
Out-of-the-box, there is no direct way to do this like a $slots property, however you should be able to access slots via the template controller instance itself: au.controller.view.slots - the specific slot inside of this array has more information about the slot itself and its children.
Here is an example of an Aurelia application with a modal component (custom modal element). The modal itself has a slot where HTML can be projected inside of it. We have a header, body and footer.
Each predefined slot inside of our custom element should show up inside of a children object, where the property name is the name of our slot. If you do not provide a name for a slot (the default slot) the name of it internally is: __au-default-slot-key__.
We first check if the slot exists and then we check the length of its children, the children array exists inside each slot. If a slot has no HTML projected into it, it will have a children length of zero. This is reliable, because default content defined inside of the slot does not get put into the children array, only projected HTML does.
You'll see the work is being done mostly inside of modal.html, but pay close attention to modal.js where we inject the element reference of the custom element and then access the Aurelia instance using au to get to the controller containing our slots itself.
There is one caveat with this approach: you cannot use if.bind to conditionally remove HTML inside of your custom element. If you use if.bind on a DIV containing a slot, it actually removes its slot reference so it can't be checked. To work around this, just use show.bind (as I do in my provided running example).
Use CSS :empty Selector
CSS is the right tool for this job, not Aurelia. The :empty selector will allow you to display: none the div.actions when the slot isn't populated.
.card .actions:empty {
display: none;
}
According to the :empty selector spec as explained by CSS-Tricks, white space will cause empty to fail to match, so we just need to remove the white space around the slot.
<div class="actions"><slot name="actions"></slot></div>
Working example here: https://gist.run/?id=040775f06aba5e955afd362ee60863aa
Here's a method I've put together to detect if any slots have children (excluding HTML comments)
TypeScript
import { autoinject } from 'aurelia-framework';
#autoinject
export class MyClass {
private constructor(readonly element: Element) {
}
private attached() {
}
get hasSlotChildren(): boolean {
if (!this.element ||
!(this.element as any).au) {
return false;
}
let childrenCount = 0;
const slots = (this.element as any).au.controller.view.slots;
for (let slotName of Object.keys(slots)) {
const slot = slots[slotName];
if (slot.children &&
slot.children.length > 0) {
for (let child of slot.children) {
if (child instanceof Comment) {
// Ignore HTML comments
continue;
}
childrenCount++;
}
}
}
return childrenCount > 0
}
}
HTML
<template
class="my-class"
show.bind="hasSlotChildren"
>
<slot></slot>
</template>

Angular2 emit not working in child whilst using recursive component

I have implemented a 'tree' list of categories for an Angular2 (Typescript) app I am developing. This component is supposed to allow you to be able to click on a category name (no matter whether it's a category or sub-category) and this will show products of the category.
My 'category-tree' component is a separate component and it is used recursively so I can traverse the category hierarchy correctly. For each category a span is generated with a 'click' event binded to it. When clicked I use the emit function to broadcast this information back to the parent component in order to update some variables there.
This functionality is working for top-level categories but the click is not working correctly when it is on a child category. The
function which watches for the change does not receive any information.
Here is my code:
The function which logs out the information into my console. This is on the parent component:
changeCategory(event) {
console.log(event);
}
The html for the parent which holds the directive tag and the emit event name (categoryChange):
<div id='left-menu-wrapper'>
<div id='left-menu'>
<h1>{{title}}</h1>
<h2>Categories</h2>
<ul class="categories">
<category-tree [categories]="categories" (categoryChange)="changeCategory($event)"></category-tree>
</ul>
<div *ngIf="selectedCategory">
{{selectedCategory.name}}
</div>
</div>
<div *ngIf="!contentLoaded" class='spinner'></div>
</div>
<product-view [product]="selectedProduct"></product-view>
The child component:
import { Component, Input, Output, EventEmitter, forwardRef } from 'angular2/core';
#Component({
selector: 'category-tree',
templateUrl: './app/views/category-tree.html',
directives: [forwardRef(() => CategoryTree)],
outputs: ['categoryChange']
})
export class CategoryTree {
#Input() categories;
public categoryChange:EventEmitter;
constructor() {
this.categoryChange =new EventEmitter();
}
categoryClick(category) {
this.categoryChange.emit({
value: category
});
}
}
And the recursive component html:
<li *ngFor="#category of categories">
<span (click)="categoryClick(category)" [class.selected]="category === selectedCategory">{{category.name}}</span>
<ul *ngIf="category.sub_categories" class='sub-category'>
<category-tree [categories]="category.sub_categories"></category-tree>
</ul>
</li>
As you can see, I bind a click event to each category which is that current category iteration. This calls an emit function in the category-tree class with that information and broadcasts it back. Again this works with a parent category but not a child.
My thinking is that as a child's direct parent component isn't the app.component.ts this may be causing an issue? I'm not sure.
Any ideas?
Thanks
The problem here is that the emit can only talk directly to it's parent component.
Because of this, I found a very useful question and answer here which explained Service events and how to communicate with deep-level components using a service like this:
Global Events in Angular 2

Categories