I am currently using Bootstrap V3 and MVC4.
I am having issues accessing and formatting data using bootstrap defined radio buttons.
When I declare my radio buttons using the following - Specifically data-toggle="buttons":
<div class="btn-group" data-toggle="buttons" style="padding-bottom:10px">
<label class="btn btn-primary">
#Html.RadioButtonFor(model => model.searchType, "radiobutton1") RadioButton 1
</label>
<label class="btn btn-primary">
#Html.RadioButtonFor(model => model.searchType, "radiobutton2") RadioButton2
</label>
</div>
The result is:
Everything looks perfect. When I select a button, it gets pressed until I select the other.
However, when I submit the form, the value is not passed through to the controller. It is null. I have a strongly typed view that gets values from a textbox and the radio buttons on the page.
When I define my radio buttons using the following - Specifically data-toggle="buttons-radio":
<div class="btn-group" data-toggle="buttons-radio" style="padding-bottom:10px">
<label class="btn btn-primary">
#Html.RadioButtonFor(model => model.searchType, "radiobutton1") RadioButton 1
</label>
<label class="btn btn-primary">
#Html.RadioButtonFor(model => model.searchType, "radiobutton2") RadioButton2
</label>
</div>
My result is
This is not how I want my buttons. The little circles are visible. Not to mention, once I select one of the buttons there is no turning back. I am forced to keep that one, I cannot select the other option.
The one good thing about this option is that I am able to pass in the value that is selected to the controller. When the form is submitted, I am able to see that the value of searchType in my model object is either Radiobutton1 or Radiobutton2.
And for those wondering, this is what my model looks like. It has a spot for searchType and searchString.
public class SearchForm
{
public string searchString{ get; set; }
public string searchType { get; set; }
}
What I am asking is how do I combine the 2 results?
One looks perfect but doesn't pass data, the other looks bad and passes in the date.
I have narrowed it down to the data-toggle property being set to either Buttons or Buttons-Radio.
Actually all you need to do is add a name attribute to each radio button and set it to the model item you want. Then asp.net MVC does its voodoo magic and connects the bits together.
In your case something like the code below should work:
<div class="btn-group" data-toggle="buttons" style="padding-bottom:10px">
<label class="btn btn-primary">
#Html.RadioButtonFor(model => model.searchType, "radiobutton1", new {name="searchType"}) RadioButton 1
</label>
<label class="btn btn-primary">
#Html.RadioButtonFor(model => model.searchType, "radiobutton2", new {name="searchType"}) RadioButton2
</label>
</div>
I have been able to resolve my own issue however I had to take a different approach. However I will not accept this answer as it does not toggle as it is supposed to. The button stays selected until it is out of focus.
Instead of using #Html.RadioButtonFor(...) I went back and I used buttons as my two radio options.
Since when those buttons are clicked, there is no action being taken I had to create a hidden input field, and use javascript to update its value whenever a "radio button" was clicked.
All in all here was my form code:
#Html.HiddenFor(model => model.searchType)
<div class="btn-group" data-toggle="buttons-radio">
<button type="button" id="radiobutton1" data-toggle="button" name="search" value="Radiobutton1" class="btn btn-primary">Radiobutton1</button>
<button type="button" id="radiobutton2" data-toggle="button" name="search" value="radiobutton2" class="btn btn-primary">Radiobutton2</button>
</div>
<script>
var buttons= ['radiobutton1', 'radiobutton2'];
for (var i = 0; i < btns.length; i++) {
document.getElementById(buttons[i]).addEventListener('click', function () {
document.getElementByID('searchType') = this.value;
});
}
</script>
EDIT:
I have solved the entire issue now.
I have removed my old Javascript with this jquery code:
<script type="text/javascript">
$(function () {
$("#searchDiv :button").click(function () {
$("#searchDiv :button").removeClass('active');
$(this).addClass('active');
$("#searchType").val($(this).attr("value"))
});
});
</script>
Buttons now toggle correctly. And the value is passed to the hidden field.
This seems to be a little more complicated than it needed to be, but its all good.
Related
I am working on a row select functionality for an ASP.NET Core project. Basically, a user selects a row in a table, that rows id is passed to javascript where it does some work, then sets an input field in the view to the value of that id, and binds that id to a SelectedId property in my ViewModel. That all seems to work properly. Once the row is selected though I have a couple of buttons that I want to be able to do something with that object (edit, delete, view). But when I try to pass the id from any of the buttons with asp-route-id or data-id, the id passed is always 0. I'm pretty sure that the SelectedId is not actually being changed in the ViewModel even though it is being updated in the input field, but I don't know how to fix that. This is how the code is written.
EmployeeListViewModel
public class EmployeeListViewModel
{
[BindProperty]
public int SelectedId { get; set; }
[BindProperty]
public string SelectedName { get; set; }
[BindProperty]
public IEnumerable<Employee>? EmployeeList { get; set; }
}
Edit and Delete buttons, and the input that receives the id and should also be passing it back to ViewModel's SelectedId property. I put these in a form tag, because I thought it might help, but it didn't. At least not as is.
Employee/ index.cshtml
#model Project.ViewModels.EmployeeListViewModel
...
//table
...
<form>
<div class="buttons">
<div class="w-100 btn-group" >
<input asp-for="SelectedId" class="form-control selectedId"/>
//input box shows correct selected Id
<button class="btn btn-primary mx-2" asp-controller="Employee" asp-action="Edit"
asp-route-id="#Model.SelectedId"> <i class="bi bi-pencil-square"></i>
Edit
</button>
<!--button call to delete modal (js.delete.js)-->
<a class="btn btn-danger delete" id="#delete" data-id="#Model.SelectedId"
data-controller="Employee" data-Action="DeletePOST"
data-body-message="Are you sure you want to delete this employee?">
<i class="bi bi-trash"></i>
Delete
</a>
//neither anchor or button or data-id or asp-route-id pass anything but id=0
</div>
</div>
</form>
EmployeeController.cs (Edit Action being called and passed id=0)
public IActionResult Edit(int? id)
{
//do something with id
}
Any help would be greatly appreciated. Thank you, everybody.
Update to this
I decided to do a little test and initialize the SelectedId in my model to 25, and now when I select a row, I see the input change, but when I press a button, it returns 25. So the problem is 100% that the input or the asp-for taghelper are not updating the value of the SelectedId property in my ViewModel. So, possibly something about this line of code specifically.
<input asp-for="SelectedId" class="form-control selectedId"/>
change your code to :
<a class="btn btn-primary mx-2" asp-controller="Employee" asp-action="Edit"
asp-route-id="#Model.SelectedId"> <i class="bi bi-pencil-square"></i>
Edit
</a>
I am working with Blazor server. I have a ButtonComponent which displays a button and the user can pass in a label as a parameter via a modal window and then click Ok.
Once the user clicks Ok, the label typed in gets displayed on the button.
How can I display multiple components vertically(ButtonComponent)
when the user clicks the Show Button one at a time?
#page "/tester"
<button type="button" #onclick="() => showSignUpComponent = true">
Show Button
</button>
#if (showSignUpComponent)
{
<ButtonComponent label="Search" /> <hr/>
}
#code {
bool showSignUpComponent;
}
Rather than thinking of collections of controls, think of Lists of objects that need to be expressed in markup. So, if you have a collection of Label strings that need buttons, you can do something like:
#page "/btn"
<input #bind="newLabel" />
<button type="button" #onclick="() => Labels.Add(newLabel) ">
Spawn
</button>
#foreach (var label in Labels)
{
#*<ButtonComponent label="#label" />*#
<div>This would be a button with label "#label".</div>
}
#code {
string newLabel { get; set; } = "";
List<string> Labels = new List<string>();
}
You can list any class you want, and swap controls in/out as you need.
I have a dropdown with a search field to filter the list. Here is the basic structure:
<div class="dropdown">
<button class="dropdown-button dropdown-toggle" #onclick="e => this.show = !this.show"></button>
<div class="dropdown-menu #(show ? "show" : "")">
<input class="form-control form-control-sm" placeholder="type filter..."/>
<div class="scrollable-menu">
<table>
...
</table>
</div>
</div>
</div>
How do I hide the dropdown when the user clicks somewhere else?
If I use the onblur event of the button the dropdown gets hidden when the user clicks inside the filter input --> doesnt work.
The dropdown-menu is outside the dropdown div so I can't use that.
It would be ideal if I could group the button and the dropdown list together somehow so that the focusout event only gets triggered when the user clicks outside this "group" of elements.
EDIT
I updated the code snipped to show where how I toggle the dropdown.
The show variable is also inverted when the user selects an element in the list.
The simplest way is to use CSS - hide the scrollable-menu by default, then display it when anything in the dropdown has focus.
.scrollable-menu {
display: none;
}
.dropdown:focus-within .scrollable-menu {
display: block;
}
Edit: Add more complicated Blazor event based version
This problem (which I had not understood fully before) is usually solved in javascript detecting whether the target element of a focus change is within the container, but that then means interop calls to set/update your show field.
A purely Blazor solution could be handled by delaying the hide and cancelling if focus remains inside.
<div class="dropdown">
<button class="dropdown-button dropdown-toggle" #onclick=HandleClick #onfocus=HandleFocus #onblur=HandleBlur ></button>
<div class="dropdown-menu #(show ? "show" : "")" #onfocusin=HandleFocus #onfocusout=HandleBlur tabindex="-1">
<input class="form-control form-control-sm" placeholder="type filter..."/>
<div class="scrollable-menu">
<table>
...
</table>
</div>
</div>
</div>
#code{
bool show;
CancellationTokenSource tokenSource;
void HandleClick() => show = !show;
async Task HandleBlur(FocusEventArgs a)
{
tokenSource = new CancellationTokenSource();
await Task.Factory.StartNew(async ()=> {
await Task.Delay(100);
show = false;
await InvokeAsync(StateHasChanged);
},tokenSource.Token);
}
void HandleFocus(FocusEventArgs a)
{
if (tokenSource is CancellationTokenSource)
tokenSource.Cancel();
}
}
Try it out here: https://blazorrepl.com/repl/wFESlpaa33iocZJR52
use InvokeVoidAsync. It will not work if you make it async.
#onclick="#(e => {
JsRuntime.InvokeVoidAsync("eval",$"bootstrap.Dropdown(yourID).toggle()");
})"
I have a list In my view. For each row, I view button and I am passing Id value as hidden. But when I click any button it is passing wrong hidden value to the controller. Always it passes the first-row hidden value to the controller.
View:
#foreach (var list in Model)
{
<div>
<div > #( ((int)1) + #Model.IndexOf(list)).</div>
<div >#list.details</div>
<div class="col-md-2 row-index">
<button class="btn btn-link" type="submit" name="action:view" id="view">View</button>
<input type="hidden" name="viewId" id="viewId" value="list.WId" />
</div>
</div>
}
Controller:
[HttpPost]
[MultipleButton(Name = "action", Argument = "view")]
public ActionResult ViewDetail(string viewId)
{
return RedirectToAction("ViewDetails");
}
To get all values you need to change the input value type in your controller to array of strings.
I hope that this solution can help you
[HttpPost]
[MultipleButton(Name = "action", Argument = "view")]
public ActionResult ViewDetail(string[] viewId)
{
return RedirectToAction("ViewDetails");
}
if you want to get the exact value you need to duplicate the form within your foreach
in this case you should write somthing like this :
#foreach (var list in Model)
{
<div>
<div > #( ((int)1) + #Model.IndexOf(list)).</div>
<div >#list.details</div>
<div class="col-md-2 row-index">
<form ... > // complete your form attributes
<button class="btn btn-link" type="submit" name="action:view" id="view">View</button>
<input type="hidden" name="viewId" id="viewId" value="list.WId" />
</form>
</div>
</div>
}
Note : You should delete the global form
You should have one form for each row. then you submit that row.
Otherwise as you state it passes first value.
You are setting each value to the same element ID (which is invalid anyway) and name. When you submit your form (which would be more helpful to fully answer your question) it is finding the first element that matches that criteria and submitting it.
There are multiple ways to resolve this such as the already mentioned form per entry but the other preference would be to modify you button to a div and add a click handler to pass the specific value to a js function which would then submit to the controller. Its a preference choice regarding how tightly coupled you want your front end. But the main problem is your element naming convention.
I am trying to automate an environment selection screen where there are multiple selectable buttons individually hidden by a span, these display as tiles.
I have managed to navigate to a given tile and pull up the button but I am unable to click it.
Here is the code I have
public static void NavigateToEnvironment(IWebDriver driver, string environment)
{
IWait<IWebDriver> wait = new WebDriverWait(driver, TimeSpan.FromSeconds(5.00));
wait.Until(ExpectedConditions.ElementIsVisible(By.XPath($"//span[text()='{environment}']")));
var tile = driver.FindElement(By.XPath($"//span[text()='{environment}']"));
Actions action = new Actions(driver);
action.MoveToElement(tile).Perform();
wait.Until(ExpectedConditions.ElementIsVisible(By.XPath($"//*[#span=(text()='{environment}')][#btn=(starts-with(text(), 'Start'))]")));
driver.FindElement(By.XPath($"//*[starts-with(text(), 'Start')]")).Click();
}
The first part successfully moves to the correct tile and opens the span so on screen the button is there.
The wait.until condition is fine too so Selenium can see the element so its the final click command I have an issue with.
It seems only to look for the button hidden within tile one but I am trying tile three. All the buttons have the same HTML tags.
In the current code state I get element not visible.
I have tried to use the xpath as in the wait condition but that returns that the parameters are not elements so again fails.
I am kind of at a loss. Any ideas?
UPDATE:
Some HTML of one of the buttons. This basically repeats with a different application name
<li class="trans tile">
<div class="tileWrap noselect" aria-haspopup="true">
<div class="divNavIcon">
<span class="spnNavIcon primarycolorfont enable" data-bind="css: Code"></span>
</div>
<div class="tilePopup primarycolor">
<span data-bind="text: ApplicationNameAlias ? ApplicationNameAlias : ApplicationName">Enable QA</span>
<span data-bind="text: Description" class="tileSubText">Enable CI Environment</span>
<div class="tilePopupToggle">
<button type="button" data-bind="click: $parent.startApp, css: { disabled: IsRevoked }" class="btn">Start <i class="fa fa-fw fa-desktop"></i></button>
<button type="button" style="display:none;" data-bind="click: $parent.startAppNew, css: { disabled: IsRevoked }" class="btn">Start New <i class="fa fa-fw fa-external-link"></i></button>
<button type="button" style="display:none;" data-bind="attr: { "data-target": "#appPreview_" + ApplicationID }" class="btn" data-toggle="modal" data-target="#appPreview_3043">Preview <i class="fa fa-fw fa-play"></i></button>
</div>
</div>
</div>
Screenshot to help understanding - Each tile acts in the same way with a hidden start button. My code works fine for this first tile but if I want the second or third tiles it cannot find the start button
As per the HTML you have shared to click on the button with text as Start you can use the following code block :
wait.Until(ExpectedConditions.ElementToBeClickable(By.XPath("//div[#class='tilePopup primarycolor']//div[#class='tilePopupToggle']/button[#class='btn' and normalize-space()='Start']/i[#class='fa fa-fw fa-desktop']"))).Click();
Update
Can you try removing the <button> tag as :
wait.Until(ExpectedConditions.ElementToBeClickable(By.XPath("//div[#class='tilePopup primarycolor']//div[#class='tilePopupToggle']//i[#class='fa fa-fw fa-desktop']"))).Click();
Note : As per aurelia/binding/issues/163 disable.bind disables button but inner content is still clickable and we are targeting i[#class='fa fa-fw fa-desktop']
I have managed a pretty elegant work around to this issue. The buttons are contained in li items so i'm just finding the relevant one of those.
public void NavigateToEnvironment(IWebDriver driver, string environment)
{
var tile = driver.FindElement(By.XPath($"//span[text()='{environment}']"),5);
Actions action = new Actions(driver);
action.MoveToElement(tile).Perform();
var tile2 = driver
.FindElement(By.XPath("//*[#id='content']/div/div/div/div/ul"))
.FindElements(By.TagName("li"))
.Where(x => !string.IsNullOrWhiteSpace(x.Text))
.ToList();
var singleTile = tile2.Single(x => x.Text.Contains(environment));
driver.FindElement(By.XPath($"//*[#id='content']/div/div/div/div/ul/li[{tile2.IndexOf(singleTile) + 1}]/div[1]/div[2]/div/button[1]")).Click();
}