Create a function to pass html control "ui" as paramater. - c#

I have there 6 UL and inside each UL i dynamically generate li from serverside. right now for 6 UL i am doing ti 6 times repetadly. Is there a way i can create a funciton to pass ul as an parameter in the function and call same function with different ul elements.
right now i have this
<ul ID="ul_1"></ul>
<ul ID="ul_2"></ul>
<ul id="ul_3"></ul>
<ul id="ul_4"></ul>
server side code to populat is like this
foreach (String li in ListA)
{
HtmlGenericControl uli = new HtmlGenericControl("li");
uli.InnerHtml = li;
ul_1.Controls.Add(uli);
}
I am using this code for each of the UL that is 4 times but i am trying to create a function so that i can use the same function just passing the UL id. I am not having any idea...Any help folks....

If I correctly understood what you want maybe you could try something like this.
Model:
public class ListItem {
public string Content { get; set; }
}
public class ListOfItems {
public int Id { get; set; }
public IList<ListItem> Items { get; set; }
}
View (for example using razor):
#model IEnumerable<ListOfItems>
....
#foreach (var itemList in Model)
{
<ul id="ul_#itemList.Id">
#foreach(var item in itemList.Items) {
<li>#item.Content</li>
}
</ul>
}
Controller:
public ActionResult ViewList(){
var model = new List<ListOfItems>();
var listItem1 = new ListItem
{
Content = "My first list item!"
};
var listItem2 = new ListItem
{
Content = "My second list item!"
};
var listOfItems1 = new ListOfItems
{
Id = 1
};
listOfItems1.Item.Add(listItem1);
listOfItems1.Item.Add(listItem2);
model.Add(listOfItems1);
return View(model);
}
A little bit of refactoring and you're good to go :)

Related

add list of dynamic components in blazor

I just started to have a look in blazor (v0.3) and doing some test I wanted to add a list using blazor
First I created a List<string> to test a simple list in the same page
<ul>
#foreach (var item in listItems)
{
<li>#item</li>
}
</ul>
#functions {
private List<string> listItems = new List<string>();
private string newItem;
private void AddItem()
{
if (string.IsNullOrEmpty(newItem))
return;
listItems.Add(newItem);
newItem = "";
}
}
this is working fine, is adding every element to the list when I add it. but then, i tried to add components, add a single component was easy, based on this question here but for a list I had the next problem:
I created a <li> compontent just to test the functionality of components, here is the component view
<li id="#ID">
#Text
</li>
#functions {
[Parameter]
string Text { get; set; }
[Parameter]
string ID { get; set; }
}
then in the parent view
<input type="text" bind="TxtExample" name="inpAdd"/>
<button onclick="#addCompoment">add comp1</button>
<div class="simple-list-list">
#if (!componentListTest.Any())
{
<p>You have no items in your list</p>
}
else
{
<ul>
#foreach (var item in componentListTest)
{
#item
}
</ul>
}
</div>
#functions {
private List<RenderFragment> componentListTest { get; set; }
private int currentCount {get; set;}
private string TxtExample { get; set; }
protected override void OnInit()
{
currentCount = 0;
componentListTest = new List<RenderFragment>();
}
protected void addCompoment()
{
componentListTest.Add(CreateDynamicComponent(currentCount));
currentCount++;
}
RenderFragment CreateDynamicComponent(int counter) => builder =>
{
var seq = 0;
builder.OpenComponent(seq, typeof(listExample));
builder.AddAttribute(++seq, "Text", "text -- "+TxtExample);
builder.AddAttribute(++seq, "id","listed-"+counter);
builder.CloseComponent();
};
}
when I load the fist element is loaded correctly:
but when I entered the second one, all of them are replaced for the last one:
Any idea whats going on?
You are making it too complicated. You don't need to dynamically instantiate components for this scenario to work.
You can just do a:
<ul>
#foreach (var item in listItems)
{
<myComponent bind-myVar="#item"></myComponent>
}
</ul>
And the components will be instantiated for you.
Also see here how to make the parameters work on your component.
This is because TxtExample is global to the component. When Blazor detects a potential UI change, it recalculates the entire component and updates the DOM with any differences. So when you change the textbox, TxtExample is updated and then the Razor is recalculating, inserting the new value of TxtExample for all rows.

Passing complex array from Controller to View ASP.NET MVC

I have a model in my ASP.NET MVC application:
public class SearchArrayModel
{
public long ID { get; set; }
public string Name { get; set; }
public struct AttribStruct
{
public string AttribName { get; set; }
public string[] AttribValues { get; set; }
}
public AttribStruct[] AttribStructTable { get; set; }
}
In controller I'm filling it by some data from WebAPI (filling process is okay), I created an array of SearchArrayModel because I have a lot of items to fill from webAPI (It's a webAPI from site similar to ebay), for example some phones with its names, and attributes which u normally see on the auction site).
SearchArrayModel[] src = new SearchArrayModel[x];
{
//filling the fields
}
And on the end of the ActionResult I have:
return View(src);
//Visual Studio tells me that it is a "SearchArrayModel[] src"
I have also View, where I want to get and display the data:
#model allegrotest.Models.SearchArrayModel
#using (Html.BeginForm())
{
<h2>#Model.AttribStructTable[1].AttribName</h2>
<h3>#Model.AttribStructTable[1].AttribValues[1]</h3>
//indexes of arrays are just for testing, if all will be good I will write a loop
}
But when I'm starting the app I have an error:
The model item passed into the dictionary is of type
'allegrotest.Models.SearchArrayModel[]', but this dictionary requires
a model item of type 'allegrotest.Models.SearchArrayModel
I know that this is a complex array and I don't know how to pass this array from Controller to View.
I tried to write in View:
#model allegrotest.Models.SearchArrayModel[]
but then I can't get into the fields inside the #Model
Starting from assumption that "filling process is okay" is wrong.
Note: I made this assumption because I see that you are not interested in Model[index] and I noticed in SearchArrayModel[x]; { } the ;.
SearchArrayModel src = new SearchArrayModel
{
AttribStructTable = new SearchArrayModel.AttribStruct[]
{
new SearchArrayModel.AttribStruct{AttribName = "0Nume", AttribValues = new []{"0one", "0two"}},
new SearchArrayModel.AttribStruct{AttribName = "1Nume", AttribValues = new []{"1one", "1two"}},
},
Name = "SearchArrayName"
};
Your View is okay and is working
#model allegrotest.Models.SearchArrayModel
#using (Html.BeginForm())
{
#foreach(var attribStruct in #Model.AttribStructTable)
{
<h2>#attribStruct.AttribName</h2>
#foreach(var attribValue in attribStruct.AttribValues)
{
<h3>#attribValues</h3>
}
}
}
Another solution will be to make the modelof View to be an IEnumerable and in the Action you can make return View(src.ToList());
Also I noticed, when I tested your code, that you have Model.AttribStructTable which is wrong because your Model is a collection and you have to specify which element from model you want to display Model[index] (not posible with IEnumerable), Model.First() or you can iterate through collection.
#model IEnumerable<allegrotest.Models.SearchArrayModel>
#using (Html.BeginForm())
{
#foreach(var attribStruct in #Model.First().AttribStructTable)
{
<h2>#attribStruct.AttribName</h2>
#foreach(var attribValue in attribStruct.AttribValues)
{
<h3>#attribValues</h3>
}
}
}
If you iterate through all items from Model will look like this
#model IEnumerable<allegrotest.Models.SearchArrayModel>
#using (Html.BeginForm())
{
#foreach(var searchArrayModel in Model)
{
#foreach(var attribStruct in #searchArrayModel)
{
<h2>#attribStruct.AttribName</h2>
#foreach(var attribValue in attribStruct.AttribValues)
{
<h3>#attribValues</h3>
}
}
}
}
#model allegrotest.Models.SearchArrayModel[]
which is an array. So you could try
#foreach (SearchArrayModel item in Model)
{
<h2>#item.AttribStructTable[1].AttribName</h2>
<h3>#item.AttribStructTable[1].AttribValues[1]</h3>
..
}
or
#for (int i = 0; i < Model.Length; ++i)
{
<h2>#Model[i].AttribStructTable[1].AttribName</h2>
<h3>#Model[i].AttribStructTable[1].AttribValues[1]</h3>
..
}

How to add #Html.ValidationMessageFor for each item in a collection?

How would you add #Html.ValidationMessageFor() for each item in a collection? Say,
public class FooVm
{
// some property
public ICollection<BarVm> Bars { get; set; }
}
public class BarVm
{
// some property
[Range(1, int.Max, ErrorMessage = "Must be greater than 1")
public float? Fox { get; set; }
}
Then in a view
#model namespace.here.FooVm
<div class="container"></div>
Populate
<script>
$(function() {
var i = 0;
var populate = function() {
var strBuilder = '<input type="text" name="Bars[i].Fox" />';
$(".container").append(strBuilder);
return false;
};
$(".trigger").click(populate);
});
</script>
It's all working. But how can I add the validation in every textbox? I'm using ASP.NET MVC 4 still practicing. I'm also utilizing unobtrusive validation for client validation. Any you-should-do-something-like-this suggestions or tips, sample code would be great. Thanks.
Actually, using Javascript to populate a View is not the way MVC should be used. Instead, you can render all textboxes like this:
First the code for the class:
public class FooVm
{
// some property
public List<BarVm> Bars { get; set; }
public FooVm()
{
// Make sure the collection exists to prevent NullReferenceException
this.Bars = new List<BarVm>();
}
}
public class BarVm
{
// some property
[Range( 1, Int32.MaxValue, ErrorMessage = "Must be greater than 1" )]
public float? Fox { get; set; }
}
Now the code for the View:
#model WebApplication2.Models.FooVm
<h2>Sample View</h2>
#using ( Html.BeginForm( "YourAction", "YourController" ) )
{
<div class="container">
#for ( int i = 0; i < Model.Bars.Count; i++ )
{
#Html.TextBoxFor( m => m.Bars[i].Fox )
#Html.ValidationMessageFor( m => m.Bars[i].Fox );
}
</div>
}
This will render the necessary tags - and of course the validationmessage-bits. However, it's also possible to combine all error messages in one place by using
#Html.ValidationSummary()
If you really want to display the stuff only after clicking a button, consider using a partial view and loading that one. That's a much better approach than trying to create all necessary tags and attributes for validation using javascript.
Regards,
Frank

Sending Complex Data to ASP MVC view

I've been struggling to research an answer to this question as I cannot come up with the correct search terms.
Basically I have 2 IEnumerable<T>'s in my controller, below is the code for the attempt I made.
Controller:
IEnumerable<Room> allRooms = RoomHelper.FindAllRooms();
foreach (var room in allRooms)
{
IEnumerable<Bunk> associatedBunks = RoomHelper.FindAssociatedBunksByRoom(room);
if (associatedBunks.Count() > 0)
{
ViewData["Room_"+room.RoomId] = associatedBunks;
}
}
And I'm trying to send them to the view in a way that I can do two foreach loops that will cycle through one set of data (in this case the Room objects and will then using the Room.RoomId key cycle through another IEnumerable which contains the associated data.
My view looks like this but is showing parse errors:
#foreach (var room in ViewBag.Rooms)
{
<h2>#room.RoomName</h2>
#if (ViewData["Room_" + room.RoomId].Count() > 0)
{
<ol>
#foreach (var bunk in ViewData["Room_" + room.RoomId])
{
<li>#bunk.BunkId</li>
}
</ol>
}
}
The end result I'm looking for in the HTML is something like:
<h2>Room 1</h2>
<ol>
<li>Bunk 1</li>
<li>Bunk 2</li>
</ol>
<h2>Room 2</h2>
<ol>
<li>Bunk 3</li>
<li>Bunk 4</li>
</ol>
What is the best practice in ASP.NET MVC 4 with EF5 to achieve this kind of result when passing "multidimensional" (is this multidimensional?) data?
Don't rely on ViewData. Store the data that you want to pass on to your view in a proper ViewModel:
public class RoomViewModel
{
List<Room> Rooms { get; set;}
...
}
Store your data in one of those.
Your Controller method then returns an instance of it:
public RoomViewModel GetRooms(int someParameter)
{
RoomViewModel result = new RoomViewModel();
result.Rooms = RoomHelper.Something(someParameter);
...
return result;
}
Your View declares its model on top:
#model MyApplication.ViewModels.RoomViewModel
and hence you use it in your View.
<h2>#Model.Rooms.Count rooms found</h2>
etc.
Use a code block in your view instead of adding an '#' in front of each C# statement:
#{
foreach (var room in ViewBag.Rooms)
{
#Html.Raw("<h2>" + room.RoomName + "</h2>");
if (ViewData["Room_" + room.RoomId].Count() > 0)
{
#Html.Raw("<ol>");
foreach (var bunk in ViewData["Room_" + room.RoomId])
{
#Html.Raw("<li>" + bunk.BunkId + "</li>");
}
#Html.Raw("</ol>");
}
}
}
You should avoid the use of #HtmlRaw("") as far as possible as it has a XSS vulnerability. But this should put you on the right track.
As per description given by you it seems that Bunk is associated with rooms. If that's the case then Bunk may have some id for pointing to room it belongs. Now you can create a ViewModel like this
public class BunkViewModel:Bunk
{
public BunkViewModel()
{
Mapper.CreateMap<Bunk,BunkViewModel>();
}
//I'm assuming that you already have some id in bunk to point to room it belongs.
//But writing it here to make it clear
public int RoomId { get; set; }
public string RoomName { get; set; }
//Use AutoMapper to Map
public static BunkViewModel Map(Bunk source)
{
return Mapper.Map<Bunk,BunkViewModel>(source);
}
}
Now in your controller
public ActionResult ActionName()
{
var result = new List<BunkViewModel>();
var rooms = RoomHelper.FindAllRooms();
var bunks = BunkHelper.GetAllBunks();
foreach(var bunk in bunks)
{
var bunkViewModel = BunkViewModel.Map(bunk);
var room = rooms.FirstorDefault(r=>room.RoomId == bunk.RoomId);
bunkViewModel.RoomId = room.RoomId; //No need to set if you already have this id in bunk
bunkViewModel.RoomName = room.RoomName;
result.Add(bunkViewModel);
}
return View(result.);
}
Now in your view you can do like this
#model List<MyApplication.ViewModels.RoomViewModel>
#foreach(var bunk in Model)
{
//Print it here as you want..
}

getJson doesn't call JsonResult for models rendered with PartialView

So I have this application in ASP MVC 3.
My database has two tables: Comenzi and DetaliiComenzi with one-to-many relationship (and Link-to-Sql) - in my application I want my users to buy some products by making a oder(stored in table comenzi) and for that order a list of products he wants to buy (will be stored in DetaliiComenzi with Order.id as foreign key).
Basically, after I create a new entry for Comenzi, I want to be able to make a list of products for that order (something like a shop chart but the user will choose his products in a view, adding how many products as he likes).
I have used Steve Sanderson’s method of editing (and creating) a variable length list.
-- Here is the model for which I create the list.
When I'm choosing a single product to oder I must first select the Group (Grupa) which he belongs to from a dropdownlist (using ListaGrupe) and then from a second dropdownlist (ListaProduse) a product from that particular group of products I selected in the first dropdownlist.
public class Comd
{
public string Grupa { get; set; }
public string Produs { get; set; }
public string Cantitate { get; set; }
public string Pret { get; set;}
public string TVA { get; set; }
public string Total { get; set; }
public List<SelectListItem> ListaGrupe
{
get;
set;
}
public List<SelectListItem> ListaProduse
{
get;
set;
}
}
--The Controller:
public ActionResult ComandaDetaliu(string id)
{
Comd model = new Comd();
IProduseRepository _prod = new ProduseRepository();
model.ListaGrupe = _listecomanda.GetGrupe();
string first = model.ListaGrupe[0].Value;
model.ListaProduse = _listecomanda.GetProduse(first);
string pret = _prod.GetByDenumire(model.ListaProduse[0].Text).pret.ToString();
model.Pret = pret;
double fr = 0.24;
model.TVA = fr.ToString();
var data = new[] { model };
return View(data);
}
-- The View
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<InMVC3.Models.Comd>>" %>
<%# Import Namespace="InMVC3.Helpers"%>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Comanda numarul: <%: RouteData.Values["id"].ToString()%></h2>
<% using(Html.BeginForm()) { %>
<div id="editorRows">
<% foreach (var item in Model)
Html.RenderPartial("ProduseEditor", item);
%>
</div>
<%= Html.ActionLink("Adauga alt produs", "Add", null, new { id = "addItem" }) %>
<input type="submit" value="Finished" />
<% } %>
-- The Partial View "Produse Editor"
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<InMVC3.Models.Comd>" %>
<%# Import Namespace="InMVC3.Helpers" %>
<div class="editorRow">
<script type="text/javascript">
$(document).ready(function () {
$("#Grupa").change(function () {
var url = '<%= Url.Content("~/") %>' + "Comenzi/ForProduse";
var ddlsource = "#Grupa";
var ddltarget = "#Produs";
$.getJSON(url, { id: $(ddlsource).val() }, function (data) {
$(ddltarget).empty();
$.each(data, function (index, optionData) {
$(ddltarget).append("<option value='" + optionData.Value + "'>" + optionData.Text + "</option>");
});
});
});
});
</script>
<% using(Html.BeginCollectionItem("comds")) { %>
Grupa: <%= Html.DropDownListFor(x => x.Grupa, Model.ListaGrupe) %>
Produsul: <%= Html.DropDownListFor(x => x.Produs, Model.ListaProduse) %>
Cantitate: <%=Html.TextBoxFor(x=>x.Cantitate) %>
Pret: <%=Html.DisplayFor(x => x.Pret, new { size = 4})%>
TVA: <%= Html.DisplayFor(x=>x.TVA) %>
Total: <%=Html.DisplayFor(x=>x.Total) %>
Sterge
<% } %>
-- And the JsonResult method
public JsonResult ForProduse(string id)
{
throw new NotSupportedException();
JsonResult result = new JsonResult();
var produsele = _listecomanda.GetProduse(id);
result.Data = produsele;
result.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
return result;
}
All I need to know is how to make the call to the JsonResult action because this is what doesn't works so that when I change the selected value in the first dropdownlist to dynamically change the second too.
Of course, I also need to change the other properties too but that after I get how to make getJson to work.
If you need more details please tell me.
UPDATE 1:
--The Helper
public static class HtmlPrefixScopeExtensions
{
private const string idsToReuseKey = "__htmlPrefixScopeExtensions_IdsToReuse_";
public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName)
{
var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
string itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().ToString();
// autocomplete="off" is needed to work around a very annoying Chrome behaviour whereby it reuses old values after the user clicks "Back", which causes the xyz.index and xyz[...] values to get out of sync.
html.ViewContext.Writer.WriteLine(string.Format("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />", collectionName, html.Encode(itemIndex)));
return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, itemIndex));
}
public static IDisposable BeginHtmlFieldPrefixScope(this HtmlHelper html, string htmlFieldPrefix)
{
return new HtmlFieldPrefixScope(html.ViewData.TemplateInfo, htmlFieldPrefix);
}
private static Queue<string> GetIdsToReuse(HttpContextBase httpContext, string collectionName)
{
// We need to use the same sequence of IDs following a server-side validation failure,
// otherwise the framework won't render the validation error messages next to each item.
string key = idsToReuseKey + collectionName;
var queue = (Queue<string>)httpContext.Items[key];
if (queue == null) {
httpContext.Items[key] = queue = new Queue<string>();
var previouslyUsedIds = httpContext.Request[collectionName + ".index"];
if (!string.IsNullOrEmpty(previouslyUsedIds))
foreach (string previouslyUsedId in previouslyUsedIds.Split(','))
queue.Enqueue(previouslyUsedId);
}
return queue;
}
private class HtmlFieldPrefixScope : IDisposable
{
private readonly TemplateInfo templateInfo;
private readonly string previousHtmlFieldPrefix;
public HtmlFieldPrefixScope(TemplateInfo templateInfo, string htmlFieldPrefix)
{
this.templateInfo = templateInfo;
previousHtmlFieldPrefix = templateInfo.HtmlFieldPrefix;
templateInfo.HtmlFieldPrefix = htmlFieldPrefix;
}
public void Dispose()
{
templateInfo.HtmlFieldPrefix = previousHtmlFieldPrefix;
}
}
}
UPDATE
I now have another issue. When I Post that list to the actiont, I get the following error at the foreach statement inside the controller action: Object reference not set to an instance of an object.
-- The Controller Action
[HttpPost]
public ActionResult ComandaDetaliu(IEnumerable<Comd> comenzi)
{
if (ModelState.IsValid)
{
foreach (var item in comenzi)
{
detalii_comenzi det = new detalii_comenzi();
det.id_comanda = Convert.ToInt32(RouteData.Values["id"].ToString());
det.id_produs = Convert.ToInt32(item.Produs);
det.cantitate_comandata = Convert.ToDecimal(item.Cantitate);
det.cantitate_livrata = 0;
det.pret =Convert.ToDecimal(item.Pret);
det.tvap = Convert.ToDecimal(item.TVA);
}
return RedirectToAction("Index");
}
return View(comenzi);
}
Your problem is the duplicate IDs - Every row has a dropdown with ID "Grupa" so your jquery selector will match the dropdowns in every row.
You need to add a prefix to the controls - there are several ways to achieve that - a search for "mvc3 field prefix" brings up several other questions:
How to define form field prefix in ASP.NET MVC
ASP.MVC 3 Razor Add Model Prefix in the Html.PartialView extension
ASP.NET MVC partial views: input name prefixes
Most of those are focused on mapping when the form is posted, but the same issue applies with your javascript.
You could just update the ids in your script to something like "##(ViewBag.Prefix)Grupa", but a better approach would be to use classes instead of ids in your selector and make the script reusable - something like:
ddlSource = this;
ddlDest = this.Parent().find(".produs");

Categories

Resources