Well of course the correct code for DisplayTemplates would be @Html.EditAttributes(Html.ViewData.ModelMetadata.PropertyName) without the @ but that doesn't fix the problem ;)
In case you use PropertyFor to render your properties there is a context class that keeps track of which property/content that is currently rendered. See http://world.episerver.com/Modules/Forum/Pages/Thread.aspx?id=65101
Note: However that this is kind of internal stuff, meaning we do not consider it part of public API so it might get changed in upcoming releases.
Unfortunately a PropertyFor does not fix this either.
My views looks something like this
Property on my Page Type:
public virtual MyBlock MainBlock { get; set;}
Block Type:
public class MyBlock : BlockData
{
public virtual string MainContent { get; set; }
}
View for Page - since I don't want a flyout-editor for this, I use DisplayFor:
@model MyPage
@Html.DisplayFor(m => m.MainBlock)
View for Block:
@model MyBlock
@Html.PropertyFor(m => m.MainContent)
This still renders data-epi-property-name="MainContent".
I am using EPiServer 7.1 and Patch 3.
To replicate this in Alloy Templates you can easily browse to /About-us/News-Events/Events/ for example
Change /Views/NewsPage/Index.cshtml:
@Html.PropertyFor(x => x.CurrentPage.NewsList)
to
@Html.DisplayFor(x => x.CurrentPage.NewsList)
Browse to Events-page again and check the HTML for your h2 with the text Upcoming events. This h2-tag has @Html.EditAttributes(x => x.Heading) which renders data-epi-property-name="PageListHeading".
Change /Views/PageListBlock/Index.cshtml
Add following:
@Html.PropertyFor(m => m.Heading)
This does also render data-epi-property-name="PageListHeading".
Note that this View use a model called PageListModel and not the Block Type. I have tried to change the Controller and View for PageListBlock to use PageListBlock instead of the PageListModel. This still render PageListHeading and not NewsList.PageListHeading.
By the way, it would be nice if a Block could know more about its context, such as what its PropertyName would be if placed on a Page.
Is it possible to add the PropertyName to Html.ViewData.ModelMetadata.PropertyName as it is done on DisplayTemplates?
For example if I would like to add a unique id when rendering my Block even if it is placed several times on a Page Type.
If it is a local block I could use the PropertyName, if it is a Shared Block I could use the ContentLink Id or Name from the IContent Mixin.
The purpose of the ContentContext is exactly that you should be able get the name of the current property rendered and the current content rendered (can be e.g. a property on a shared block rendered in a content area).
When you use PropertyFor the ContentContext is updated, but if you use DisplayFor you need to add that information to the context. You can do it in the view like:
@{ EPiServer.Web.Mvc.ContentContext.PushCurrentProperty(Request.RequestContext, "MainBlock"); }
@Html.DisplayFor(m => m.MainBlock)
@{ EPiServer.Web.Mvc.ContentContext.PopCurrentProperty(Request.RequestContext); }
but you get of course nicer views if you create your own extension method like:
public static MvcHtmlString MyDisplayFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
string editableProperty = PropertyRenderer.GetPropertyName<TModel, TValue>(expression);
ContentContext.PushCurrentProperty(html.ViewContext.RequestContext, editableProperty);
var output = html.DisplayFor(expression);
ContentContext.PopCurrentProperty(html.ViewContext.RequestContext);
return output;
}
Then you can call it from the view like:
@Html.MyDisplayFor(m => m.MainBlock)
This does not work for me.
My Page View now looks like this:
@{ EPiServer.Web.Mvc.ContentContext.PushCurrentProperty(Request.RequestContext, "MainBlock"); }
@Html.DisplayFor(m => m.MainBlock)
@{ EPiServer.Web.Mvc.ContentContext.PopCurrentProperty(Request.RequestContext); }
while my Block View looks like this:
@Html.EditAttributes(m => m.MainContent)
@Html.PropertyFor(m => m.MainContent)
I'm rendering EditAttributes outside a HTML-element only to see what the result would be.
The result from EditAttributes are: data-epi-property-name="MainContent" data-epi-use-mvc="True"
and from PropertyFor only gives a div container with these attributes: class="epi-editContainer" data-epi-use-mvc="True" without a data-epi-property-name attribute.
If I remove the Push and PopCurrentProperty lines, the PropertyFor will render with a data-epi-property-name="MainContent"
EditAttributes is kind of "stupid", it will always output attributes. So in block views (since they can be rendered nested inside another property) it is better to use BeginEditSectcion/EndEditSection. Like:
<%: Html.BeginEditSection("div", p => p.TextBlock) %>>
<%: Html.Partial("TextBlock", Model.TextBlock )%>
<%: Html.EndEditSection("div")%>>
Same behaviour using BeginEditSection and EndEditSection as PropertyFor.
Without PushCurrentContext the data-epi-property-name is "MainContent".
With PushCurrentContext, data-epi-property-name is not added at all.
That is expected that a property should not render any data-epi-property-name when it is nested in an outer property. If you have a block named e.g. MainBlock as a property on a page and say that there is a property is MainBody on the block. When rendered the expectation would be that you get data-epi-property-name outputted for MainBlock and nothing for MainBody. Then during editing of MainBlock a flyout should appear with all properties for the block.
But now I think I understand what you want to do: You like to directly edit the block properties on the page, right? Then you would need to prevent data-epi-property-name to be outputted for MainBlock but you also want data-epi-property-name to be MainBlock.MainBody for the block properties...
I do not think there is anything in EPiServer that will help you with this. I think you need to add the name of the outer property to the context in the main view (where you know the name of the block property). And then later on in the view used for Block fetch the name, something like:
outer view:
@{
ViewBag.OuterPropertyName = "MainBlock.";
}
@Html.DisplayFor(m => m.CurrentPage.MainBlock)
block view:
@{ var propertyName = @ViewBag.OuterPropertyName as string + "BlockBody";}
<div @Html.EditAttributes(propertyName)>
@Html.DisplayFor(m => m.BlockBody)
</div>
Then it outputs attribute as expected in markup but for me it then blows in the RenderPropertyController... So it does not seem to be possible to edit a block property directly on the page instead you must edit the block as whole.
Yes that's exactly what I want to do, maybe I should have mentioned that in the beginning.
My idea was that the block should be kind of independent and that I should not add extra information from the View. It felt that an flyout would be a bit overkill since the editor mostly want to edit a single property on the block.
But maybe that doesn't align with how the Blocks are supposed to work in EPiServer.
Thanks for the time and explanations though.
So how the story ends? Was you able to make this approach to work?
I've noticed that when I'm using @Html.EditAttributes(...), the input must be exact to render the data-epi-property-name correctly.
This can be quite problematic when rendering Partials such as blocks or properties with DisplayTemplates.
For example if I have a DisplayTemplate that has a model XHtmlString from a property called "MainBody" and I have to use @Html.EditAttributes("MainBody"), not @Html.EditAttributes(m => m) since it will throw a FormatException, nor can I use @Html.EditAttributes(m => Model) since it will render the text data-epi-property-name="Model" data-epi-use-mvc="True".
The trick is that I have to find what the PropertyName of the XhtmlProperty that will be rendered.
The only way I've found is: @Html.EditAttributes(@Html.ViewData.ModelMetadata.PropertyName) to render data-epi-property-name="MainBody" data-epi-use-mvc="True".
Another example is when I have a Local Block on a Page Type. In this example I have a Property called MainBody on a Block called MyBlock on my PageType.
The correct set of attributes I need is data-epi-property-name="MyBlock.MainBody" data-epi-use-mvc="True".
The Model is defined as the Block Type but I cannot use @Html.EditAttributes(m => m.MainBody) since it will render data-epi-property-name="MainBody" data-epi-use-mvc="True".
The only way I've found while digging around is to do the following:
This will create the string "MyBlock.MainBody" but I am far from sure how secure and optimized this is.
So my question is, are there any better ways to find these values? If not, could you please make it simpler?