How to get sequence/array index in Editor Template?
Asked Answered
S

6

16

Case: I have a list of items of Class X displayed using Editor Template for Class X.

Problem: How can I get index of an item being processed on the inside of the Editor Template?

Scissor answered 19/6, 2013 at 17:42 Comment(0)
A
15

I've been using this HtmlExtension that returns only the needed id of an iteration. It's basically a regex on ViewData.TemplateInfo.HtmlFieldPrefix that's capturing the last number.

public static class HtmlExtensions
    public static MvcHtmlString Index(this HtmlHelper html)
    {
        var prefix = html.ViewData.TemplateInfo.HtmlFieldPrefix;
        var m = Regex.Match(prefix, @".+\[(\d+)\]");
        if (m.Success && m.Groups.Count == 2) 
            return MvcHtmlString.Create(m.Groups[1].Value);
        return null;
    }
}

Can be used in an EditorFor-template like this:

@Html.Index()
Argyle answered 24/2, 2016 at 7:48 Comment(0)
L
5

Use a for loop instead of for each and pass the indexer into the EditorFor extension; razor should handle the rest.

@for(var i = 0; i < Model.count(); i++)
{
    @Html.EditorFor(m => Model.ToArray()[i], new { index = i })
}

Update:

pass in the the index of the item using view data as show above.

In your editor template access the item via the ViewBag

<span> Item Index: @ViewBag.index </span>
Liquidambar answered 19/6, 2013 at 17:51 Comment(8)
well, but how can I know the "i" inside the EditorTemplate?Scissor
If you use @Html.EditorFor in the EditorTemplate you will not need to know the index of the item, Razor will name the elements appropreatly so they will post back into a collection. If however you want to use the index in another manner you can pass it in the ViewData. See Edits.Liquidambar
This will work! :) The only thing I wonder, that the index is there. Razor knows it. How can one get it out?Scissor
What do you mean "Get it out" and what would you do with it?Liquidambar
"Get it out" = access it. What I am trying to do is a long story and is not for this post. I can say that it involves maintenance of a big application and there is only so much freedom I have to modify anything.Scissor
FYI - It does work but it this method will not pass through the name of the model. In my case, I was passing into the editor model.EventField so names were generated like "EventField[0].PayerName". It changed to "[0].PayerName" and caused binding issues.Farrow
I don't think this is a good solution as you're using a loop in a view and Razor is powerful enough to avoid that. Please see my answer below.Lashawna
This will not produce the correct name/id attributes in the markupPeti
L
5

Using the EditorTemplate is the best solution when viewing models that contain a list of something.

In order to find the index for the sub-model being rendered you can use the property that Razor sets by default:

ViewData.TemplateInfo.HtmlFieldPrefix

Say, for example, you have the following view models:

public class ParagraphVM
{
    public int ParagraphId { get; set; }
    public List<LineVM> Lines { get; set; }
}

and

public class LineVM
{
    public int Id { get; set; }

    public string Text {get; set;}
}

and you want to be able to edit all the "LineVM" within a "ParagraphVM". Then you would use an Editor Template so you would create a view at the following folder (if it doesn't exist) with the same name as the sub-model Views/Shared/EditorTemplates/LineVM.cshtml:

@model MyProject.Web.MVC.ViewModels.Paragraphs.LineVM
@{
     //this will give you the List's element like Lines[index_number]
     var field = ViewData.TemplateInfo.HtmlFieldPrefix;
}
<div id="@field">
    @Html.EditorFor(l => l.Text)
</div>

Assuming you have a Controller's ActionResult that is returning a View and passing a ParagrapghVM viewmodel to a view, for example Views/Paragraph/_Paragraph.cshtml:

@model MyProject.Web.MVC.ViewModels.Paragraphs.ParagraphVM

@using (Html.BeginForm("Details", "Paragraphs", FormMethod.Post))
{
    @Html.EditorFor(p => p.Lines)
}

This view would render as many editors for the list Lines as items contains that list. So if, for example, the property list ParagraphVM.Lines contains 3 items it would render something like:

<div id="#Lines[0]">
   <input id="Lines_0__Text name="Lines[0].Text"/>
</div>
<div id="#Lines[1]">
   <input id="Lines_1__Text name="Lines[1].Text"/>
</div>
<div id="#Lines[2]">
   <input id="Lines_2__Text name="Lines[2].Text"/>
</div>

With that you can know exactly what position each items is within the list and for example use some javascript to create a carousel or whatever you want to do with it. But remember that to edit that list you don't really need to know the position as Razor takes care of it for you. If you post back the model ParagraphVM, the list Lines will have the values bound (if any) without any additional work.

Lashawna answered 26/5, 2015 at 2:49 Comment(0)
T
5

How about:

@using System
@using System.Text.RegularExpressions

var i = Convert.ToInt32(Regex.Matches(
             ViewData.TemplateInfo.HtmlFieldPrefix,
             @"\[([0-9]+)?\]")[0].Groups[1].ToString());
Tychonn answered 20/8, 2015 at 13:26 Comment(0)
S
3

I think the easiest way is:

@Regex.Match(ViewData.TemplateInfo.HtmlFieldPrefix, @"(?!\[)\d+(?=\])")

Or as helper:

    public static string Index(this HtmlHelper html)
    {
        Match m = Regex.Match(html.ViewData.TemplateInfo.HtmlFieldPrefix, @"(?!\[)\d+(?=\])");
        return m.Success ? m.Value : null;
    }

Inspired by @Jona and @Ryan Penfold

Solorzano answered 27/6, 2016 at 13:18 Comment(0)
E
2

You can use @Html.NameFor(m => m.AnyField). That expression will output the full name property including the index. You could extract the index there...

Equisetum answered 1/4, 2015 at 12:44 Comment(1)
that's valid, but there is a better option to extract it directly. See my answer below.Lashawna

© 2022 - 2024 — McMap. All rights reserved.