locale is in my redux app state. Changing its value through react-devtools (revert option), changes paragraph inner value but not the select box value.
If it renders again shouldn't it take the same value as inside the p tag?
import React, {Component, PropTypes} from 'react'
import {defineMessages, injectIntl, intlShape} from 'react-intl'
const messages = defineMessages({
spanish: {
id: 'languageSelector.spanish',
description: 'Select language',
defaultMessage: 'Spanish'
},
english: {
id: 'languageSelector.english',
description: 'Select language',
defaultMessage: 'English'
},
french: {
id: 'languageSelector.french',
description: 'Select language',
defaultMessage: 'French'
}
})
class LanguageSelector extends Component {
render () {
const {formatMessage, locale} = this.props.intl
return (
<div>
<select defaultValue={locale} onChange={(e) => this.handleChange(e)}>
<option id='es' value='es'>{formatMessage(messages.spanish)}</option>
<option id='fr' value='fr'>{formatMessage(messages.french)}</option>
<option id='en' value='en'>{formatMessage(messages.english)}</option>
</select>
<p>{locale}</p>
</div>
)
}
handleChange (e) {
this.props.onChange(e.target.value)
}
}
LanguageSelector.propTypes = {
intl: intlShape.isRequired,
onChange: PropTypes.func.isRequired
}
export default injectIntl(LanguageSelector)
Change defaultValue to value. i.e.
<select value={locale} onChange={(e) => this.handleChange(e)}>
Explanation
You only use defaultValue when the form field is an uncontrolled component. The only way to change the value of an uncontrolled component is through user input.
If you use value then the form component is considered a controlled component. Its value can be changed in subsequent renders by setting the value explicitly. Controlled components must also have an onChange handler, which yours does.
For more information on controlled/uncontrolled form components, see Forms in React.
Related
I'm new in webcomponents with stenciljs, I'm testing creating a select, the idea with this code create and render the select:
<rhx-select label-text="A select web component">
<rhx-select-item value="1" text="option 1"/>
<rhx-select-item value="2" text="option 2"/>
</rhx-select>
The problem i have is how can i get the tags that inside my web component?
this is my code:
import { Component, h, Prop, } from '#stencil/core';
#Component({
tag: 'rhx-select',
styleUrl: 'select.css',
shadow: true,
})
export class RhxSelect {
#Prop() labelText: string = 'select-rhx';
#Prop() id: string;
#Element() el: HTMLElement;
renderOptions() {
let data = Array.from(this.el.querySelectorAll('rhx-select-item'));
return data.map((e) =>{
<option value={e.attributes.getNamedItem('value').value}>{e.attributes.getNamedItem('text').value}</option>
});
}
render(){
return (
<div>
<label htmlFor={this.id}>
{this.labelText}
</label>
<select id={this.id} class="rhx-select">
{this.renderOptions()}
</select>
</div>
)
}
}
Thank you for your time.
If you add the #Element() decorator you can parse the children with vanilla JS:
getItems() {
return Array.from(this.el.querySelectorAll('rhx-select-item'));
}
You can then use those elements and their properties/attributes however you want, for example to generate a list of <option> elements.
A good example is ion-select which gets the children in the childOpts() getter function.
A couple things to keep in mind:
You'll probably want to hide the items with display: none
If the options might change after the initial load you'll need to listen for those changes. Ionic uses the watchForOptions function.
this.el.querySelectorAll won't return any elements until after the component has rendered once, so that its children are available in the DOM. Therefore you will have to use something like the componentDidLoad hook:
export class RhxSelect {
// ...
#State()
items: HTMLRhxSelectItemElement[] = [];
componentDidLoad() {
this.items = Array.from(this.el.querySelectorAll('rhx-select-item'));
}
render() {
return (
<div>
<label htmlFor={this.id}>
{this.labelText}
</label>
<select id={this.id} class="rhx-select">
{this.items.map(item => (
<option value={item.getAttribute('value')}>{item.getAttribute('text')}</option>
))}
</select>
</div>
)
}
}
Note however that componentDidLoad is only executed once, after the component has loaded. If you want your component to support dynamic changes to the options, then you'll have to use something else, like componentDidRender, but then you'll also have to make sure you don't end up with an infinite render loop. There's also a couple ways to solve this, by combining different lifecycle methods.
See https://stenciljs.com/docs/component-lifecycle for a list of all available lifecycle methods.
I want to develop a custom block that will let the user pick some information from autocomplete. I manage to create the autocomplete component on edit function.
A user can select an item but i don't know how to handle the attribute save.
I'm trying to save the selected item as attribute package_name. I created the onChange function on Autocomplete component but event.target.value is undefined.
Here is my code from block.js
const { __ } = wp.i18n; // Import __() from wp.i18n
const { AlignmentToolbar,
BlockControls,
registerBlockType } = wp.blocks;
const { RichText } = wp.editor;
const { Autocomplete, } =wp.components;
const MyAutocomplete = () => {
const autocompleters = [
{
name: 'fruit',
triggerPrefix: '#',
options: [
{ name: 'Apple', id: 1 },
{ name: 'Orange', id: 2 },
{ name: 'Grapes', id: 3 },
{ name: 'test', id: 4 },
],
getOptionLabel: option => (
<span>
{ option.name }
</span>
),
getOptionKeywords: option => [ option.name ],
isOptionDisabled: option => option.name === 'Grapes',
getOptionCompletion: option => (
<abbr title={ option.name }>{ option.name }</abbr>
),
}
];
function onChangeAuto(newContent){
console.log('autocompletexx '+newContent);
}
function onSelectAuto(event){
console.log(event.target);
console.log( event.target.value);
}
return (
<div>
<Autocomplete completers={ autocompleters }>
{ ( { isExpanded, listBoxId, activeId } ) => (
<div
contentEditable
suppressContentEditableWarning
aria-autocomplete="list"
aria-expanded={ isExpanded }
aria-owns={ listBoxId }
aria-activedescendant={ activeId }
onChange={onChangeAuto }
onSelect={onSelectAuto}
>
</div>
) }
</Autocomplete>
<p class="autocomplete_p">Type # for triggering the autocomplete.</p>
</div>
);
};
registerBlockType( 'residence-gutenberg-block/membership-package-settings', {
title: __( 'Residence - Membership Package Settings' ), // Block title.
icon: 'shield',
category: 'common',
keywords: [
__( 'membership-package-settings' ),
],
attributes:{
package_id:{
type:'string',
select:'p'
},
package_name:{
type:'string',
},
},
edit: function( props ) {
const { attributes: {package_name}, className,setAttributes,isSelected } = props;
return (
<div className={ props.className }>
<form>
<label className="wpresidence_editor_label">Current Package: {package_name}</label>
<MyAutocomplete></MyAutocomplete>
</form>
</div>
);
},
save: function( props ) {
// Rendering in PHP
return null;
},
} );
Passing down onChange, onSelect to the div element won't work, because these attributes are only applicable to the form field elements (as input, select, etc.).
I checked the documentation and the source code and didn't find any details or official approaches for dealing with the case.
However, I'm seeing two possible approaches for getting the selected value:
1. Using Autocomplete onReplace prop
Looking into the Autocomplete's source code, I noticed that onSelect callback invokes onReplace prop with the selected option as array. It may not fit all the cases, but you can give it a try! It may be enough for your case! You can try to add your handler to the onReplace as follows:
<Autocomplete
onReplace={ option => { console.log(option) } }
completers={ autocompleters }>
{ ( { isExpanded, listBoxId, activeId } ) => (
<div
contentEditable
suppressContentEditableWarning
aria-autocomplete="list"
aria-expanded={ isExpanded }
aria-owns={ listBoxId }
aria-activedescendant={ activeId }
/>
) }
</Autocomplete>
2. Listen for <div /> changes manually
You can add onInput, onBlur listeners to the <div />, having an uncontrolled react div component and when the div's value is changed then we can keep the changed value in your parent component's state.
Here's a great discussion, which describes these technique: React.js: onChange event for contentEditable
The good think is that there's already a plugin (based on this discussion) that can do it for you: react-contenteditable.
Firstly you have to convert your <MyAutocomplete /> component to be a statefull (not functional) and then:
import ContentEditable from 'react-contenteditable'
// Boilerplate ...
<Autocomplete completers={ autocompleters }>
{ ( { isExpanded, listBoxId, activeId } ) => (
<ContentEditable
html={this.state.html}
onChange={this.handleChange}
contentEditable
suppressContentEditableWarning
aria-autocomplete="list"
aria-expanded={ isExpanded }
aria-owns={ listBoxId }
aria-activedescendant={ activeId }
/>
) }
</Autocomplete>
Conclusion
I'm surprised that in the Autocomplete's documentation there aren't any details for this case. I guess it's because of the following statement (27.08.2018):
Gutenberg is being developed on GitHub, and you can try an early beta
version today from the plugin repository. Though keep in mind it’s not
fully functional, feature complete, or production ready.
However, one of both mentioned approaches above will help you, until they provide a complete API to work with their components.
I would suggest you to keep wrapping the Wordpress's <Autocomplete /> with your own component - in order to easily refactor your code later, when they release the complete API.
If you have questions, feel free to comment below.
I am developing one application with reactive dynamic angular form. This forms fields are coming from an API request and is generated dynamically.
I really need to add 'required field' asterisk (*) to my form inputs which are required. How to achieve this ?
The below is how my form fields look like.
<ng-container *ngIf="input1.type=='string'" [hidden]="False">
<div>
<mat-form-field>
<input matInput [formControlName]="input1.field_name" type="text" placeholder="{{input1.title}}">
</mat-form-field>
</div>
</ng-container>
My solution by generate new Directive:
ng generate directive directive/mark-asterisk
This is full code of directive:
import {Directive, ElementRef, Input, OnInit} from '#angular/core';
import {FormGroup} from '#angular/forms';
#Directive({
selector: '[appMarkAsterisk]'
})
export class MarkAsteriskDirective implements OnInit {
#Input() formGroup: FormGroup;
#Input() controlName: string;
constructor(private elementRef: ElementRef) {
}
ngOnInit(): void {
const isRequired = this.formGroup.controls[this.controlName]?.errors?.required;
if (isRequired) {
this.elementRef.nativeElement.innerHTML = '*';
}else{
this.elementRef.nativeElement.innerHTML = '';
}
}
}
Now in your HTML form, use it like this:
<label>Company name: <span appMarkAsterisk [formGroup]="formGroup" [controlName]="'companyName'"></span></label>
Html
<span *ngIf = "input1?.required">*</span>
.ts
You want to require a input depending on some conditions. Have a look here
Should be fixed when Angular v13 is released. https://github.com/angular/components/pull/23362
It seems like the actual validators will run from the typescript file but in order to actually get the asterisk you can just edit the html file.
<input matInput [formControlName]="input1.field_name" type="text" [placeholder]="{{input1.title}}" required>
Bind the required attribute to the input to display * on required validation from the reactive form field.
<input formControlName="name" [required]="formGroup.get('name').errors !== null && formGroup.get('name').errors.required">
I was setting the required bit in the FormGroup, so I shouldn't need to set it in the html too. The css class ng-star-inserted was added to the class list, but the star wasn't added. I just added what I think should have been the css:
mat-label.ng-star-inserted::after {
content: " *";
}
EDIT: I looked at what angular does with template forms with the [required]="condition", and it looks like it adds a <span class="ng-star-inserted"> *</span> after the label.
I don't know why adding the css worked for me. Now that I did this test, all the other elements are marked with .ng-star-inserted. all of them inputs/labels/divs/dialogs/icons. So when I put this back in, I get stars everywhere.
Making a directive or using required attribute on the element can be a good temporary solution, but this should be working and will be fixed soon !
Material issue here: https://github.com/angular/components/issues/2574?_pjax=%23js-repo-pjax-container.
A lot of temporary solutions are discussed in the comments. I'll try to update this answer when the bug fix is released.
While we don't have a definitive solution, here is my suggestion:
#Component({
selector: "app-root",
template: `
<form [formGroup]="formGroup">
<mat-form-field>
<input
matInput
type="text"
[required]="isRequired(input1.field_name)"
[placeholder]="input1.title"
[formControlName]="input1.field_name"
/>
</mat-form-field>
</form>
`,
})
export class AppComponent {
readonly formGroup = new FormGroup({
name: new FormControl("", Validators.required),
});
readonly input1 = { field_name: "name", title: "Name", type: "string" };
isRequired(name: string): boolean {
return this.formGroup.get(name)?.hasValidator(Validators.required) ?? false;
}
}
First create a directive to add the asterisk without messing with input attributes:
import { AfterContentChecked, Directive, Optional } from "#angular/core";
import { AbstractControl } from "#angular/forms";
import { MatFormField, MatInput, MatSelect } from "#angular/material";
/**
* Input/Select into FormField consider Validator.required from reactive form if the [required] attribute is missing in the template
*/
#Directive({
selector: 'mat-form-field:has(input:not([required])), mat-form-field:has(mat-select:not([required]))'
})
export class ReactiveAsteriskDirective implements AfterContentChecked {
constructor(#Optional() private matFormField: MatFormField) { }
ngAfterContentChecked() {
if (this.matFormField) {
const ctrl = this.matFormField._control;
if (ctrl instanceof MatInput || ctrl instanceof MatSelect)
if (ctrl.ngControl)
if (ctrl.ngControl.control)
if (ctrl.ngControl.control.validator)
if (ctrl.ngControl.control.validator({} as AbstractControl))
ctrl.required = ctrl.ngControl.control.validator({} as AbstractControl).required;
}
}
}
Now you might want to make the asterisk appear with red color, since it indicates a required input. To do so, add this to your component CSS:
:host ::ng-deep .mat-placeholder-required {
color: red;
}
I am trying to create a simple select component which takes in some data via attributes and renders the required options. I plan to use this select component inside the template of another component, say PageComponent's template( page.template.html ).
I am binding a variable of PageComponent to the select component using [(ngModel)]. Upon selecting an option, the value of the variable get's updated as expected but how do I set it to point to the first option upon page load without triggering a manual selection on the select component?
Here's the code.
In page.template.html
<select ui-select
[options]="options"
[(ngModel)]="alertType"
[defaultValue]="'Select an alert Type'">
</select>
{{alertType}} <!-- To see the value while debugging -->
In page.component.ts, PageComponent class
alertType:any;
options = [
{id:1, value:"item-1"},
{id:2, value:"item-2"},
{id:3, value:"item-3"}
];
In select.component.ts
import {Component, Input} from '#angular/core'
#Component({
selector: '[ui-select]',
template: `
<option *ngIf="defaultValue" value="-1">{{defaultValue}}</option>
<option *ngFor="let option of options" [value]="option.id">{{option.value}}</option>
`
})
export class SelectComponent {
#Input() options: any;
#Input() defaultValue: string;
}
Right now, the {{alertType}} value initially shows nothing and updates only upon selecting a new option from the select box.
I do understand that it's happening because alertType is set to undefined by default in PageComponent but I can't figure out how to set it to the first option value when the page loads.
Update
The original question has been answered but I had one more question regarding this so updated the question.
In the updated code, the select component accepts a defaultValue custom property from the template and renders a conditional <option> for that default value.
Now, how do I say that if defaultValue is set, alertType should have that value or otherwise it should have the value of the first item in the options list.
P.S - If you have comments about the approach used in building the component, please feel free to add them in answers as it will help me learn.
So you want alertType should be set to the selected value, whether it could be from your provided options or from your defaultValue(first option).
this could be done by using AfterViewInit hook,
the modified code will be..
#Component({
selector: 'my-app',
directives:[
SelectComponent
],
template : `<select ui-select
[options]="options"
[(ngModel)]="alertType"
[defaultValue]="'Select an alert Type'" #mySelect>
</select>
{{alertType}}`
})
export class App implements AfterViewInit{
#ViewChild('mySelect', {read: ViewContainerRef}) mySelect;
options = [
{id: 1, value: "item-1", selected: false},
{id: 2, value: "item-2", selected: true},//if all options will be false then defaultValue will be selected
{id: 3, value: "item-3", selected: false}
];
alertType: any;
ngAfterViewInit() {
this.alertType = this.mySelect.element.nativeElement.value;
}
}
I have created a plunker, check this!!
You could try it like so:
options = [
{id:1, value:"item-1"},
{id:2, value:"item-2"},
{id:3, value:"item-3"}
];
alertType: any = options[0].id;
For your new question, you could leverage the selected attribute in your ui-select component:
<option *ngIf="defaultValue" selected="true" value="-1">{{defaultValue}}</option>
I'm really new to ReactJS and trying to work with Material-UI components on a new Meteor app I'm working with. A classic use case has come to my needs: a list of items changes the UI when the user selects or not some ListItem. Surprisingly, I found that React isn't easy with parent-child component relations like that.
I tried to follow the Material-UI Docs, implementing SelectableList component like the docs suggests using the SelectableContainerEnhance class. Then I went this way:
const {ListItem, Avatar, Divider} = mui;
App = React.createClass({
mixins: [ReactMeteorData],
getMeteorData() {
return {
players: Players.find({}, { sort: { score: -1 } }).fetch()
}
},
render() {
return (
<SelectableList subheader="Players list">
{this.data.players.map((player) => {
return (
React.Children.toArray([
<Divider />,
<ListItem
value={player._id}
primaryText={player.name}
secondaryText={player.score}
leftAvatar={<Avatar>{player.name}</Avatar>} />
])
);
})}
</SelectableList>
<Divider />
{ true /* What to do now? */ ?
(<span>Thanks!</span>) :
(<span>Click a player to select</span>)}
);
}
});
Ok, the list items has become selectable. But how to know if any ListItem is selected? And how to get the value and adjust the UI according to it?
They talk about setting up a valueLink in the documentation.
<SelectableList
subheader="Players List"
valueLink={{
value: this.state.selectedIndex,
requestChange: this.handleUpdateSelectedIndex
}}>
And then define a handleUpdateSelectedIndex to set the state:
getInitialState() {
return {selectedIndex: 1};
},
handleUpdateSelectedIndex(e, index) {
this.setState({
selectedIndex: index,
});
},
This will give you this.state.selectedIndex on your App component that you can do whatever you need to do with it.