recently i am playing around with blazor-pizza shops tutorial.
I think of something to fix here.
Current:
American bacon is my first selection, after i selected it, the dropdownlist is selecting Artichoke hearts
Outcome I want:
Dropdownlist to reset default selecting "(select)" after i select the American bacon
My code like this:
#inject HttpClient HttpClient
#code {
List<Topping> toppings;
[Parameter] public Pizza Pizza { get; set; }
[Parameter] public EventCallback OnCancel { get; set; }
[Parameter] public EventCallback OnConfirm { get; set; }
protected async override Task OnInitializedAsync()
{
toppings = await HttpClient.GetFromJsonAsync<List<Topping>>("toppings");
}
void ToppingSelected(ChangeEventArgs e)
{
if (int.TryParse((string)e.Value, out var index) && index >= 0)
{
AddTopping(toppings[index]);
toppings.Remove(toppings[index]);
}
}
void AddTopping(Topping topping)
{
if (Pizza.Toppings.Find(pt => pt.Topping == topping) == null)
{
Pizza.Toppings.Add(new PizzaTopping() { Topping = topping });
}
}
void RemoveTopping(Topping topping)
{
Pizza.Toppings.RemoveAll(pt => pt.Topping == topping);
toppings.Add(topping);
toppings = toppings.OrderBy(p=>p.Name).ToList();
}
}
<div class="dialog-container">
<div class="dialog">
<div class="dialog-title">
<h2>#Pizza.Special.Name</h2>
#Pizza.Special.Description
</div>
<form class="dialog-body">
<div>
<label>Size:</label>
<input type="range" min="#Pizza.MinimumSize" max="#Pizza.MaximumSize" step="1" #bind="Pizza.Size" #bind:event="oninput" />
<span class="size-label">
#(Pizza.Size)" (£#(Pizza.GetFormattedTotalPrice()))
</span>
</div>
<div>
<label>Extra Toppings:</label>
#if (toppings == null)
{
<select class="custom-select" disabled>
<option>(loading...)</option>
</select>
}
else if (Pizza.Toppings.Count >= 6)
{
<div>(maximum reached)</div>
}
else
{
<select id="ToppingSelection" class="custom-select" #onchange="ToppingSelected">
<option value="-1" disabled selected>(select)</option>
#for (var i = 0; i < toppings.Count; i++)
{
<option value="#i">#toppings[i].Name - (£#(toppings[i].GetFormattedPrice()))</option>
}
</select>
}
</div>
<div class="toppings">
#foreach (var topping in Pizza.Toppings)
{
<div class="topping">
#topping.Topping.Name
<span class="topping-price">#topping.Topping.GetFormattedPrice()</span>
<button type="button" class="delete-topping" #onclick="#(() => RemoveTopping(topping.Topping))">x</button>
</div>
}
</div>
</form>
<div class="dialog-buttons">
<button class="btn btn-secondary mr-auto" #onclick="OnCancel">Cancel</button>
<span class="mr-center">
Price: <span class="price">#(Pizza.GetFormattedTotalPrice())</span>
</span>
<button class="btn btn-success ml-auto" #onclick="OnConfirm">Order ></button>
</div>
</div>
I would recommend rewriting the form to use the built-in Blazor components like InputSelect with proper two-way binding etc.
However, a "quick" fix could be something like a static binding. First, create a field that always has a value of -1 which is your code for (select)
#code {
private readonly Int32 _toppingsIndex = -1;
}
In the next step, set the value to the select control.
<select id="ToppingSelection" class="custom-select"
#onchange="ToppingSelected"
value="#_toppingsIndex">
<option value="-1" disabled>(select)</option>
#for (var i = 0; i < toppings.Count; i++)
{
<option value="#i">#toppings[i].Name - (£#(toppings[i].GetFormattedPrice()))</option>
}
</select>
Explanation
As soon as you select something, your method ToppingSelected is executed, and the topping is removed from the list. As soon as the method has finished, a new render cycle is started. The value is still -1, so the option with the value -1 will be selected after the rendering is finished.
Related
I am developing an application using C# in Blazor Framework. I have designed some forms like the following, where the grey areas are populated with the button below which triggers a pop up window for selection. Then after selection is done the selected item description will populated into the gray area. This grey area is an InputText element.
If the grey InputTexts are marked as required & readonly, then the areas are grey and users cannot insert manually their values but only though selection window. This is good, but if the user did not populate the window for selection it can also submit the form.
If the grey InputTexts are marked as required and beeing readonly though css, then the validation works, so the user should populate the window selection first, but if he did not, then the grey area becomes editable for manual input.
Any ideas how I can protect the application from manual input but at the same time make the validation work?
Any ideas how I can protect the application from manual input but at the same time make the validation work?
If I'm reading the question correctly, the demo below shows how to link the selector (in this case a select control) and the display and show the correct validation information and UX without access to the readonly control.
As you show no code, I don't know whether this fits with your model and form.
#page "/"
#using System.ComponentModel.DataAnnotations;
<PageTitle>Index</PageTitle>
<h1>Demo</h1>
<EditForm Model="model" OnValidSubmit=#OnSubmit class="border border-dark p-3 m-2">
<DataAnnotationsValidator />
<div class="mb-2">
<label class="form-label">Country</label>
<InputText class="form-control" disabled #bind-Value="#model.Value" />
<ValidationMessage For="() => model.Value" />
</div>
#if (!show)
{
<div class="mb-2">
<button type="button" class="btn btn-dark" #onclick=OnShow>Select Country</button>
</div>
}
else
{
<div class="mb-2">
<InputSelect class="form-select" #bind-Value:get=#model.Value #bind-Value:set="this.OnSetCountry">
<option value="">-- Select a Country -- </option>
<option value="UK">UK</option>
<option value="France">France</option>
<option value="Portugal">Portugal</option>
</InputSelect>
</div>
}
<div class="col=12 mt-2 text-end">
<button class="btn btn-success" type="submit">Submit</button>
</div>
</EditForm>
<h3 class="mt-4">Hides the -- Select a Country -- once a value is selected</h3>
<EditForm Model="model2" OnValidSubmit=#OnSubmit class="border border-dark p-3 m-2">
<DataAnnotationsValidator />
<div class="mb-2">
<label class="form-label">Country</label>
<InputText class="form-control" disabled #bind-Value="#model2.Value" />
<ValidationMessage For="() => model2.Value" />
</div>
#if (!show2)
{
<div class="mb-2">
<button type="button" class="btn btn-dark" #onclick=OnShow2>Select Country</button>
</div>
}
else
{
<div class="mb-2">
<InputSelect class="form-select" #bind-Value:get=#model2.Value #bind-Value:set="this.OnSetCountry2">
#if (model2.Value is null)
{
<option selected disabled value="">-- Select a Country -- </option>
}
<option value="UK">UK</option>
<option value="France">France</option>
<option value="Portugal">Portugal</option>
</InputSelect>
</div>
}
<div class="col=12 mt-2 text-end">
<button class="btn btn-success" type="submit">Submit</button>
</div>
</EditForm>
#code {
private Model model = new();
private bool show = false;
private Model model2 = new();
private bool show2 = false;
private void OnSetCountry(string? value)
{
model.Value = null;
if (value is not null || value != string.Empty)
model.Value = value;
show = false;
}
private void OnSetCountry2(string? value)
{
model2.Value = null;
if (value is not null || value != string.Empty)
model2.Value = value;
show2 = false;
}
private void OnShow()
=> show = !show;
private void OnShow2()
=> show2 = !show2;
public void OnSubmit()
{ }
public class Model
{
[Required]
public string? Value { get; set; }
}
}
So i have 2 Viewbags, they each have a list of values from a database table, the first Viewbag have all possible values of a database column, while the other have only the values corresponding to the selected value in the first Viewbag.
I have the logic for the search.
but i need to have the form update after selecting one value, since they both need to be in the same form, it is not searching for the second value.
OBS:i am only using the controllers, and cshtml views, not razor pages.
Here is a simple demo how to create cascading selectlist in asp.net core mvc:
Model:
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
}
public class SubCategory
{
public int Id { get; set; }
public int CategoryId { get; set; }
public string SubCategoryName { get; set; }
}
View(Index.cshtml):
<div>
<div style="float:left;width:40%">
<form id="form">
<div class="form-group row">
<label>Category Name</label>
<div class="col-12">
<select id="CategoryId" class="custom-select mr-sm-2"
asp-items="#(new SelectList( #ViewBag.Category,"Id","Name"))">
<option value="">Please Select</option>
</select>
</div>
</div>
<div class="form-group row">
<label>SubCategory Name</label>
<div class="col-12">
<select id="SubCategoryId" class="custom-select mr-sm-2"
asp-items="#(new SelectList(string.Empty,"Id","SubCategoryName"))">
<option value="">Please Select</option>
</select>
</div>
</div>
<div>
<input type="button" value="Search" />
</div>
</form>
</div>
</div>
#section Scripts
{
<script>
$(function () {
$('#CategoryId').change(function () {
var data = $("#CategoryId").val();
$.ajax({
url: '/Home/GetSubCategory?CategoryId=' + data,
type: 'Get',
success: function (data) {
var items = "";
$.each(data, function (i, item) {
items += "<option value='" + item.value + "'>" + item.text + "</option>";
});
$('#SubCategoryId').html(items);
}
})
});
})
</script>
}
Controller:
public class HomeController : Controller
{
private readonly MvcProj3Context _context;
public HomeController(MvcProj3Context context)
{
_context = context;
}
public IActionResult Index()
{
ViewBag.Category = _context.Category.ToList();
return View();
}
public JsonResult GetSubCategory(int CategoryId)
{
ViewBag.SubCategory = (from m in _context.SubCategory
where m.CategoryId == CategoryId
select m).ToList();
return Json(new SelectList(ViewBag.SubCategory, "Id", "SubCategoryName"));
}
}
Result:
The problem is, that the .cshtml file is completely rendered before its send to your browser. Therefore you cannot change it with C# code after its sent to the browser.
You could use blazor if you want to do it with c#, or you could do it with javascript.
Hi im having problem with my view
as you can see there is an input and a select option
<div class="form-group">
<label asp-for="DomWasLoc" class="control-label"></label>
<input asp-for="DomWasLoc" class="form-control" id="firstname" name="firstname" />
<select class="form-control"
id="name" name="name"
asp-items="#(new SelectList(ViewBag.LocationList, "LocName","LocName"))">
<option value="">- Select -</option>
</select>
<span asp-validation-for="DomWasLoc" class="text-danger"></span>
</div>
every time you select an item on on the dropdownList the selected item value is inputted in the input box.
but every time i click submit i always shows this error
AspNetCore._Views_DomesticWastes_Create_cshtml+d__25.MoveNext() in Create.cshtml
+
asp-items="#(new SelectList(ViewBag.LocationList, "LocName","LocName"))">
what should i do so that when i click submit it ignore the select and just add the data from input box?
thanks in advance guys! :)
There is no need to combine input and select for the same field DomWasLoc. You could bind the DomWasLoc directly to select.
A demo code like below:
<div class="form-group">
<label asp-for="DomWasLoc" class="control-label"></label>
#*<input asp-for="DomWasLoc" class="form-control" />*#
<select class="form-control"
asp-for="DomWasLoc"
asp-items="#(new SelectList(ViewBag.LocationList, "LocName","LocName"))">
<option value="">- Select -</option>
</select>
<span asp-validation-for="DomWasLoc" class="text-danger"></span>
</div>
Update:
Complte Code:
1. Model
public class Location
{
public int Id { get; set; }
public string LocName { get; set; }
}
public class DomesticWaste
{
public int Id { get; set; }
public string DomWasLoc { get; set; }
}
Controller
// GET: DomesticWastes/Create
public IActionResult Create()
{
List<Location> locationlist = new List<Location>() {
new Location{ Id = 1, LocName = "L1" },
new Location{ Id = 2,LocName = "L2" },
new Location{ Id = 3,LocName = "L3" }
};
ViewBag.LocationList = locationlist;
return View();
}
// POST: DomesticWastes/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,DomWasLoc")] DomesticWaste domesticWaste)
{
if (ModelState.IsValid)
{
_context.Add(domesticWaste);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(domesticWaste);
}
I am doing user roles in MVC6 (using EF7), and I am think I am just missing something, but on a form used to define a user roles I get nothing in the posted back Model to the controller
This is my Model
public class UserRoleItem
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public IdentityUserRole<int> userRole { get; set; }
public bool HasRole { get; set; }
}
public class UsersRolesViewModel
{
public int UserId { get; set; }
public string FullName { get; internal set; }
public List<UserRoleItem> UserRoles;
}
This is the view
#model Skill.ViewModels.Manage.UsersRolesViewModel
<br />
<h3>Set Roles for user - #Model.FullName</h3>
<form asp-action="ChangeUsersRoles" >
<div class="form-horizontal">
<hr />
<input type="hidden" asp-for="UserId" />
#foreach (var userRole in Model.UserRoles)
{
<input type="hidden" asp-for="#userRole.Id" />
<div class="form-group">
<div class="inline-block col-md-8">
<span class="col-md-1" align="center">
<input type="checkbox" asp-for="#userRole.HasRole" />
</span>
<div class="col-md-7">
#userRole.Name (#userRole.Description)
</div>
</div>
</div>
}
<hr />
<div class="form-group">
<div class="col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
</form>
This is the Controller
// GET: Users/ChangeUserRoles/5
public async Task<IActionResult> ChangeUserRoles(int? id)
{
if (id == null)
{
return HttpNotFound();
}
var model = new UsersRolesViewModel();
ApplicationUser appUser = await _context.ApplicationUsers.SingleAsync(m => m.Id == id);
if (appUser == null)
{
return HttpNotFound();
}
else
{
model.UserId = (int)id;
model.FullName = appUser.FullName;
var some = from r in _context.Roles
from ur in _context.UserRoles
.Where(inner => r.Id == inner.RoleId && inner.UserId == id)
.DefaultIfEmpty()
select new UserRoleItem
{
Id = (int)r.Id,
Name = r.Name,
Description = r.NormalizedName,
userRole = ur, // this is needed else it has a hissy fit
HasRole = (ur != null)
};
// get all of the Roles and then also link to the ones the user currently has
model.UserRoles = (some).ToList();
}
return View(model);
}
// POST: Users/ChangeUserRoles/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ChangeUserRoles([Bind(include:"UserId,UserRoles")]UsersRolesViewModel userModel)
{
if (ModelState.IsValid)
{
// update based on the changes
// return RedirectToAction("Edit", new { userModel.UserId });
}
return View(userModel);
}
So when I get the post back on Save, the UserRoles list is null, so I assume I am just missing an obvious thing here?
Also another small issue is the EF Linq statement. If I remove the
userRole = ur,
statement from the Select portion of the Linq query, the system has a fit and says my schema is out of date (which it isn't). I think it is due to the following statement where I am testing the outer join value
HasRole = (ur != null)
Although this seems perfectly reasonable and works if the ur variable is used prior to testing for null (or not)
After entering this question - I further investigated the issue and found that I could do what I needed using an EditorTemplate
So I created the EditorTemplate Folder under my Users View folder as it was a UsersController and then added the following UserRoleItem.cshtml file
#model UserRoleItem
<input type="hidden" asp-for="Id" />
<div class="form-group">
<div class="inline-block col-md-8">
<span class="col-md-1" align="center">
<input type="checkbox" asp-for="HasRole" />
</span>
<div class="col-md-7">
#Model.Name (#Model.Description)
</div>
</div>
</div>
I then changed the view (called ChangeUsersRole.cshtml) to be
#model Skill.ViewModels.Manage.UsersRolesViewModel
<br />
<h3>Set Roles for user - #Model.FullName</h3>
<hr />
<form asp-action="ChangeUsersRoles">
<div class="form-horizontal">
<input type="hidden" asp-for="UserId" />
#Html.EditorFor(m => m.UserRoles)
<hr />
<div class="form-group">
<div class="col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
</form>
Note also: that I tried using the new format
<input asp-for="UserRoles" />
instead of this line in the view
#Html.EditorFor(m => m.UserRoles)
But it did not work as expected. Again maybe I am still doing something wrong - or maybe that feature isn't working yet?
I have following model:
class HomePageModel
{
public User user { get; set; }
public ResultHistory resultHistory { get; set; }
public Option option { get; set; }
public HomePageModel()
{
}
}
Code from controller when passing it into view:
[FacebookAuthorize]
public async Task<ActionResult> Index(Context context)
{
ViewBag.AppUrl = GlobalFacebookConfiguration.Configuration.AppUrl;
if (ModelState.IsValid)
{
var user = await context.Client.GetCurrentUserAsync<MyAppUser>();
var option = new Option();
//CODE for reading user from db, simpified
Users dbUser = db.Get(context);
var resultHistory = new ResultHistory(dbUser.NSPGW, dbUser.NSPGL,
dbUser.NMPGW,dbUser.NMPGL,dbUser.NDPGW, dbUser.NDPGL);
HomepageModel homepageModel = new HomepageModel();
homepageModel.option = option;
homepageModel.resultHistory = resultHistory;
homepageModel.user = user;
return View(homepageModel);
}
}
Code from controller
#using (Html.BeginForm("Start", "Home", FormMethod.Post, new { id="opcijeForm"}))
{
<div class="row">
<div class="col-md-4">
<div class="radio">
<label>
<input type="radio" name="radio" value="sp" />SP
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="radio" value="mp" />MP
</label>
</div>
<div>
<input type="submit" value="start" />
</div>
</div>
<div class="col-md-8">
<div id="spoption">
#Html.DropDownListFor(m => m.option.sl, Model.option.slSelectList)
<label style="padding-left:10px">
<input type="checkbox" id="bb" /> BB
</label>
#Html.DropDownListFor(m => m.option.swt, Model.option.swtSelectList, new {id="bbdd" })
<label style="padding-left:10px">
<input type="checkbox" id="ziv" /> ziv
</label>
#* #Html.DropDownListFor(m => m.gameOption.sln, Model.option.slnSelectList, new { id="zivdd"})*#
</div>
<div id="mpoption">
<label>Numer of oppointment</label>
#Html.DropDownListFor(m=>m.option.spn, Model.option.spnSelectList)
#Html.DropDownListFor(m=>m.option.smv, Model.option.smvSelectList)
</div>
</div>
</div>
}
and receiving model on post
[FacebookAuthorize]
[HttpPost]
public ActionResult Start(HomepageModel model)
{
//Some code
//here is for example model.option null
}
When I submit form with above model, in my controller action where I receive same model, all it's properties are null.Each class that is within HomePage has only string and int as properties. If I move something, for example from class Option and put it in HomePageModel direct, then properties I have moved are not null. Is there a way to post model as above, or should I just copy properties from these three class into HomePageModel class?
[EDIT]
It is because of meta-data FacebookAuthorize, when I remove it everything is working
MVC app facebook passing object from view to controller at submit