Ticket (Solved)

How to: Create custom Zones in your views/shapes to render a Widget?

Re: http://www.ideliverable.com/blog/a-closer-look-at-content-types-drivers-shapes-and-placement zoneHolding: "…If the Layout shape were a regular shape (deriving from Shape) then you would have to first new up the zone shape you want to add, and then assign it to a property on the layout." This gives me hope that one can add Custom Zones arbitrarily but unsure if it would work with a standard view.cshtml/shape?

To try and get something working [blindly and naively] I edited CoreShapes.cs

        builder.Describe("ProjectionSummaryList")
            .OnCreating(creating => creating.Create = () => new ZoneHolding(() => creating.New.Zone()))
            .OnCreated(created => {
                var projectionSummaryList = created.Shape;
                projectionSummaryList.ProjectionList = created.New.Zone();
                projectionSummaryList.ProjectionList.ZoneName = "ProjectionList";
                projectionSummaryList.ProjectionList.Add(created.New.PlaceChildContent(Source: projectionSummaryList));
            });

Created a ProjectionSummaryList.cshtml view:

@{
    Func<dynamic, dynamic> Zone = x => Display(x); // Zone as an alias for Display to help make it obvious when we're displaying zones
}
This is a: @Model.Test

@Zone(Model.ProjectionList)

Placed the following in Placement.info

Place Parts_FeaturedItems="ProjectionList"/>

But obviously, the widget wouldn’t render, and I wasn’t even sure how to test whether or not the Zone was even being created? Could anyone lend any suggestions as to how I might be able to create Custom Zones in a view? Thanks in advance, Pug

Re: How to: Create custom Zones in your views/shapes to render a Widget?

MY DESIRED OUTCOME: https://i.imagestash.io/y7MzddzwJn.jpg

NOTE: This is not a solution!

As a terrible hack to get something working, but at the expense of editing ease and very foreseeable front end design and backend implementation issues - I have temporarily created the following:

Layouts.cshtml

            @if (Model.Content != null) {
                <div id="content" class="group">
                    @Zone(Model.Content)
                        @* hack due to not being able to delegate widgets to my own custom zones in views *@
                        @if (Model.CustomContentLeft != null)
                        {
                            <div id="CustomContentLeft" style="float: left; width: 50%;" class="group">
                                @Zone(Model.CustomContentLeft)
                            </div>
                        }
                        @if (Model.CustomContentRight != null)
                        {
                            <div id="CustomContentRight" style="float: left; width: 50%;" class="group">
                                @Zone(Model.CustomContentRight)
                            </div>
                        }
                </div>
            }

CustomContentLeft: Contains a widget and is handled by Placement.info

Match DisplayType="ProjectionSummary">
    Place Parts_FeaturedItems_ProjectionSummary="/CustomContentLeft"/> 
/Match>

CustomContentRight: Is populated by the following Projection View:

@using Orchard.ContentManagement
@using Orchard.Layouts.Models

@*This is used/denoted in Queries -> "query" -> Layouts, which basically builds the layout of each page [contained in the projection] as per its detail *@

@{
    var webPages = ((IEnumerable<ContentItem>)Model.ContentItems).ToList();
}

@foreach (var wp in webPages)
{
    var layoutPart = wp.As<LayoutPart>();
    WorkContext.Layout.CustomContentRight.Add(@Display(BuildDisplay(layoutPart, "ProjectionSummary")), "1");
}

This does indeed separate the 'Widget' and associated content from a Layouts -> Projection List into a Left/Right column scenario, but will be plaqued with design and Admin functionality issues. Also note: If my projection list returned just a widget and one page of content per iteration, I could have easily achieved the above with CSS float: left; for the Widget and Content, but as there are varying extra items, this is not possible.

ISSUES:

  • The two physically separate columns, CustomContentLeft and …Right, will have to have some method of keeping two semantically related divs (one in each CustomContent[Left/Right] container) equal to the same height (with varying content) to give the desired appearance of a list; 'a nightmare'.

  • I will have to rely on Layer rules to summarize the content i.e. on some Layout -> Rows, I will have to hide superfluous content depending on Detail or Summary; this will also be a managerial nightmare.

MY CONCERN:

Even If I am able to create custom zones in my views, I still don't believe I will be albe to separate the Widget from the Other content successfully, for in a list using custom zones, there would eventually be many zones with the same name rendered [unless I added an itereator to the name] - but then I would have to premptively code the Placment.info with all of them, etc., etc. - and Orchard, I imagine, would throw an error or not know what to do?

I'm really beginning to believe this is impossible [several weeks have gone by with many 100's of attempts but no solution].

Many thanks for your time, anything you can offer - no matter how small - will be immensely appreciated :)

Thursday, July 28, 2016 2:53:03 AM bypug
  • pug
  • Lv. 04 Rookie
  • Total EXP: 144

Re: How to: Create custom Zones in your views/shapes to render a Widget?

Not sure it can help but here some findings based on our previous tests, with a projection element which lists some pages, and one page with, in my case, a html widget element, so with a body part.

So, without any code, just by using the following, i could create a Foo zone:

    <Place Parts_Common_Body_Summary="Foo:0"/>

But this is a local zone which is dynamically added. This means that you can retrieve it from the Model / Shape of the html widget element. So, if we overidde Widget-HtmlWidget.cshtml view, we can use:

    @Display(Model.Foo)  // Note: @Zone() is an alias of @Display()

See in attachments the foo1 image where you can see, through the Shape Tracing module, the Foo local zone added to the widget element. Note: You can use Shape Tracing to see models / shapes, create alternates ... There are little drawbacks, e.g sometime this is not the right alternate file which is copy / paste.

That said, you can add a more global zone to the Layout by adding a slash like this:

    <Place Parts_Common_Body_Summary="/Foo:0"/>

Then, for testing, i've overidden Content-Page.Summary.cshtml, see the foo2 image, where i could use the following:

    Test: @Display(Layout.Foo)

See foo3 image for the result, on the left you can see Zone [Foo] added. It is there because we used it in the above alternate.

Best.

Thursday, July 28, 2016 4:22:20 AM byjtkech
  • jtkech
  • Lv. 13 Rookie
  • Total EXP: 1028

Re: How to: Create custom Zones in your views/shapes to render a Widget?

First of all, THANK YOU very much for taking such a keen interest jtkech (I have been going slightly crazy by myself trying to sort this -ha). Anyhow, I replicated what you did above and it's unfortunately an absolute nightmare!

MY STEPS [Fresh install] I tried to follow my first images naming convention but with your changes:

+1 Enabled Modules > Widget Elements and Layout - Projection Element.

+2 Created 'WebsitePages' Taxonomy and added: Pages ('Parent'), plus three terms: PageNumeric, PageAlpha, and Summary.

+3 Content Definition > Page > Add Field 'Taxonomy', select 'WebsitePages'.

+4 Created [4] Pages (Page1,..2,..A,..B), all with: Content > Text > 'This is Page #' Widget Element > Html (PageWidget) with text 'Page Html Widget' And selected appropriate Taxonomy e.g. Page1,2 = Numeric.

+5 Create Query > 'SummaryPageAlpha' > Edit > Filter > Taxonomy, Has Terms 'PageAlpha'. [Do the same for SummaryPageNumeric]

+6 Create Page 'SummaryPageAlpha' with Projection Element using 'SummaryPageAlpha Query' [Do the same for SummaryPageNumeric]

+7 Create Query > 'IndexPage' > Edit > Filter > Taxonomy, Has Terms 'Summary'.

+8 Create Page 'IndexPage' and add Projection Element with 'IndexPage' query.

+9 Add Your placement: Place PartsCommonBody_Summary="/Foo:0"/> And override: Widget-HtmlWidget.cshtml

[RESULTS]

Before I even attempted to format the IndexPage/Summary via Content-Page.Summary.cshtml, I noted: SummaryPageAlpha and SummaryPageNumeric behaving erroneously immediately. i.e. As Orchard is building the view and attaching the widget to Foo, it begins to replicate, hence the more pages you have Page3,4,5,6 you will have that# of Widgets attached in each iteration?

So I suspected that when adding Content-Page.Summary.cshtml with display foo, it would behave the same, but display 4 widgets in each summary [due to the 2 pages being built at first depth, then when projected again, built again x 2], alas, I was disappointingly right [2x2]: [both results] https://i.imagestash.io/eEoLZRvOWK.jpg

yikes you say, ha - seriously though, what do you think - pretty scary isnt it? Best regards, Pug

Friday, July 29, 2016 2:43:08 AM bypug
  • pug
  • Lv. 04 Rookie
  • Total EXP: 144

Re: How to: Create custom Zones in your views/shapes to render a Widget?

Indeed, you're right, i talked too quickly. When we use /Foo:x with a slash and then use @Display(Layout.Foo), widget elements will be rendered on each page summary execution ...

So, here we have better to use Foo:x without slash, then e.g in Widget-HtmlWidget.cshtml we can use @Display(Model.Foo) ...

But, if I understand correctly (based on what we have already done in others tickets), in your list, for each page (displayType = summary), you want to define the placement of your widget element as you can do e.g for the Parts_Title_Summary. But your widget element is an inner element of the layout part, here the Parts_Layout_Summary shape (because of our previous changes).

So, if in this context, from the layout part, you only want to display the widget element, the 1st idea i see here is to use this placement: <Place Parts_Layout_Summary="Foo:0"/>. And then use @Display(Model.Foo) in your page summary override.

UPDATE: other useful infos here.

Best.

Friday, July 29, 2016 4:30:58 AM byjtkech
  • jtkech
  • Lv. 13 Rookie
  • Total EXP: 1028

Re: How to: Create custom Zones in your views/shapes to render a Widget?

Thanks jtkech, I'm trying to replicate the above into the actual project - before doing so - I've noted that my Slider Widget doesnt appear as normal widgets when traced i.e. it doesnt follow the pattern Element > Widget > FeaturedItemSliderWidget [but It has the widget part attached and stereotype set as widget]; could this be indicative of something amiss: https://i.imagestash.io/qWwrDjyM6L.jpg Or does Orchard jsut go by how the view was named - and if so - should I rename it to Widget.FeaturedItemSlider.cshtml

[UPDATE:] Following on from that, I note the Migration is:

        SchemaBuilder.CreateTable("FeaturedItemSliderWidgetPartRecord", builder => builder
            .ContentPartRecord()
            .Column<string>("GroupName", col => col.WithLength(100)));

        ContentDefinitionManager.AlterTypeDefinition("FeaturedItemSliderWidget", builder => builder
                .WithPart("FeaturedItemSliderWidgetPart")
                .WithPart("CommonPart")
                .WithPart("WidgetPart")
                .WithPart("IdentityPart")
                .WithSetting("Stereotype", "Widget")
            );

Yet, checking some Orchard default Widgets, they use:

.AsWidgetWithIdentity()

Perhaps this is the culprit [though I think I read it's just a helper to achieve what i've done]?

Also [Back on topic], I'm quite confused as to how I'm going to replicate your findings with the FeaturedSlider, namely - If I try to attach Foo zone:

Place Parts_FeaturedItems="Foo:0"/>

And then override the Widget slider, it is a lot more involved than just a @Display(model.content)…change to @Display(Model.Foo), so am I to amend the FeatureWidgets driver to send the constructed shape to Foo? I'm so sorry for being so dense, Jtkech - but I think I've been at it that long I'm confusing issues?

Friday, July 29, 2016 8:44:45 AM bypug
  • pug
  • Lv. 04 Rookie
  • Total EXP: 144

Re: How to: Create custom Zones in your views/shapes to render a Widget?

No worries, me too i'm a little lost :)

In one of your images i see that your Parts_FeaturedItems shape is in the Featured layout zone. Here, layout means the document layout where zones, defined in the current theme, are rendered. This would mean that you have placed your widget in the Featured zone, this through the admin Widgets page. Or maybe by using in placement file: Parts_FeaturedItems: /Featured:x.

But i thought that your widget element was in a layout part. Here, layout means the layout part that belongs to the Page type. When you edit it you can place elements where you want, but, in the front end, all layout (part) elements will be rendered in the Content zone of the (document) layout ...

So, something needs to be clarified here. But if you really use a projection of pages, then you can define the placement for e.g the title part ... and the layout part but not for its inner elements.

Note: the following is based on your previous changes where you pass the right DisplayType to the layout inner elements and where you override Parts.Layout.Summary.cshtml (copy / paste of Parts.Layout.cshtml).

Then, in a projection summary list, as in my last proposition with <Place Parts_Layout_Summary="Foo:0"/>, you can place the layout part in a custom zone, and then use @Display(Model.Foo) in a page summary override .... Then you have to implement the summary alternate of your widget element and maybe hide some other elements by providing some empty alternates in your theme as e.g Elements.Html.Summary.cshtml.

Best

Saturday, July 30, 2016 12:11:00 AM byjtkech
  • jtkech
  • Lv. 13 Rookie
  • Total EXP: 1028

Re: How to: Create custom Zones in your views/shapes to render a Widget?

Sorry - yes, I have confused you. I was only showing you the Featured Zone [where I do use just a widget and not a widget within a layout] as I was expecting the structure to be something like: Zone [Featured] > Widget > Parts_FeaturedItems - and it wasn’t; 'just thought it may've revealed something amiss, that's all'.

Anyhow, back on topic - yes - I do wish to achieve the latter [as per my walkthrough and original image] where I can separate the Widget from the layout into a left column and the remaining contents of that layout into a right column.

So the closest I have got […prior just forcing the zones manually in Layout.cshtml (which is complelely impractical) was:

Orchard.Web\Modules\Orchard.Layouts\Drivers\LayoutPartDriver.cs [...with our addition]

            ContentShape("Parts_Layout_ProjectionSummary", () => {
                if (DetectRecursion(part, "Parts_Layout"))
                    return shapeHelper.Parts_Layout_Recursive();

                var elements = _layoutManager.LoadElements(part);
                var layoutRoot = _elementDisplay.DisplayElements(elements, part, displayType: displayType);
                return shapeHelper.Parts_Layout(LayoutRoot: layoutRoot, displayType: displayType);
            }));

...

Placement>
    Place Parts_FeaturedItems_ProjectionSummary="/SliderProjSummary:0"/>
/Placement>

For the projection [individual pages] of the projection [summary of pages]: elements.projection.projectionsummary.cshtml

@using Orchard.ContentManagement
@using Orchard.Layouts.Models

@{
    var list = Model.List;
    var pager = Model.Pager;

    if (list != null)
    {
        var webPages = ((IEnumerable<ContentItem>)list.ContentItems).ToList();

        foreach (var wp in webPages) {
            var layoutPart = wp.As<LayoutPart>();
        <div class="bisite-wrapper">
            <div id="zone-bisite-slider" style="float: left; width: 45%;">
                @Display(WorkContext.Layout.SliderProjSummary) @*works with /SliderProjSummary:0*@
            </div>
            <div id="zone-bisite-info" style="float: left; width: 45%;">
                @Display(BuildDisplay(layoutPart, "ProjectionSummary"))
            </div>
        </div>
        }
    }
}

...The Query for the Index Summary used in the Parent Projection https://i.imagestash.io/aJM6n9RMyA.jpg

websitePagesListSummary.cshtml

@using Orchard.ContentManagement
@using Orchard.Layouts.Models

@*This is used/denoted in Queries -> "query" -> Layouts, which basically builds the layout of each page [contained in the projection] as per its detail *@

@{
    Model.Metadata.Wrappers.Clear(); // Most likely redundant
    var webPages = ((IEnumerable<ContentItem>)Model.ContentItems).ToList();
}

@foreach (var wp in webPages)
{
    var layoutPart = wp.As<LayoutPart>();

    @Display(BuildDisplay(layoutPart, "ProjectionSummary"))

    @*This worked*@
    //WorkContext.Layout.CustomContentRight.Add(@Display(BuildDisplay(layoutPart, "ProjectionSummary")), "1");
}

But as per our finding 'when attaching to Layout', the widgets are duplicated on each iteration when you view the top most projection.

Is that what you were anticipating as my meaning/desire? So from here, I wish to attempt your last statement:

Note: the following is based on your previous changes where you pass the right DisplayType to the layout inner elements and where you override Parts.Layout.Summary.cshtml (copy / paste of Parts.Layout.cshtml).
Then, in a projection summary list, as in my last proposition with <Place Parts_Layout_Summary="Foo:0"/>, you can place the layout part in a custom zone, and then use @Display(Model.Foo) in a page summary override .... Then you have to implement the summary alternate of your widget element and maybe hide some other elements by providing some empty alternates in your theme as e.g Elements.Html.Summary.cshtml.

…but I think I'll need to trouble you with some further clarification with those sentences, as I right royally ran into trouble with errors, and even after reverting back, receive errors with my old code (something must have corrupted as lots of my razor views have red Display / Model / Contentitem etc. highlighted in the code now; though I believe that is a Resharper issue :). I'm getting really embarrassed over this, including all the time you're investing - so sorry, thanks Pug

Saturday, July 30, 2016 8:44:49 AM bypug
  • pug
  • Lv. 04 Rookie
  • Total EXP: 144

Re: How to: Create custom Zones in your views/shapes to render a Widget?

No worries, me too i have to discover again things step by step. No worries for the red lines, sometimes i have the same.

1st i'm happy to understand why your widget element was, for testing, in the Featured layout zone.

Good to use a projection layout with a specific ProjectionSummary displayType. But by using a Shape Layout you have to build yourself the necessary child shapes. See in Orchard.Projections the difference between ListLayout.cs and ShapeLayout.cs. I already done such alternates where i had to parse inner parts / elements but not sure it's necessary here.

So, here i've used a List Layout, just to use the ProjectionSummary displayType. Then i reverted our change in LayoutPartDriver, but i kept the change in WidgetElementHarvester to have the right displayType for the widget element. So, i rather used the placement file in my theme.

    <Match DisplayType="ProjectionSummary">  // when this displayType.
      <Place Parts_Layout="Foo:0"/>  // use this one and put it in the Foo zone without a slash "/".
      <Place Parts_Layout_Summary="-"/>  // hide this one which only displays an excerpt ...
      <Place Parts_Common_Body="Content:2"/> // use this one for my html widget element
      <Place Parts_Common_Body_Summary="-"/> // but i could use this one
      <Place Parts_Title_Summary="Header:5"/> // this to render the title in this displayType
    </Match>

Note that the custom displayType of the projection layout is not used for the projection itself but for each content listed and their inner elements. At least in the 1st place when you display your index page which have the projection element, but by recursivity this could happen but normally this is filtered. Note that before reverting the change in LayoutPartDriver the recursivity filtering was not working for me, now it's ok. But maybe here i misunderstood you needs.

So, in my tests, i didn't use any projection element overrides, i also reverted the change in Parts.Layout.Summary.cshtml ... And, in my theme, i only overrided Content-Page.ProjectionSummary.cshtml where i've used:

    ...
    ...
    @if (Model.Footer != null) {
      <footer>
        @Display(Model.Footer)
      </footer>
    }

    @if (Model.Foo != null) {
      <div class="test">
        @Display(Model.Foo)
      </div>
    }

In your case, you may have to add this in the above placement file

    <Match DisplayType="ProjectionSummary">  // when this displayType.
      ...
      <Place Parts_FeaturedItems="-"/> // check if needed
      <Place Parts_FeaturedItems_Summary="-"/>  // you could use this one ...
      <Place Parts_FeaturedItems_ProjectionSummary="Content:2"/> // ... if you don't need this one
      ...
    </Match>

Note that we have put all the LayoutPart in the Foo zone, not the FeaturedItems which will be in the local content zone of the widget element which is in the layout part ...

So, in the ProjectionSummary display type, in the Content-Page.ProjectionSummary.cshtml, we can render the LayoutPart where we want. And this LayoutPart will render your widget element. If you want to render or hide some other elements, you can add in your theme some custom or empty alternates as e.g Elements.Html.ProjectionSummary.cshtml.

Best.

Sunday, July 31, 2016 7:19:17 AM byjtkech
  • jtkech
  • Lv. 13 Rookie
  • Total EXP: 1028

Re: How to: Create custom Zones in your views/shapes to render a Widget?

Ok, so I have mostly replicated what you have except for the last paragraph where I got a little confused: . Content-Page.ProjectionSummary.cshtml

@*was  Content.Summary.cshtml  *@

@using Orchard.Utility.Extensions;
@{
    var contentTypeClassName = ((string) Model.ContentItem.ContentType).HtmlClassify();
}
<article class="content-item @contentTypeClassName">
<header>
    @Display(Model.Header)
    @if (Model.Meta != null) {
        <div class="metadata">
            @Display(Model.Meta)
        </div>
    }
</header>
@*@Display(Model.Content)*@
@if (Model.Footer != null) {
    <footer>
        @Display(Model.Footer)
    </footer>
}

@if (Model.Foo != null) {
    <div class="test">
        @Display(Model.Foo)
    </div>
}
@--CONFUSED--Here, Model.Content is Empty?
<div class="test-Featured">
    @Display(Model.Content)
</div>
</article>

Which results in:

https://i.imagestash.io/mnwRQA7OQZ.jpg

Where I'm confused is how I will be able to display/reference individual FeaturedItem Sliders in order to show/hide them to achieve the goal of:

https://i.imagestash.io/ZLOvdZKo3p.jpg

I believe your intent was to use Place Parts_FeaturedItems="-"/> so there would be just the text without the sliders in the ProjectionSummary, then somehow show the Slider via Model.content - but I'm not quite following how to achieve that? Thanks ever so much as always, Pug

Sunday, July 31, 2016 1:56:06 PM bypug
  • pug
  • Lv. 04 Rookie
  • Total EXP: 144

Re: How to: Create custom Zones in your views/shapes to render a Widget?

Oups, just realized that my last proposition is quite useless because we only put the layout part in a local Foo zone, this in place of the local Content zone, that's why you didn't have anything in Model.Content.

I thought that you only want to place the layout part (with its widget element) in a custom zone, this to place it correctly in relation to other content parts (e.g title part) but not in relation to others elements which belong to the same layout part.

So, it seems that you really need to extract the widget element. Maybe a solution by putting your widget part in a global zone as we already did, but by also checking the content item id this to only show the one related to the current page content being rendered. I'll take a look on this.

Best.

Sunday, July 31, 2016 8:05:01 PM byjtkech
  • jtkech
  • Lv. 13 Rookie
  • Total EXP: 1028

Re: How to: Create custom Zones in your views/shapes to render a Widget?

So, i got something that seems to work.

I reverted back to a regular placement for my body part.

    <Place Parts_Common_Body="Content:2"/>

I overrided Elements.Widget.ProjectionSummary.cshtml.

    @using Orchard.Layouts.Helpers
    @{
        var tagBuilder = TagBuilderExtensions.CreateElementTagBuilder(Model);
    }
    @tagBuilder.StartElement
    @if (Model.WidgetShape != null) {
        if (Model.Widget.ContentType == "HtmlWidget") {
            Layout.Foo = Model;
        }
        else {
            @Display(Model.WidgetShape)
        }
    }
    @tagBuilder.EndElement

Then in Content-Page.ProjectionSummary.cshtml. Quickly done, maybe need more null checking ...

    ...
    @if (Layout.Foo != null && Model.ContentItem.Id == Layout.Foo.ContentItem.Id) {
        @Display(Layout.Foo.WidgetShape)
    }

This works if you do Display(Model.Content) before. If you want to do it after, you can use this.

    ...
    @{
        ...
        // do this at the beginning ...
        var content = Display(Model.Content);
    }
    ...
    @if (Layout.Foo != null && Model.ContentItem.Id == Layout.Foo.ContentItem.Id) {
        @Display(Layout.Foo.WidgetShape)
    }
    ...
    // ... then use it where you want
        @content
    ...

Best.

Sunday, July 31, 2016 9:06:48 PM byjtkech
  • jtkech
  • Lv. 13 Rookie
  • Total EXP: 1028

Re: How to: Create custom Zones in your views/shapes to render a Widget?

If it's better for your placement, in place of overriding Content-Page.ProjectionSummary.cshtml you could override Elements.Projection.cshtml (note: displayType is Detail in the 1st place). Then use something like this:

    @{
        var list = Model.List;
        var pager = Model.Pager;

        if (list != null) {
            foreach (var item in list.Items) {

             var renderedItem = Display(item);
             dynamic renderedWidget = null;

             // and/or other logic, e.g you can render the widget only once in the list
             if (Layout.Foo != null && item.ContentItem.Id == Layout.Foo.ContentItem.Id) {
                  renderedWidget = Display(Layout.Foo.WidgetShape);
             }

             // then display shapes where you want
             @renderedItem
             @renderedWidget            
           }
        }

        if (pager != null) {
            @Display(pager)
        }
    }

Best.

Sunday, July 31, 2016 10:48:17 PM byjtkech
  • jtkech
  • Lv. 13 Rookie
  • Total EXP: 1028

Re: How to: Create custom Zones in your views/shapes to render a Widget?

Just to clarify, jtkech: I've made your recent alterations and suspect we may be testing different scenarios; for I receive zero output with that setup (but most likley placement issues?).

Are you testing a ['Projection'] of many [Projection's (of pages)] - for if you are testing just a projection of pages I believe I could see how you might override:

Elements.Widget.ProjectionSummary.cshtml But I could never override that view in mine, only:

Elements.Widget.Detail.cshtml From the inner [projection of pages (detail)] as per:

https://i.imagestash.io/39ojVWXoD2.jpg

I am trying to replicate the original 'My Desired Outcome' image, but instead of using FeaturedItem Sliders I'm now using Html Widgets as per your testing for continuity.

...I'm thinking I should setup a site you can access or [I'm not experienced with github] some sort of respository you and I can both edit discuss so we are not comparing apples to bananas etc. haha; that's if we are, and I'm not misinterpreting your last solution [also some sort of compensation should be discussed for all your hardwork?]

Monday, August 1, 2016 12:13:42 AM bypug
  • pug
  • Lv. 04 Rookie
  • Total EXP: 144

Re: How to: Create custom Zones in your views/shapes to render a Widget?

Oh, i was just using a projection (projection element in a layout part) which is a projection of pages. But not a projection of projections ... I see above that you have a List just under Zone [Content], so maybe you are using a Projection Page. Just tried to create a Projection Page but i couldn't get a List directly under Zone [Content] and with the ProjectionSummary displayType applied to the main projection (only for the child content shapes) ...

Indeed, with a github account we could share a repo for testing. But what you can do is to use my github account jtkech to send me an email, then we will be able to exchange infos. E.g if you have a version with a sqlce database that i can run on my local, you can attach to your email a zip file of this version with maybe some more directions on what i have to try ...

Note: can't right now but i'll take a look in the coming days.

Best.

Monday, August 1, 2016 12:55:21 AM byjtkech
  • jtkech
  • Lv. 13 Rookie
  • Total EXP: 1028

Re: How to: Create custom Zones in your views/shapes to render a Widget?

I exactly reproduced your above scenario where you have 9 steps (but not the last one), this to have a projection of projections in an IndexPage. For all queries i've used an html list layout (not a shape layout) with the ProjectionSummary displayType.

Then use this placement.info:

<Match DisplayType="ProjectionSummary">
      <Place Parts_Layout="Content:5"/>
      <Place Parts_Layout_Summary="-"/>
      <Place Parts_Common_Body="Content:2"/>
      <Place Parts_Common_Body_Summary="-"/>
      <Place Parts_Title_Summary="Header:5"/>
    </Match>

Then i overrided Elements.Widget.ProjectionSummary.cshtml.

  @using Orchard.Layouts.Helpers
  @{
      var tagBuilder = TagBuilderExtensions.CreateElementTagBuilder(Model);
  }
  @tagBuilder.StartElement
  @if (Model.WidgetShape != null) {
      if (Model.Widget.ContentType == "HtmlWidget") {
          Layout.MyWidget = Model;
      }
      else {
          @Display(Model.WidgetShape)
      }
  }
  @tagBuilder.EndElement

Now i have a projection of projections, so the inner projections (as contents of the main projection) now use the ProjectionSummary displayType. So i've overrided Elements.Projection.ProjectionSummary.cshtml with this testing code:

    @{
      var list = Model.List;
      var pager = Model.Pager;

      if (list != null) {

          dynamic widget = null;
          var renderedWidget = false;
          List<dynamic> contents = new List<dynamic>();

          foreach (var item in list.Items) {
              contents.Add(Display(item));

              if (Layout.MyWidget != null
                  && item.ContentItem.Id == Layout.MyWidget.ContentItem.Id
                  && !renderedWidget) {
                  renderedWidget = true;
                  widget = Display(Layout.MyWidget.WidgetShape);
              }
          }

          <div>
              @if (widget != null) {
                  <div style="float:left;margin-right:20px">
                      @widget
                  </div>
              }
              <div style="float:left">
                  @foreach (var content in contents) {
                      @content
                  }
              </div>
          </div>
      }

      if (pager != null) {
          @Display(pager)
      }
  }

See the result in the attached image, does it fit your need?

Note: remember that i reverted our change in LayoutPartDriver ...

UPDATE: we use Layout.MyWidget as a temporary storage, maybe better to use the TempData dictionary as the following:

    TempData["MyWidget"] = Model;
    TempData.ContainsKey("MyWidget")
    Display(((dynamic)TempData["MyWidget"]).WidgetShape)

Best.

Attachments
Tuesday, August 2, 2016 3:19:01 AM byjtkech
  • jtkech
  • Lv. 13 Rookie
  • Total EXP: 1028

Re: How to: Create custom Zones in your views/shapes to render a Widget?

Wow, Wow…WOW! That looks amazing - I can't wait to get home and try it out. The only thing by looking at the code that has me a little apprehensive is in the:

Elements.Widget.ProjectionSummary.cshtml

if (Model.Widget.ContentType == "FeaturedItemSliderWidget")//""HtmlWidget")
{
    Layout.MyWidget = Model;
}

…as in, how will the slider(s) display in the Child Projection, as wont it be added to the MyWidget Zone all the time [as Both Projections use DisplayType:ProjectionSummary]? I could be wrong as the code is confusing me a little just by looking at it, but once I get home I will let you know how it goes. So thankful for all your problem solving / trouble shooting jtkech, truly amazing and beyond anything I could believe or hope for. THANK YOU, truly unbelievable work! Pug

Tuesday, August 2, 2016 11:53:30 AM bypug
  • pug
  • Lv. 04 Rookie
  • Total EXP: 144

Re: How to: Create custom Zones in your views/shapes to render a Widget?

...Yeah, as the widget is always intercepted and added to 'MyWidget', it will never display in the primary projection. I have been trying to debug through the Model to see if I can access the topmost parent, but it stops at 'Canvas', in an attempt to make the IF conditional when in the Secondary Projection. I've also toyed with the first Projection having a displayType of Detail, the second still being ProjectionSummary, but need some way of passing the displayType through Elements.Projection.ProjectionSummary.cshtml to Elements.Widget.cshtml:

if (Model.Widget.ContentType == "FeaturedItemSliderWidget" && DisplayType=="ProjectionSummary")
{
    Layout.MyWidget = Model;
}

Have you any ideas on how to differentiate the Child and Parent Projections? This is amazingly trying isn't it, ha!

Wednesday, August 3, 2016 2:44:29 AM bypug
  • pug
  • Lv. 04 Rookie
  • Total EXP: 144

Re: How to: Create custom Zones in your views/shapes to render a Widget?

Quick answer so not sure.

So, i think it's because the display type of the primary projection is Detail even if the display type of each of its contents is ProjectionSummary. So, try to do what i proposed but override Elements.Projection.cshtml in place of Elements.Projection.ProjectionSummary.cshtml.

UPDATE: after, maybe better to also use the TempData dictionary as mentioned above.

Best.

Wednesday, August 3, 2016 3:02:04 AM byjtkech
  • jtkech
  • Lv. 13 Rookie
  • Total EXP: 1028

Re: How to: Create custom Zones in your views/shapes to render a Widget?

Oh sorry, maybe i misunderstood, in the primary projection you want to display the widget element but normally. Right?

Wednesday, August 3, 2016 3:19:20 AM byjtkech
  • jtkech
  • Lv. 13 Rookie
  • Total EXP: 1028

Re: How to: Create custom Zones in your views/shapes to render a Widget?

Yes mate :) The Primary has many pages [in our examples, two, both with a slider] - which when viewed as is, should show all content and all sliders as you would expect. Once the Secondary/Parent projection containing the child projections displays, it should be like what you achieved - only one slider, but with all other content (which I will have to manually show/hide portions of to make it more of summary).

Wednesday, August 3, 2016 3:36:19 AM bypug
  • pug
  • Lv. 04 Rookie
  • Total EXP: 144

Re: How to: Create custom Zones in your views/shapes to render a Widget?

So, in our example, primary projections are rendered in SummaryPageNumeric and SummaryPageAlpha pages where you want the widget element normally rendered. And the secondary/Parent is in IndexPage page ...

Do you also use such a widget element in the IndexPage in addition to the projection element? and if so you also want to render it normally.

Best

Wednesday, August 3, 2016 3:43:53 AM byjtkech
  • jtkech
  • Lv. 13 Rookie
  • Total EXP: 1028

Re: How to: Create custom Zones in your views/shapes to render a Widget?

So, in our example, primary projections are rendered in SummaryPageNumeric and SummaryPageAlpha pages where you want the widget element normally rendered. And the secondary/Parent is in IndexPage page ... I believe you've got it (sorry, it's my unusual case and poor articulation that has probably hindered you from the onset), if the following makes sense, we're locked and loaded:

We have two Primary Projections [Summary1 and 2], one made up of Alpha Pages, the other made up of Numeric Pages (all the pages have a slider) and if you viewed the SummaryAlpha or SummaryNumeric Projection as is, it would list all the pages in their entireity 'as is', one atop of each other. The Secondary Projection basically summarizes those two Primary Projections (Hides all but the first slider, but shows all other content.)

Do you also use such a widget element in the IndexPage in addition to the projection element? and if so you also want to render it normally

I currently just have a Projection Element in the Index Page (besides the Canvas wrapper)...if I needed to add a slider to this Page on top of the rendered projection (I have no plans to at the moment), I would probably do it through Layers > Featured if that would make it easier with the idea you may be having? But yes, if there was one, it should render normally.

Wednesday, August 3, 2016 4:09:49 AM bypug
  • pug
  • Lv. 04 Rookie
  • Total EXP: 144

Re: How to: Create custom Zones in your views/shapes to render a Widget?

So, try this:

In Elements.Widget.ProjectionSummary.cshtml.

    @using Orchard.Layouts.Helpers
    @{
        var tagBuilder = TagBuilderExtensions.CreateElementTagBuilder(Model);
    }
    @tagBuilder.StartElement
    @if (Model.WidgetShape != null) {
        if (Model.Widget.ContentType == "HtmlWidget"
            && TempData.ContainsKey("SecondaryProjection")) {
            TempData["MyWidget"] = Model;
        }
        else {
            @Display(Model.WidgetShape)
        }
    }
    @tagBuilder.EndElement

In Elements.Projection.ProjectionSummary.cshtml.

    @{
        var list = Model.List;
        var pager = Model.Pager;

        if (list != null) {

            dynamic widget = null;
            var renderedWidget = false;
            List<dynamic> contents = new List<dynamic>();

            TempData["SecondaryProjection"] = true;
            foreach (var item in list.Items) {
                contents.Add(Display(item));

                if (TempData.ContainsKey("MyWidget")
                && item.ContentItem.Id == ((dynamic)TempData["MyWidget"]).ContentItem.Id
                && !renderedWidget) {
                    renderedWidget = true;
                    widget = Display(((dynamic)TempData["MyWidget"]).WidgetShape);
                }
            }
            TempData.Remove("SecondaryProjection");

            <div>
                @if (widget != null) {
                    <div style="float:left;margin-right:20px">
                        @widget
                    </div>
                }
                <div style="float:left">
                    @foreach (var content in contents) {
                        @content
                    }
                </div>
            </div>
        }

        if (pager != null) {
            @Display(pager)
        }
    }

Best.

Wednesday, August 3, 2016 4:28:55 AM byjtkech
  • jtkech
  • Lv. 13 Rookie
  • Total EXP: 1028

Re: How to: Create custom Zones in your views/shapes to render a Widget?

That's it!!!!!!!!!!! What a beast of a thread ;)

https://www.youtube.com/watch?v=44-RsrFVw

Ye gods, thanks ever so much - I would never have sorted this. I will implement this no matter what, but do you think there could be any performance issues I should look out for, as in, once I have many iterations instead of just two…mind you, I intend to hide many of the child elements in the Parent projection, so it probably will only render the slider and a few text elements along with some svg's and another widget; so it shouldn’t be that bad. Anyhow, you truly are a genius [do you have a suggestion of a book or source that helped you the most in MVC c# Programming, I mean, I can see Orchard has a pretty big learning curve - but feel it's made harder by less than adequate C# mvc knowledge]. Anyways, truly humbled by all your patience and hard work, simply exemplary!! Kind Regards, Pug.

Wednesday, August 3, 2016 5:13:44 AM bypug
  • pug
  • Lv. 04 Rookie
  • Total EXP: 144

Re: How to: Create custom Zones in your views/shapes to render a Widget?

Cool !

About performance, it is mainly related to the execution of the queries in your projection of projections.

Yes, we have more iterations but only one is used to render the shapes (in temporary variables) as before ... What you can do for the widget which are not rendered is to use a value true / false to the ProjectionSummary slot in the TempData dictionnary. Then in the projection alternate set it to false when the only one widget is rendered. Then in the widget alternate test it, so if the ProjectionSummary slot exists but is set to false, then do nothing ...

For the books, sorry, i've learned MVC through Orchard, maybe not the best but that's life :)

Finnaly, take te time to test all of this, and if it works mark this ticket as answered.

Best

Wednesday, August 3, 2016 5:35:04 AM byjtkech
  • jtkech
  • Lv. 13 Rookie
  • Total EXP: 1028

Post a reply

You need to be signed in to post a reply.

Sign In