I have a index.razor, that has a for loop that generates let's say 100 components (those display text, and a couple more vlaues), now i want to hava a reference of every component, is there a way to save those in a List or an array?
I tryed it myself in many ways but I get the name of the component displayed in the html.
addPdfComp is executed by a button:
public void addPdfComp(int type)
{
var newComponent = new PdfComp();
newComponent.text = "some test text";
PdfComponentsList.Add(newComponent);
}
and to display the components:
#foreach (var comp in PdfComponentsList)
{
<div>#comp</div>
}
result:
see result
Edit:
The component itself:
<div class="row p-1 " style="border:solid; ">
<h5>TextComponent</h5>
<div>
<label>X</label>
<input type="number" class="col-2" #bind-value="_x"/>
<label>Y</label>
<input type="number" class="col-2" #bind-value="_y"/>
<label>Text</label>
<input type="text" class="col-4" #bind-value="text" />
</div>
#code {
[Parameter]
public string text { get; set; } = "Missing text";
[Parameter]
public string type { get; set; } = "noone";
[Parameter]
public int _x { get; set; } = 0;
[Parameter]
public int _y { get; set; } = 0;
}
The default string representation for an object is the fully qualified class name for that object, which is what you're seeing. This means that whatever PdfComp is, it doesn't have a meaningful .ToString() implementation.
And that's all Razor syntax really does in this case, it just emits output as a string. So #something will basically evaluate something as a string and write it to the output.
Based on a comment above:
My intention is [...] to display the component itself as if i just typed: <PdfComp></PdfComp> 100 times.
An instance of PdfComp in C# code is not the same thing as an instance of <PdfComp></PdfComp> in the markup. (Though I imagine they are very closely analogous somewhere in the depths of the framework.) What you want isn't a list of object instances, but a list of the data your view needs to render the markup.
So, structurally, more like this:
// PdfComponentsList here is a list of strings
PdfComponentsList.Add("some test text");
And then loop through it in the markup to display the elements:
#foreach (var pdf in PdfComponentsList)
{
<PdfComp text="#pdf"></PdfComp>
}
You could also make PdfComponentsList a list of some object to hold multiple properties, for example:
PdfComponentsList.Add(new SomeObject { Text = "some test text" });
And in the markup:
#foreach (var pdf in PdfComponentsList)
{
<PdfComp text="#pdf.Text"></PdfComp>
}
Related
Im trying to wrap one of my components on some specific tag based on some conditions.
To make it simple lets say that i have a
<div class="inner-class">
this must be in inner-class div and wrapped in some-class div
</div>
And if 'condition' == true then it should wrap it in another div the result should be like this
<div class="wrapper-class">
<div class="inner-class">
this must be in inner-class div and wrapped in some-class div
</div>
</div>
And before you say, use if-else method. i must say no. because the tag and the class in it is dynamic so i cant write it like that.
What i tried to do is
#if (condition)
{
#:<div class="wrapper-class">
}
<div class="inner-class">
this must be in inner-class div and wrapped in some-class div
</div>
}
#if (condition)
{
#:</div>
}
I Thought it should do the job.
But the problem is the browser closes the outer div before putting the inner div in it.
You have to use BuildRenderTree
With BuilderRenderTree you have full control to build component html.
For more information read this good article Building Components Manually via RenderTreeBuilder
I ended up writing a wrapper component similar to this to solve this problem pretty elegantly:
#if (Wrap)
{
<div id="#Id" class="#Class">
#ChildContent
</div>
}
else
{
#ChildContent
}
#code
{
[Parameter] public string? Id { get; set; }
[Parameter] public string? Class { get; set; }
[Parameter] public RenderFragment? ChildContent { get; set; }
[Parameter] public bool Wrap { get; set; }
}
It doesn't support dynamic tag types, but that would be pretty easy to create using a component implementing BuildRenderTree as mRizvandi suggested.
If it's a simple layout like you've described than you can make use of the MarkupString. The trick is understanding that MarkupString will automatically close any tags that are left open. Therefore you just need to build the entire string properly before trying to render it as a MarkupString.
#{ bool condition = true; }
#{ string conditionalMarkup = string.Empty; }
#if (condition)
{
conditionalMarkup = "<div class=\"wrapper-class\">";
}
#{ conditionalMarkup += "<div class=\"inner-class\"> this must be in inner-class div and wrapped in some-class div </div>"; }
#if (condition)
{
conditionalMarkup += "</div>";
#((MarkupString)conditionalMarkup)
}
else
{
#((MarkupString)conditionalMarkup)
}
I do this for building simple conditional markup. Usually inside of an object iteration making use of String.Format to fill in property values.
In my razor page PageModel I have a bound List<T> property:
[BindProperty]
public List<JobCard> CustomerSpecificJobCards { get; set; }
Here is the JobCard model:
public partial class JobCard
{
public int JobCardId { get; set; }
public string Products { get; set; }
public string CusCode { get; set; }
public string Category { get; set; }
public bool IsAssigned { get; set; }
}
The list of JobCards is populated after the user posts an order number from the page:
CustomerSpecificJobCards = AllJobCards.Where(jc => jc.CusCode == WorkOrderInfo.MarkForCusCode).ToList();
It is then displayed in a form on the razor page via a foreach loop:
#foreach (var card in Model.CustomerSpecificJobCards)
{
<div class="col-1"><input asp-for="#card.IsAssigned" /></div>
<div class="col-9">#card.Products</div>
<div class="col-2">#card.JobCardId</div>
}
Users are shown a list of job cards and a checkbox. Once checked the user submits the selections. When I look at the CustomerSpecificJobCards that are posted, the list is empty. Why? Based on information here, I decided to change the foreach loop to a for loop:
#for (var card = 0; card < Model.CustomerSpecificJobCards.Count; card++)
{
<div class="col-1"><input asp-for="#Model.CustomerSpecificJobCards[card].IsAssigned" /></div>
<div class="col-9">#Model.CustomerSpecificJobCards[card].Products</div>
<div class="col-2">#Model.CustomerSpecificJobCards[card].JobCardId</div>
}
[EDIT] Originally, I thought all the values were returned using the for loop, but it turns out only the .IsAssigned values are returned... Products and JobCardId are empty. I'm using Razor Pages for the first time. What am I doing wrong?
[Followup] After reading Rafalon's answer, I found this explanation of binding a complex collection with checkboxes in either a for or foreach loop. Plus, here is another excellent related link on data binding.
Short answer: check the generated HTML for both for and foreach and you'll see that with foreach, you can not expect the form to return the List correctly (with asp-for helper).
for is needed so you get indexable inputs, looking like:
<input name='CustomerSpecificJobCards[0].IsAssigned'
id='CustomerSpecificJobCards_0__IsAssigned' />
You could still do it with foreach, but without asp-for it's quite tedious, and your model have to meet some requirements (having an index property for example).
Your other problem comes from the fact that #Model.CustomerSpecificJobCards[card].Products is text only.
Therefore you should either replace it with an input, just like IsAssigned, or else you can add a hidden input:
<input type="hidden" asp-for="Model.CustomerSpecificJobCards[card].Products" />
Same goes for JobCardId.
This is my .cshtml code,
# { var myList = (List<MyViewModel>)ViewBag.MyCollection; }
<input id="myListHidden" type="hidden" data-my-list="#myList" />
And this is my typescript code to get the value above,
let _myList = $('#myListHidden').data('my-list');
And this is the return value,
"System.Collections.Generic.List`1[MyProject.Data.ViewModels.MyViewModel]"
I just want to iterate through this collection. This is what I've tried
for (let entry of _myList ) {
console.log(entry);
}
But it gives the output as System.Collections.Generic.List as string.
I want to iterate all the values inside this collection.
Edit
MyViewModel's properties are as follow,
public long Id { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
You will need to serialize your collection and then output that serialized value as "Raw" (or else the razor engine will escape your JSON, and you don't want that)
#using Newtonsoft.Json;
#{
var myList = JsonConvert.SerializeObject(ViewBag.MyColection);
}
<input id="myListHidden" type="hidden" data-my-list="#Html.Raw(myList)" />
In this example, I use the Newtonsoft serializer. You can use it by installing the NuGet package.
The above will generate something like this:
<input id="myListHidden" type="text" data-my-list="[{"Id":1,"Name":"Bob","Active":true}]" />
You can then use the value how you wish
EDIT:
Note that if you do not use #Html.Raw() the razor engine will output this:
<input id="myListHidden" type="text" data-my-list="[{"Id":1,"Name":"Bob","Active":true}]" />
In my ASP.NET MVC 5 website I have a menu with two types of files: actual pages returned from the controllers and pdf files that you can view in the navigator. Since my menu is created dynamically (it varies according to Active Directory rights) I have to distinguish which type is which (Document or Page), and this means that according to the file type you click, it will not result in the same action.
To show you, imagine there is a List<DocumentModel> documents and the following to treat the information:
<ul>
#foreach (var document in documents)
{
<li>
#if (document.type.Equals("Document"))
{
using (Html.BeginForm("DisplayPDF", "Navigation", new { pdfName = document.link }, FormMethod.Post))
{
#Html.ActionLink(document.docName, "DisplayPDF", null, new {id="linkStyle", #class = "saveButton", onclick = "return false;" })
}
}
else
{
//This link is different then the one above visually
<span class="glyphicon glyphicon-file"></span>#document.docName
}
</li>
</ul>
<!--This script allows to submit the form, and treat the action of DisplayPDF-->
<script>
$(document).ready(function () {
$('.saveButton').click(function () {
$(this).closest('form')[0].submit();
});
});
</script>
DocumentModel is as follows:
public class DocumentModel
{
public long idDocument { get; set; }
public long idCategory { get; set; }
public string docName { get; set; }
public string link { get; set; }
public string type { get; set; }
}
Finally my DisplayPDF method is the following:
public FileContentResult DisplayPDF(string pdfName)
{
var fullPathToFile = #"Your\Path\Here" + pdfName;
var mimeType = "application/pdf";
var fileContents = System.IO.File.ReadAllBytes(fullPathToFile);
return new FileContentResult(fileContents, mimeType);
}
Now as you can see in the Razor view I displayed, there are two types of clickable links. One is only a controller redirection, whereas the second calls the DisplayPDF method and then returns a PDF page. The problem I'm having is that these links are displayed differently, and I don't know how to have the same display for both.
To synthesize my question : how to display the same using #Html.ActionLink() and 'a' tag, knowing that currently, the 'a' tag has the proper display?
Update I modified some code as to give IDs to <a> tag and #Html.ActionLink, then I added this css in stylesheet:
#linkStyle {
margin-left: 25px;
color: antiquewhite;
}
But I still have a different display
UPDATE 2 I think that the problem of display come from the BeginForm method, any ideas to fix this?
I am creating a form to put data in a database. Now I want to use an array, because I want to put a form, inside a form. For example, you have a form where a user puts information about himself, like age name and so on. One question is about his friends, you must fill in the name and age off all your friends. Because you have more then one friend, you can add more than one friend. So you fill in the name and age of one friend, click on save, and then you can continue fill in the info of another friend. When you click save, you don't save the whole form.
I hope this I explaned it well.
This is my form:
public class User
{
[BsonElement("Id")]
public int Id { get; set; }
[BsonElement("Email")]
public string Email { get; set; }
[BsonElement("Name")]
public string Name{ get; set; }
[BsonElement("Company")]
public string Company { get; set; }
[BsonElement("Array friends")]
}
View:
#model ISPInvoice.Models.Invoice
#using (Html.BeginForm("Edit", "Home"))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>New Note</legend>
<h3>New Note</h3>
<div class="editor-label">
#Html.LabelFor(model => model.InvoiceNumber)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.InvoiceNumber)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
Add another class called Friend
public class Friend
{
[BsonElement("Name")]
public string Name { get; set; }
[BsonElement("Age")]
public int Age{ get; set; }
}
In your User class add a List of Friends.
public class User
{
[BsonElement("Id")]
public int Id { get; set; }
[BsonElement("Email")]
public string Email { get; set; }
[BsonElement("Name")]
public string Name{ get; set; }
[BsonElement("Company")]
public string Company { get; set; }
[BsonElement("Array friends")]
public List<Friend> Friends { get;set;}
}
In order to access the Friends list inside the View, you can loop through the list.
#foreach (namespace.Models.Friend friend in Model.Friends)
{
#Html.LabelFor(item => friend.Name)
}
It sounds like you'll need some client-side code.
What I'd probably do, as I understand your need, would be to have a hidden input in the form that you update with the friends that have been selected.
The trouble is that standard form encoding doesn't support hierarchical data, so you need to add support for that manually. That's not too hard, as you'll see, but it's a hassle. This would be super easy if you were using a framework like Angular, or even just posting with AJAX in general. But that's a pretty big change, that might not be worth going through and implementing just for this case.
This example depends on jQuery. It's also super simple, and doesn't support anything like removal or editing of existing friends. You should be able to add that stuff pretty easily, though. It also does absolutely no validation on anything, so take that how you will.
var friends = [];
function refreshHiddenField() {
$("#friendsListText").val(friends.map(function(d) { return d.name + "=" + d.age }).join(";"));
}
document.addEventListener("DOMContentLoaded", function(event) {
$("#btnAddFriend").click(function() {
var name = $("#newFriendName").val();
var age = $("#newFriendAge").val();
friends.push({ name : name, age: age });
refreshHiddenField();
var li = $("<li />").text(name + " (" + age + ")");
$("#friendsList").append(li);
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<!-- This will actually be an #Html.HiddenFor(m => m.FriendsText), or something -->
<input type="text" id="friendsListText" readonly placeholder="Add some friends first" />
<hr />
<input type="text" id="newFriendName" placeholder="Name" />
<input type="number" id="newFriendAge" placeholder="Age" />
<button type="button" id="btnAddFriend">Add!</button>
<br />
<ul id="friendsList">
</ul>
As you'll find playing with that (albeit very lightly tested) example, adding a friend appends the details to a user-visible list, and appends a machine-readable format into an input. Per the comment, this input is visible in this example to see what's happening, but it wouldn't be in production.
Once you've got your client-side code done, you just have to parse the hidden field on save. Assuming a Friend class with properties for string Name and int Age,
[HttpPost]
public ActionResult EditProfile(User m)
{
// Unrelated stuff
var friends = m.FriendsText.Split(';').Select(c => {
var args = c.Split('=');
return new Friend { Name = args[0], Age = int.Parse(args[1]) };
})
// Use the collection
// Unrelated stuff
}
This is just some pretty simple string manipulation with LINQ and the Split function to understand the machine-readable format from the client.
Note with this implementation that names can't have semicolons or equals signs in them.
Once you've got it all in, printing it out is easy. Just, yes, add a collection type to your model, then loop through with a foreach loop.
<ul>
#foreach (var f in Model.FriendsCollection)
{
<li>#f.Name (#f.Age)</li>
}
</ul>
I know that's not what you're talking about, but I offer this just as an example of how you would include a collection in the model, per your original question.