MS Fabric DetailList with input box tabIndex always renders to -1 - spfx

I am adding a input box to a column in a Microsoft Fabric DeatialsList. I set it in the onRender of the IColumn definition. All works fine except the tabIndex is alway -1. I would like to set it to 0.
export default class Scrach extends React.Component<IScrachProps, {}> {
private testData:{}[]=[{Item:1},
{Item:1},
{Item:1}];
private testCols: IColumn[] = [
{key:"col1",
name:"Items",
fieldName:"Item",
onRender:(item)=>{return(<input style={{width:'3em'}}
tabIndex={0}
type="text"
defaultValue="1"
onClick={this.selectText}/>);},
minWidth:60,
isResizable:false
}];
public render(): React.ReactElement<IScrachProps> {
return (
<div>
<DetailsList
items={this.testData}
columns={this.testCols}
setKey="set"
layoutMode={DetailsListLayoutMode.fixedColumns}
selectionPreservedOnEmptyClick={true}
selectionMode={SelectionMode.none}
/>
</div>
);
}
}
Any ideas?

Try to set data-is-focusable="true" in the column onRender instead of tabIndex.

Related

How to bind a list of objects in ASP.NET Core MVC when the posted list is smaller than original?

Using "disabled" attribute on inputs on form does not post them, which is expected and wanted. However, if you prepare a form of 3 objects in a list, disable the first and third, and submit, the 2nd object appears in post header, but does not bind to the list correctly, because it has an index [1] instead of [0].
I understand how model binding works and why it does not bind the posted object that I want, but I don't know how else to describe the problem to get specific results that would lead me to my solution. Anything I search for leads to basic post and binding examples.
List inside the model I'm using:
public IList<_Result> Results { get; set; }
Class _Result has one of the properties:
public string Value { get; set; }
I fill up the list and use it in view like so:
#for (int i = 0; i < Model.Results.Count; i++)
{
...
<td>
<input asp-for="Results[i].Value" disabled />
</td>
...
}
I have checkboxes on form, which remove (with javascript) the "disabled" attribute from the inputs and thus allow them to be posted.
When I fill up the said list with 3 _Result objects, they are shown on form and all have the "disabled" attribute. If I remove the "disabled" attribute from the first two objects and click on submit button, I receive the Results list with first 2 _Result objects, which is as expected.
However, if I remove the "disabled" attribute only from the second _Result object (the first _Result object still has "disabled" attribute), the Results list comes back empty in my Controller method.
In my Form Data Header, I see this: "Results[1].Value: Value that I want posted", which means that post occurs, but list does not bind the object due to the index.
Any idea on how I can achieve that proper binding? Also, the reason I'm using "disabled" attribute is because I'm showing many results on a single page and want to only post those that are selected.
For getting selected items, you could try checkbox with View Model instead of using jquery to control the disable property.
Change ViewModel
public class ModelBindVM
{
public IList<_ResultVM> Results { get; set; }
}
public class _ResultVM
{
public bool IsSelected { get; set; }
public string Value { get; set; }
}
Controller
[HttpGet]
public IActionResult ModelBindTest()
{
ModelBindVM model = new ModelBindVM
{
Results = new List<_ResultVM>() {
new _ResultVM{ Value = "T1" },
new _ResultVM{ Value = "T2" },
new _ResultVM{ Value = "T3" }
}
};
return View(model);
}
[HttpPost]
public IActionResult ModelBindTest(ModelBindVM modelBind)
{
return View();
}
View
<div class="row">
<div class="col-md-4">
<form asp-action="ModelBindTest">
#for (int i = 0; i < Model.Results.Count; i++)
{
<input type="checkbox" asp-for="Results[i].IsSelected" />
<label asp-for="#Model.Results[i].IsSelected">#Model.Results[i].Value</label>
<input type="hidden" asp-for="#Model.Results[i].Value" />
}
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>

Angular2 - Custom validator using old data

I'm having an issue with a custom validator that I've written. The validator is used to validate all the data in a component.
Here's the setup:
The component displays a table with X rows of data. Some of the rows are display-only mode and others will be in edit mode (certain cells in the table will have inputs, selects, etc). Backing the table is an array of data objects (tableData).
If a row is in edit mode, each input is two-way bound to the appropriate element in the tableData array.
My custom validator is applied to the form tag and takes tableData as input. Everything mostly works. The validation data looks at each row in the table and does everything I need it to do.
The issue is that the data based to my custom validator is old data. So if a row is in edit mode, when I change a value in a select, tableData is updated, but the version of it that's passed to the validator is before the update. So I'm always validating an old version of tableData.
I'm not sure how to get the validation to use the up to date version of tableData. I think the issue may be related to the fact that the select binding changes a value of an object in the tableData array, but the tableData array itself doesn't actually change.
I tried adding callback to the (change) event on the select in the row being edited. The method called on (change) manually triggers change detection using a ChangeDetectorRef, but that didn't work.
I don't want to spam everyone with all the entire files, so I've tried to just add the important snippets.
Here's the template:
<form #f="ngForm" novalidate custom-validator="{{tableData | json}}">
<p-dataTable [value]="tableData">
...
<p-column [header]="'Program Name'">
<template let-row="rowData" let-idx="rowIndex" pTemplate type="body">
<span *ngIf="!row['edit']">
{{row['data'].programName}}
</span>
<div *ngIf="row['edit']">
<select #progName="ngModel" [(ngModel)]="row['data'].programCode"
title="Select Program" required (change)="onProgramChange($event, idx)"
name="programSelect-{{idx}}">
<option [value]=""></option>
<option *ngFor="let prog of programList" [value]="prog.code">
{{prog.name}}
</option>
</select>
</div>
</template>
</p-column>
...
</p-dataTable>
</form>
Here's the backing component:
//imports...
...
private tableData: PersonAssignmentRowData[] = [];
private programList: Program[] = [];
...
onProgramChange(event: any, index: number) {
for(let prog of this.programList) {
if(prog.code == event.target.value) {
this.tableData[index].data.programAcronym = prog.acronym;
this.tableData[index].data.programLocation = prog.location;
this.tableData[index].data.programName = prog.name;
break;
}
}
}
...
Here's the validator:
#Directive({
selector: '[custom-validator]',
providers:[{provide: NG_VALIDATORS, useExisting: CustomValidator, multi: true}]
})
export class CustomValidator implements Validator{
#Input('custom-validator') tableDataString: string;
validate(control: AbstractControl) {
if(this.tableDataString == null || this.tableDataString.length == 0) {
return null;
}
let tableData: PersonAssignmentRowData[] = [];
tableData = JSON.parse(this.tableDataString);
let message: string = '';
//logic that tests the validity of the data and sets any error messages in the message variable
if(message.length > 0) {
return {'validationMessage': message};
} else {
return null;
}
}
}
Now it's clear. Of course it will not work. The only data source that the validator should check is a control that's passed to validate() method. No #Inputs() or anything of the kind. The only difference is that control.value below will contain all values of all controls in the form, not just your table, so you should pick the correct nested value where your table is.
#Directive({
selector: '[custom-validator]',
providers:[{provide: NG_VALIDATORS, useExisting: forwardRef(() => CustomValidator), multi: true}]
})
export class CustomValidator implements Validator {
validate(control: AbstractControl) {
tableData = control.table.value; // need to figure out exact path based on your form structure
let message: string = '';
//logic that tests the validity of the data and sets any error messages in the message variable
if(message.length > 0) {
return {'validationMessage': message};
}
return null;
}
}

how to combine checkbox with text input in reactjs

i am trying to build a Ui component in Reactjs which combines a checkbox and a text input attched to it (instead of a text label) so that if the checkbox is checked , the user can change the text input , and if its unchecked the user will not be able to do so
the final goal is to render outside of the component all of textinputs valus which left checked as a list or as a menu item.
Its should look like this :
Checkbox with Text input
anyone knows how should i do this ? im new to reactjs and got a bit confused how to pass logic between two components(as in here between the checkbox and the text input and between the "combo" component and the outer rendered list) .
thanks in advance !
EDIT1:
well i managed to build the component but i cant make the children call the parent handler (handlerCheckbox , handlerInput)in order to actually make the magic happen.
anything im doing wrong ?
this is the child:
class CheckboxTxtInput extends React.Component{
constructor(props){
super(props);
console.log(props.isChecked)
}
handleCheckboxChild(e) {
this.props.handleCheckbox(e,this.props.id)
}
handleInputChild(e){
this.props.handleInput(e,this.props.id)
}
render(){
return (
<div>
<input type="checkbox" onChange={this.handleCheckboxChild} defaultChecked={this.props.isChecked} />
<input type="text" value={this.props.inputValue} disabled={!this.props.isChecked} onChange={this.handleInputChild}/>
</div>
)
}
}
This is the parent:
export default class Text extends React.Component {
constructor(props) {
super(props);
this.state = {
textItems: [{id:0,inputValue:'text',isChecked:true},{id:1,inputValue:'text',isChecked:true}
,{id:2,inputValue:'text',isChecked:true},{id:3,inputValue:'text',isChecked:true}]
};
this.handleCheckbox = this.handleCheckbox.bind(this);
this.handleInput= this.handleInput.bind(this);
}
handleCheckbox(e,id) {
var stateCopy = Object.assign({}, this.state);
stateCopy.textItems[id].isChecked = e.target.value;
this.setState(stateCopy);
}
handleInput(e,id){
var stateCopy = Object.assign({}, this.state);
stateCopy.textItems[id].text = e.target.value;
this.setState(stateCopy);
}
render () {
return (
<div>
<hr className="divider-long"/>
<UI.sectionDividerLabeled label="Show/Hide Text"/>
<hr className="divider-long"/>
<p>Here you can show\hide your text</p>
<div>
<CheckboxTxtInput id={this.state.textItems[0].id} isChecked={this.state.textItems[0].isChecked}
inputValue={this.state.textItems[0].inputValue} handleInput={this.handleInput}
handleCheckbox={this.handleCheckbox} />
<CheckboxTxtInput id={this.state.textItems[1].id} isChecked={this.state.textItems[1].isChecked}
inputValue={this.state.textItems[1].inputValue} handleInput={this.handleInput}
handleCheckbox={this.handleCheckbox}/>
<CheckboxTxtInput id={this.state.textItems[2].id} isChecked={this.state.textItems[2].isChecked}
inputValue={this.state.textItems[2].inputValue}
handleInput={this.handleInput} handleCheckbox={this.handleCheckbox}/>
<CheckboxTxtInput id={this.state.textItems[3].id} isChecked={this.state.textItems[3].isChecked}
inputValue={this.state.textItems[3].inputValue} handleInput={this.handleInput}
handleCheckbox={this.handleCheckbox}/>
</div>
<RenderText />
</div>
)
}
}
The simplest, React-like way to do this is to have a parent wrapper component - say LabeledCheckbox which contains your Text input and your Checkbox components.
When either of the child components do something, they call a callback provided by the parent, and the parent maintains the state for the two components, passing that state down into the props of both children.
The children in this case would never maintain their own state, instead simply calling callbacks and being prop-fed.
Create one component with checkbox and input field with the state of the checkbox and text field.
And then you can reuse it where you want.
You can do something like this :
class CheckboxTxtInput extends React.Component{
constructor(){
super();
this.state = {
checkbox: false,
inputValue: ""
}
}
handleCheckbox(e){
this.setState({checkbox: e.target.checked})
}
handleInput(e){
this.setState({inputValue: e.target.value})
}
render(){
return (
<div>
<input type="checkbox" onChange={this.handleCheckbox.bind(this)} checked={this.state.checkbox}/>
<input type="text" value={this.state.inputValue} disabled={this.state.checkbox} onChange={this.handleInput.bind(this)}/>
</div>
)
}
}
class Test extends React.Component {
render(){
return (
<div><CheckboxTxtInput /></div>
)
}
}
React.render(<Test />, document.getElementById('container'));
Here is the fiddle.
Hope this helps.

Correct way to bind an mvc3 radiobutton to a model

I have a view that contains a radiobutton list for my terms and conditions of the site.
e.g.
Yes
#Html.RadioButtonFor(model => model.TermsAndConditions, "True")
No
#Html.RadioButtonFor(model => model.TermsAndConditions, "False",
new { Checked = "checked" })
</div>
#Html.ValidationStyledMessageFor(model => model.TermsAndConditions)
All is ok if the user completes the form without any errors however if I do serverside validation and the page is refreshed I lose the selection that the user made for the radiobutton and the selected radio goes back to the default false field.
How am I meant to be binding the radiobutton so if a user selects true this value is maintained even after serverside validation?
Any suggestions would be great!
For the short answer, you need to do three things:
Remove the new { Checked = "checked" } from the second radio button. This hard-coded checked value will override all of the magic.
When you return your ViewResult from the controller action, give it an instance of your model class where TermsAndConditions is false. This will provide the default false value you need in order to have the false radio button preselected for you.
Use true and false as the values for your radio buttons instead of "True" and "False". This is because your property is of type bool. Strictly speaking, you coincidentally chose the correct string representations for true and false, but the value parameter for the RadioButtonFor method is of type object. It's best to pass in the actual type you want to compare to rather than converting it to a string yourself. More on this below.
Here's what's going on in depth:
The framework wants to do all of this for you automatically, but you did those first two things incorrectly which makes you have to fight with the framework to get the behavior you want.
The RadioButtonFor method calls .ToString() on the value of the property you specified and compares it to the .ToString() of the value you passed in when creating the radio button. If they are equal, then it internally sets isChecked = true and ends up rendering checked="checked" in the HTML. This is how it decides which radio button to check. It simply compares the value of the radio button to the value of the property and checks the one that matches.
You can render radio buttons for pretty much any property this way and it will magically work. Strings, ints, and even enum types all work! Any object that has a ToString method that returns a string which uniquely represents the object's value will work. You just have to make sure you're settings the radio button's value to a value that your property might actually have. The easiest way to do this is just to pass in the value itself, not the string representation of the value. Let the framework convert it to a string for you.
(Since you happened to pass in the correct string representations of true and false, then those values will work as long as you fix your two actual mistakes, but it's still wise to pass in the actual values and not their strings.)
Your first real mistake was hard-coding Checked = "checked" for the "No" radio button. This will override what the framework is trying to do for you and results in this radio button always being checked.
Obviously you want the "No" radio button to be preselected, but you have to do it in a way that's compatible with everything above. You need to give the view an instance of your model class where TermsAndConditions is set to false, and let it "bind" that to the radio buttons. Normally, a controller action which responds to the initial GET request of a URL doesn't give the View an instance of the model class at all. Typically, you just return View();. However, since you want a default value selected, you must provide the view with a instance of your model that has TermsAndConditions set to false.
Here is some source code illustrating all of this:
Some sort of Account class that you probably already have. (Your View's model):
public class Account
{
public bool TermsAndConditions { get; set; }
//other properties here.
}
Some methods in your controller:
//This handles the initial GET request.
public ActionResult CreateAccount()
{
//this default instance will be used to pre-populate the form, making the "No" radio button checked.
var account = new Account
{
TermsAndConditions = false
};
return View( account );
}
//This handles the POST request.
[HttpPost]
public ActionResult CreateAccount( Account account )
{
if ( account.TermsAndConditions )
{
//TODO: Other validation, and create the account.
return RedirectToAction( "Welcome" );
}
else
{
ModelState.AddModelError( "TermsAndConditionsAgreement", "You must agree to the Terms and Conditions." );
return View( account );
}
}
//Something to redirect to.
public ActionResult Welcome()
{
return View();
}
The entire View:
#model Account
#{
ViewBag.Title = "Create Account";
}
#using ( Html.BeginForm() )
{
<div>
<span>Do you agree to the Terms and Conditions?</span>
<br />
#Html.RadioButtonFor( model => model.TermsAndConditions, true, new { id = "TermsAndConditions_true" } )
<label for="TermsAndConditions_true">Yes</label>
<br />
#Html.RadioButtonFor( model => model.TermsAndConditions, false, new { id = "TermsAndConditions_false" } )
<label for="TermsAndConditions_false">No</label>
<br />
#Html.ValidationMessage( "TermsAndConditionsAgreement" )
</div>
<div>
<input id="CreateAccount" type="submit" name="submit" value="Create Account" />
</div>
}
BONUS: You'll notice that I added a little extra feature to the radio buttons. Rather than just use plain text for the radio button labels, I used the HTML label element with the for attribute set to the IDs of the each radio button. This lets users click on the label to select the radio button instead of having to click on the radio button itself. This is standard HTML. For this to work I had to set manual IDs on the radio buttons, otherwise they would both get the same ID of just "TermsAndConditions", which wouldn't work.
There are a few things you need to do here in order to ensure the user's selection is maintained after server side validation.
a) Bind the "checked" property of each radio to your model in the view, for example:
Yes
#Html.RadioButtonFor(model => model.TermsAndConditions, "True", model.TermsAndConditions == true ? new { Checked = "checked" } : null)
No
#Html.RadioButtonFor(model => model.TermsAndConditions, "False", model.TermsAndConditions == false ? new { Checked = "checked" } : null)
b) To define the initial default value when the view is first displayed, initialise the model returned to the view in the GET request (in the controller action), for example:
public ActionResult SomeForm()
{
return View(new SomeModel { TermsAndConditions = false });
}
b) Ensure in your [HttpPost] controller action that you return the model when the validation fails, for example:
[HttpPost]
public ActionResult SomeForm(SomeModel model)
{
if (!ModelState.IsValid)
return View(model);
// Do other stuff here
}
This way when the view is rendered in the response after validation fails, it will have the actual model state that was passed in (thus maintaining the user's selection).
I can't really tell since you haven't shown your code, but I suspect that if you're failing on server side validation you're just returning the raw view. When it fails, you need to populate the view with the model that was submitted, same as if you were returning any other validation errors. Otherwise you'll get the default model values (which will always be false for the registration boolean).
Maybe you could post your server side code?
Here I am offering another more complex example.
public enum UserCommunicationOptions
{
IPreferEmailAndSMS = 1,
IPreferEmail = 2,
IPreferSMS = 3
}
Html
#model UserProfileView
// Some other code
<div class="form-group">
<label class="col-lg-2 control-label">Communication</label>
<div class="col-lg-10">
<div class=" col-xs-">
#if (Model.UserCommunicationOption.ToString() == UserCommunicationOptions.IPreferEmailAndSMS.ToString())
{
#Html.RadioButtonFor(x => x.UserCommunicationOption, (int)UserCommunicationOptions.IPreferEmailAndSMS, new { #checked = "checked" })
}
else
{
#Html.RadioButtonFor(x => x.UserCommunicationOption, (int)UserCommunicationOptions.IPreferEmailAndSMS)
}
<label class=" control-label" for="#Model.UserCommunicationOption">I Prefer Email And SMS</label>
</div>
<div class=" col-xs-">
#if (Model.UserCommunicationOption.ToString() == UserCommunicationOptions.IPreferEmail.ToString())
{
#Html.RadioButtonFor(x => x.UserCommunicationOption, (int)UserCommunicationOptions.IPreferEmail, new { #checked = "checked" })
}
else
{
#Html.RadioButtonFor(x => x.UserCommunicationOption, (int)UserCommunicationOptions.IPreferEmail)
}
<label class=" control-label" for="#Model.UserCommunicationOption">I Prefer Email</label>
</div>
<div class=" col-xs-">
#if (Model.UserCommunicationOption.ToString() == UserCommunicationOptions.IPreferSMS.ToString())
{
#Html.RadioButtonFor(x => x.UserCommunicationOption, (int)UserCommunicationOptions.IPreferSMS, new { #checked = "checked" })
}
else
{
#Html.RadioButtonFor(x => x.UserCommunicationOption, (int)UserCommunicationOptions.IPreferSMS)
}
<label class=" control-label" for="#Model.UserCommunicationOption">#DLMModelEntities.Properties.Resource.IPreferSMS</label>
</div>
</div>
</div>
Model
[Required(ErrorMessageResourceName = "Communications", ErrorMessageResourceType = typeof(Resource))]
[Display(Name = "Communications", ResourceType = typeof(DLMModelEntities.Properties.Resource))]
public UserCommunicationOptions UserCommunicationOption { get; set; }
GET
var client = AppModel.Clients.Single(x => x.Id == clientId);
if (Convert.ToBoolean(client.IsEmailMessage) && Convert.ToBoolean(client.IsSMSMessage))
{
model.UserCommunicationOption = UserCommunicationOptions.IPreferEmailAndSMS;
}
else if (Convert.ToBoolean(client.IsEmailMessage))
{
model.UserCommunicationOption = UserCommunicationOptions.IPreferEmail;
}
else if ( Convert.ToBoolean(client.IsSMSMessage))
{
model.UserCommunicationOption = UserCommunicationOptions.IPreferSMS;
}
POST
[HttpPost]
public ActionResult MyProfile(UserProfileView model)
{
// Some code
var client = AppModel.Clients.Single(x => x.Id == clientId);
if (model.UserCommunicationOption == UserCommunicationOptions.IPreferEmail)
{
client.IsSMSMessage = false;
client.IsEmailMessage = true;
}
else if (model.UserCommunicationOption == UserCommunicationOptions.IPreferEmailAndSMS)
{
client.IsSMSMessage = true;
client.IsEmailMessage = true;
}
else if (model.UserCommunicationOption == UserCommunicationOptions.IPreferSMS)
{
client.IsSMSMessage = true;
client.IsEmailMessage = false;
}
AppModel.SaveChanges();
//Some code
}
Database
Webpage
I had a similar issue and solved the problem by setting a ViewData value in controller to keep track of what the user had selected.

ASP.NET MVC2 validation not working with drop down list in IE <8

I have a form with a dropdownlist rendered using Html.DropDownListFor(...). The view model field that corresponds with the dropdown list has a [Required(...)] attribute attached to it. This works fine on my local machine, but as soon as I publish to our development server, the drop down lists keep displaying the required error message, even when a value is selected in the list. This only happens in IE - Firefox submits just fine.
Any thoughts?
Relevant code
View:
<ol class="form">
<li>
<%= Html.LabelFor(x => x.ContactTitle) %>
<%= Html.DropDownListFor(x=>x.ContactTitle, Model.GetTitleOptions()) %>
<%= Html.ValidationMessageFor(x => x.ContactTitle) %>
</li>
<!-- more fields... -->
</ol>
View Model:
[Required(ErrorMessage = "Title is required")]
[DisplayName("Title")]
public string ContactTitle { get; set; }
// ...
public SelectList GetTitleOptions()
{
return new SelectList(new string[]
{
"","Dr.", "Mr.", "Ms.", "Mrs.", "Miss"
});
}
It's all pretty basic stuff... I'm at a loss.
Edit: Just discovered this bug is limited to IE 8 compatibility view (and maybe prior versions). IE 8 in standards mode works as expected...
Chalk this one up to stupidity. The code in the example produces output similar to the following:
<select>
<option></option>
<option>Dr.</option>
<option>Mr.</option>
<option>Ms.</option>
<option>Mrs.</option>
<option>Miss</option>
</select>
And the relevant MVC validation function (when a RequiredAttribute is applied to a property that corresponds to a drop down list) is:
Sys.Mvc.RequiredValidator._validateSelectInput = function Sys_Mvc_RequiredValidator$_validateSelectInput(optionElements) {
/// <param name="optionElements" type="DOMElementCollection">
/// </param>
/// <returns type="Object"></returns>
for (var i = 0; i < optionElements.length; i++) {
var element = optionElements[i];
if (element.selected) {
if (!Sys.Mvc._validationUtil.stringIsNullOrEmpty(element.value)) {
return true;
}
}
}
return false;
}
Notice the function checks element.value. In the case of the html above, the value attribute is empty because there is no value attribute on the option elements. Therefore, the validation function returns false and the error occurs. This only appears to happen in IE <8, presumably because other browsers by default assign an option element's text to the value attribute if none is specified.
The solution was to modify the way I was returning the select list items from which the drop down list was built like so:
public IEnumerable<SelectListItem> GetTitleOptions()
{
return BuildSelectListItems(new string[]
{
"","Dr.", "Mr.", "Ms.", "Mrs.", "Miss"
});
}
private List<SelectListItem> BuildSelectListItems(IEnumerable<string> values) {
return (from v in values
select new SelectListItem()
{
Text = v,
Value = v
}).ToList();
}
This results in the much more predictable HTML output:
<select>
<option value=""></option>
<option value="Dr.">Dr.</option>
<option value="Mr.">Mr.</option>
<option value="Ms.">Ms.</option>
<option value="Mrs.">Mrs.</option>
<option value="Miss">Miss</option>
</select>
which of course the function validates properly.

Resources