reactjs pre-select radio button - reactjs

I have a section in Client`s panel to agree (or not) to receive promo emails.
I set this as a choice of two radio buttons, yes or no.
Technically everything is working just fine, I can store proper information in the DB but the initial state of radio buttons is not showing. The proper value is being sent ot its 'parent' but it seems it's not passed down to the actual form.
What I would like to achieve is the initial radio button being checked (true, false):
The info is being sent (in line 52, [PROMO_CONSENT]: promo_consent):
[PROMO_CONSENT_FORM]: {
label: _t(panelMessages.promos),
data: Promo,
form: {
formId: PROMO_CONSENT_FORM,
data: {
[PROMO_CONSENT]: promo_consent
}
}
}
but then it doesn't seems to show anywhere else. To manage the states of radiobuttons, component FormChoiceGroup is being called component: FormChoiceGroup,.
[PROMO_CONSENT_FORM]: {
formId: [PROMO_CONSENT_FORM],
endpoint: '/accounts/set_promo/',
fields: [
{
name: PROMO_CONSENT,
label: _t(panelMessages.promoLabel),
component: FormChoiceGroup,
type: 'radio',
isRequired: false
}
]
}
It's a lot of code, if anyone feels like going through it, I'd appreciate it.
Block:
Panel main block
Form:
Panel form, the part of consent starts at line 96
ChoiceGroup:
Component to manage radio buttons

I had to send NOT a boolean value, but turn it into a string, to send literal "true" or "false" and not true/false.
I set up a const that checked the status of the variable and then assigned it with literal values:
const val = this.state.formData[field.name] == false ? "false" : "true";
This simple thing did the trick.

Related

Fluent UI DetailsList - Is there a way to add filters to each column

I am using Fluent UI DetailsList. My table looks like below:
I need filters below every column (text or drop-down) as shown below:
Please let me know if this is possible? Or maybe a way to display custom header (using html) ?
This actually turned out to be easier than I thought it'd be...
If you're ok with clicking the column header to reveal the choices (vs having the dropdown directly under the title) then this can be achieved using the ContextualMenu component in conjunction with DetailsList. I got it working by tweaking from the variable row height example in the official docs: https://developer.microsoft.com/en-us/fluentui#/controls/web/detailslist/variablerowheights.
Add a ContextualMenu underneath your DetailsList:
<DetailsList
items={items}
columns={columns}
/>
{this.state.contextualMenuProps && <ContextualMenu {...this.state.contextualMenuProps} />}
Inside your column definition, set the hasDropdown action so the user gets a UI indicator that they can/should click the header, and call a contextMenu method (note I'm using onColumnContextMenu as well as onColumnClick so it doesn't matter if they left or right click the header:
{
key: 'dept',
name: 'Department',
fieldName: 'dept',
minWidth: 125,
maxWidth: 200,
onColumnContextMenu: (column, ev) => {
this.onColumnContextMenu(column, ev);
},
onColumnClick: (ev, column) => {
this.onColumnContextMenu(column, ev);
},
columnActionsMode: ColumnActionsMode.hasDropdown,
}
When the onColumnContextMenu method gets invoked, we need to build the context menu properties that will get consumed by the ContextualMenu component. Note the dismissal method as well, which clears out the state so the menu is hidden.
private onContextualMenuDismissed = (): void => {
this.setState({
contextualMenuProps: undefined,
});
}
private onColumnContextMenu = (column: IColumn, ev: React.MouseEvent<HTMLElement>): void => {
if (column.columnActionsMode !== ColumnActionsMode.disabled) {
this.setState({
contextualMenuProps: this.getContextualMenuProps(ev, column),
});
}
};
Finally, inside of getContextualMenuProps you need to determine what the options should be for the user to click. In this example, I'm simply giving sort options (you'll need to add an onClick handler to actually do something when the user clicks the item), but I'll use the column to determine what those items should actually be and paint the filters into the items collection so the user can select one to filter.
private getContextualMenuProps = (ev: React.MouseEvent<HTMLElement>, column: IColumn): IContextualMenuProps => {
const items: IContextualMenuItem[] = [
{
key: 'aToZ',
name: 'A to Z',
iconProps: { iconName: 'SortUp' },
canCheck: true,
checked: column.isSorted && !column.isSortedDescending,
},
{
key: 'zToA',
name: 'Z to A',
iconProps: { iconName: 'SortDown' },
canCheck: true,
checked: column.isSorted && column.isSortedDescending,
}
];
return {
items: items,
target: ev.currentTarget as HTMLElement,
directionalHint: DirectionalHint.bottomLeftEdge,
gapSpace: 10,
isBeakVisible: true,
onDismiss: this.onContextualMenuDismissed,
}
}
Note the target on the ContextualMenuProps object, which is what tells the ContextualMenu where to lock itself onto (in this case, the column header that you clicked to instantiate the menu.
Detail list filter for each column without context menu -
https://codesandbox.io/s/rajesh-patil74-jzuiy?file=/src/DetailsList.CustomColumns.Example.tsx
For instance - Providing filter in text field associated with each column will apply filter on color column.

How do I stop Office Fabric Dropdown from selecting first element on focus?

I am using the Dropdown from Office Fabric in React. I have many dropdowns that I am happy to select the first element by default, but I have some that I do not want this behavior. When I put a breakpoint in the debugger onDropdownChange() is being called when I click onto the dropdown for the first time.
I was able to trace the event to Dropdown.base.js, where I see this code, which seems to force a focus on the first element if none are selected:
_this._onFocus = function (ev) {
var _a = _this.state, isOpen = _a.isOpen, selectedIndices = _a.selectedIndices;
var multiSelect = _this.props.multiSelect;
var disabled = _this._isDisabled();
if (!disabled) {
if (!isOpen && selectedIndices.length === 0 && !multiSelect) {
// Per aria
_this._moveIndex(ev, 1, 0, -1);
}
if (_this.props.onFocus) {
_this.props.onFocus(ev);
}
_this.setState({ hasFocus: true });
}
};
Here is my dropdown rendering code:
return <Dropdown
selectedKey={selected}
placeholder={placeholder}
ariaLabel={this.props.ariaLabel || this.props.label}
label={this.props.label}
onChange={this.onDropdownChange}
options={this.getDropdownOptions()}
/>
Is there a way that I can get around this? I see others use office fabric and not have this behavior, such as VSO with their Columns Options pane (which is what I am building for my own site), but I don't see what I need to do or if they somehow custom handled it. The only idea I have so far as to put a blank entry as an option and then pre-select it. It is not only that it selects the first entry, but when I click the dropdown of the combo box for the first time it just selects the first option and doesn't pop open the dropdown.
Indeed, this is the default behavior, on focus event the first available dropdown option is getting selected. One option to override this behavior would be:
a) to introduce a default option
<Dropdown componentRef={this.dropdownRef}
styles={dropDownStyles}
onFocus={this.handleDrownDownFocus}
options={[
{ key: "All", text: "", disabled: true },
{ key: "A", text: "Option A" },
{ key: "B", text: "Option B" }
]}
/>
b) and force it to get selected on focus event
private handleDrownDownFocus(ev: React.FormEvent<HTMLDivElement>) {
const dropDown = this.dropdownRef.current;
if(dropDown && !this.selectedDefault){
dropDown.setSelectedIndex(ev,0);
this.selectedDefault = true
}
}
Here is a demo

How to prevent Kendo MultiSelect from losing values after editing in a grid template?

I have a grid that displays a comma-separated list of values, and it has an array that gets used in a template editor for the grid. (On the server, I transform the comma-separated list to an array for the Kendo multiselect AngularJS directive). I have almost everything working: display, edit, and adding values in the multiselect.
There's just one weird thing happening: after I add a value in the multiselect, click Save in the editor, and reopen the editor, the multiselect then only displays one of the most-recently entered values. I know that the values are there and going through the pipeline, because the values make it into the database. I can refresh the page, open the editor, and all the values display in the multiselect correctly, including the one I just added.
It's as if kendo "forgets" most of the values when I reopen the editor. How can this be prevented? Does the MultiSelect need to be rebound to the values? If so, how?
I have tried adding this onChange event, but it had no effect. I've added valuePrimitive to no effect. I tried specifying k-rebind, but it caused an error.
Here's the directive being used in the text/x-kendo-template:
<select kendo-multi-select
id="zipCode"
k-placeholder="'Enter zip codes...'"
style="width: 225px"
k-on-change="dataItem.dirty=true"
k-auto-bind="false"
k-min-length="3"
k-enforce-min-length="true"
k-data-source="options.zipCodeDataSource"
k-data-text-field="'text'"
k-filter="'startsWith'"
k-filtering="options.zipCodeFiltering"
k-no-data-template="'...'"
k-ng-model="dataItem.zipArray"
k-highlight-first="true" />
And this is the DataSource:
options.zipCodeDataSource = new kendo.data.DataSource({
severFiltering: true,
transport: {
read: {
url: serviceUrl + "ZipCode/Get",
type: "GET",
dataType: "json",
contentType: jsonType,
data: function (e) {
// get your widget.
let widget = $('#zipCode').data('kendoMultiSelect');
// get the text input
let filter = widget.input.val();
// what you return here will be in the query string
return {
filter: filter
};
}
},
},
schema: {
data: "data",
total: "Count",
model:
{
id: "text",
fields: {
text: { editable: false, defaultValue: 0 },
}
},
parse: function (response) {
return response;
}
},
error: function (e) {
}
});
If I display {{dataItem.zipArray}} in a <pre> all of the expected values are there.
I wonder if something needs to be added to the edit event handler in the kendo grid definition, but I'm not sure what it would be. I've had to do binding like that for the dropdownlist directive.
edit: function (e) {
if (e.model.marketId === 0) {
e.container.kendoWindow("title", "Add");
} else {
e.container.kendoWindow("title", "Edit");
}
// re-bind multi-select here??
// These two lines actually cause the multiselect to lose pre-existing items in dataItem.zipArray
// var multiselect = kendo.widgetInstance(angular.element('#zipCode'));
// multiselect.trigger('change');
}
...
Update:
This dojo demonstrates the issue.
Run the dojo
Edit the first record in the Contracts grid
Add a zip code such as 22250
Click Save
Then click Edit on the first row again
Only zip code 22250 is displayed in the editor
Also, I notice that if I change k-min-length="3" to k-min-length="1", then the issue goes away. But in the scenario I'm working on, it needs to be 3.
This seems to be an issue with kendo itself. Back then this issue was reported here.
Ok based on the reply from telerik, this issue has been fixed in 2017 R3 SP1 release (2017.3.1018). You can verify it by using this updated dojo example:
http://dojo.telerik.com/IREVAXar/3

ReactJS: where to put validation logic in a form with "nested" composite components?

I'm new to ReactJS and am unsure about the best place to put validation logic that is needed both by nested child components in my form, and the overall "parent" form component itself. Here is a over-simplified example that illustrates my question...
I have a object like this that represents a pet owner:
{
name: 'Jon Arbuckle',
pets: [
{ name: 'Odie', type: 'dog' },
{ name: 'Garfield', type: 'cat' }
]
}
I'm using a composite component called <PetOwnerForm> to render a form for editing this data. <PetOwnerForm> renders something like this:
<input type="text" value={name} />
<PetList value={petOwner.pets} />
<PetList> is a composite component that renders this:
<PetListItem value={this.props.value[i]} /> // Render this for each pet...
// buttons for adding/deleting pets
<PetListItem> renders something like this:
<input type="text" value={this.props.value.name} />
<PetTypePicker value={this.props.value.type} />
Lastly, <PetTypePicker> renders a <select> with <option>s for pet types.
<PetTypePicker> needs to know how to validate the selected type so it can display an inline error message (e.g., ensure that a value is selected).
However, <PetOwnerForm> also needs to know how to validate the pet type because it needs to know how to validate the entire object (on load, each time the form is updated, and before submitting the data back to the server). If any field is invalid, the "Save" button should be disabled.
So where, for example, should the "is a valid pet type selected?" logic go? (Bear in mind that this is a trivial example; in reality I have many fields like this and nested composite components).
The options I see so far are:
A) Replicate the validation logic for pet type (or whatever field) both in <PetOwnerForm> and <PetTypePicker>. This might just be a matter of calling the same, shared validation function in both places:
//PetOwnerForm.js:
validate(petOwnerObj) {
Util.isPetTypeValid(petOwnerObj.pets[i]) // for each pet
// validate the other properties in petOwnerObj...
}
//PetTypePicker.js:
validate(petType) {
Util.isPetTypeValid(petType)
}
B) Use custom PetOwner, Pet, and PetType models that have their own validators. This way you can always ask a model to validate itself, regardless of where it is. Maybe this would look something like this:
{
name: { value: 'Jon Arbuckle', isValid: ()=>{...} },
pets: [
{
name: { value: 'Garfield', isValid: ()=>{...} },
type: { value: 'cat', isValid: ()=>{...} }
},
...
]
}
C) Modify PetOwnerForm.js go recurse the pet owner object, validating each value, and setting an 'errors' property that child components can reference, resulting in an object like this:
{
name: { value: 'Jon Arbuckle asdfasdfasdf^^', errors: ['Too many characters', 'Contains invalid character']] },
pets: [
{
name: { value: '', errors: ['Required value missing'] },
type: { value: 'tree', errors: ['Invalid pet type'] }
},
...
]
}
Which option is recommended for React apps (or is there another option)?
It's a nice elaborate question. This question is not specific to ReactJS applications. It applies to all frameworks that follow component model.
Following are my recommendations:
Differentiate between action driven validation and data format validation.
Low level components are aware of data format they accept, so they must validate for it. For example, postal-code, email, phone, SSN etc have fixed formats and their corresponding components must validate for the right input format.
Low level components are not aware of actions being performed on the overall data. For example, selection of pet-owner-type can be mandatory for "create" pet-owner action but can be optional for "save draft" action. So, low level components which are not aware of end action must not perform action driven validations.
Action driven validation must be performed by the higher level component aware of action, for example PetOwnerForm. Such validation result must be notified to low level components so that they can display appropriate errors. Every low level component must have an error state to support it.

Extending UI-Router-Tabs To Display Form Validation State

I'm pretty new to Angular and having a problem figuring out how to bind a value from a data service to a customization of the https://github.com/rpocklin/ui-router-tabs project that I have created.
The customization is to display an icon in the tab heading that toggles based on the validation state of the corresponding ui-view. I've wired up a data service that is updated when validation succeeds or fails, and the binding is being "seen" by the controller that renders the tabs.
The problem arises when I attempt to pass this value into the tab data as "options". The value change/icon toggle is not being processed as the validation state changes. If I pass a hard-coded "true" into the tab data, the desired icon is displayed.
vm.tabs = [
{
heading: "View 1",
route: "manage.view1",
options: {
status: vm.stateService.getForm1State()
}
},
{
heading: "View 2",
disable: vm.disableTabs,
route: "manage.view2",
options:{
status: true
}
}
];
This doesn't seem like something that should be that difficult, so I think I'm just missing something obvious about the scoping. Here is a plunk http://plnkr.co/edit/iefvwcffSZmpfy83NGde?p=preview that demonstrates the issue.
Note: should be tagged as ui-router-tabs, but I lack the reputation to create the tag.
The problem lies here:
options: {
status: vm.stateService.getForm1State()
}
Since stateService.getForm1State() returns a boolean value, this value will be copied and assigned as the value of the status property.
So when this code has executed once it will for example be:
options: {
status: false
}
The options object won't react to any changes within the stateService.
An easy solution is to have stateService.getForm1State() return an object instead.
For example:
function stateService() {
var form1IsValid = {
status: false
};
return {
getForm1State: getForm1State,
setForm1State: setForm1State
};
function getForm1State() {
return form1IsValid;
}
function setForm1State(newValue) {
form1IsValid.status = newValue;
return;
}
Then make the options object refer to the same object:
options: vm.stateService.getForm1State()
Now whenever you have:
options.status
It will be the same as:
vm.stateService.getForm1State().status
And it will correctly reflect the changes.
Demo: http://plnkr.co/edit/KjXVSSMJbLw7uod0ac6O?p=preview

Resources