Orchard CMS: Do I have to add a new layer for each page when the specific content for each page is spread in different columns?
Asked Answered
P

2

5

Lets say I want a different main image for each page, situated above the page title. Also, I need to place page specific images in the left bar, and page specific text in the right bar. In the right and left bars, I also want layer specific content.

I can't see how I can achieve this without creating a layer for each and every page in the site, but then I end up with a glut of layers that only serve one page which seems too complex.

What am I missing?

If there is a way of doing this using Content parts, it would be great if you can point me at tutorials, blogs, videos to help get my head round the issue.

NOTE:

Sitefinity does this sort of thing well, but I find Orchard much simpler for creating module, as well as the fact that it is MVC which I find much easier.

Orchard is free, I understand (and appreciate) that. Just hoping that as the product evolves this kind of thing will be easier?

In other words, I'm hoping for the best of all worlds...

Phonetic answered 28/5, 2012 at 14:29 Comment(0)
U
5

There is a feature in the works for 1.5 to make that easier, but in the meantime, you can already get this to work quite easily with just a little bit of code. You should first add the fields that you need to your content type. Then, you are going to send them to top-level layout zones using placement. Out of the box, placement only targets local content zones, but this is what we can work around with a bit of code by Pete Hurst, a.k.a. randompete. Here's the code:

ZoneProxyBehavior.cs:
=====================

using System;
using System.Collections.Generic;
using System.Linq;
using ClaySharp;
using ClaySharp.Behaviors;
using Orchard.Environment.Extensions;

namespace Downplay.Origami.ZoneProxy.Shapes {
    [OrchardFeature("Downplay.Origami.ZoneProxy")]
    public class ZoneProxyBehavior : ClayBehavior {

        public IDictionary<string, Func<dynamic>> Proxies { get; set; }

        public ZoneProxyBehavior(IDictionary<string, Func<dynamic>> proxies) {
            Proxies = proxies;
        }

        public override object GetMember(Func<object> proceed, object self, string name) {

            if (name == "Zones") {
                return ClayActivator.CreateInstance(new IClayBehavior[] {                
                    new InterfaceProxyBehavior(),
                    new ZonesProxyBehavior(()=>proceed(), Proxies, self)
                });
            }

            // Otherwise proceed to other behaviours, including the original ZoneHoldingBehavior
            return proceed();
        }

        public class ZonesProxyBehavior : ClayBehavior {
            private readonly Func<dynamic> _zonesActivator;
            private readonly IDictionary<string, Func<dynamic>> _proxies;
            private object _parent;

            public ZonesProxyBehavior(Func<dynamic> zonesActivator, IDictionary<string, Func<dynamic>> proxies, object self) {
                _zonesActivator = zonesActivator;
                _proxies = proxies;
                _parent = self;
            }

            public override object GetIndex(Func<object> proceed, object self, IEnumerable<object> keys) {
                var keyList = keys.ToList();
                var count = keyList.Count();
                if (count == 1) {

                    // Here's the new bit
                    var key = System.Convert.ToString(keyList.Single());

                    // Check for the proxy symbol
                    if (key.Contains("@")) {
                        // Find the proxy!
                        var split = key.Split('@');
                        // Access the proxy shape
                        return _proxies[split[0]]()
                            // Find the right zone on it
                            .Zones[split[1]];
                    }
                    // Otherwise, defer to the ZonesBehavior activator, which we made available
                    // This will always return a ZoneOnDemandBehavior for the local shape
                    return _zonesActivator()[key];
                }
                return proceed();
            }

            public override object GetMember(Func<object> proceed, object self, string name) {
                // This is rarely called (shape.Zones.ZoneName - normally you'd just use shape.ZoneName)
                // But we can handle it easily also by deference to the ZonesBehavior activator
                return _zonesActivator()[name];
            }
        }
    }
}

And:

ZoneShapes.cs:
==============


using System;
using System.Collections.Generic;
using Orchard.DisplayManagement.Descriptors;
using Orchard;
using Orchard.Environment.Extensions;

namespace Downplay.Origami.ZoneProxy.Shapes {
    [OrchardFeature("Downplay.Origami.ZoneProxy")]
    public class ZoneShapes : IShapeTableProvider {
        private readonly IWorkContextAccessor _workContextAccessor;
        public ZoneShapes(IWorkContextAccessor workContextAccessor) {
            _workContextAccessor = workContextAccessor;
        }

        public void Discover(ShapeTableBuilder builder) {

            builder.Describe("Content")
                .OnCreating(creating => creating.Behaviors.Add(
                    new ZoneProxyBehavior(
                        new Dictionary<string, Func<dynamic>> { { "Layout", () => _workContextAccessor.GetContext().Layout } })));
        }
    }

}

With this, you will be able to address top-level layout zones using Layout@ in front of the zone name you want to address, for example Layout@BeforeContent:1.

Unheard answered 28/5, 2012 at 19:41 Comment(5)
After looking at it, I have no idea where to use this. Or how to use it.Phonetic
Make a module with both of those files, enable the module, then modify placement the way I explained.Unheard
Many thanks, the minute you say it, I can see the OrchardFeature attribute on the ZoneShapes class, which makes it all rather obvious.Phonetic
Is there a feature in 1.5 that makes this easier?Vanya
Yes. You can use a leading slash before the zone in placement to send a shape into any top-level zone.Unheard
P
4

ADDENDUM:

I have used Bertrand Le Roy's code (make that Pete Hurst's code) and created a module with it, then added 3 content parts that are all copies of the bodypart in Core/Common.

In the same module I have created a ContentType and added my three custom ContentParts to it, plus autoroute and bodypart and tags, etc, everything to make it just like the Orchard Pages ContentType, only with more Parts, each with their own shape.

I have called my ContentType a View.

So you can now create pages for your site using Views. You then use the ZoneProxy to shunt the custom ContentPart shapes (Parts_MainImage, Parts_RightContent, Parts_LeftContent) into whatever Zones I need them in. And job done.

Not quite Sitefinity, but as Bill would say, Good enough.

The reason you have to create your own ContentParts that copy BodyPart instead of just using a TextField, is that all TextFields have the same Shape, so if you use ZoneProxy to place them, they all end up in the same Zone. Ie, you build the custom ContentParts JUST so that you get the Shapes. Cos it is the shapes that you place with the ZoneProxy code.

Once I have tested this, I will upload it as a module onto the Orchard Gallery. It will be called Wingspan.Views.

I am away on holiday until 12th June 2012, so don't expect it before the end of the month.

But essentially, with Pete Hurst's code, that is how I have solved my problem.

EDIT:

I could have got the same results by just creating the three content parts (LeftContent, RightContent, MainImage, etc), or whatever content parts are needed, and then adding them to the Page content type.

That way, you only add what is needed.

However, there is some advantage in having a standard ContentType that can be just used out of the box.

Using placement (Placement.info file) you could use the MainImage content part for a footer, for example. Ie, the names should probably be part 1, part 2, etc.

None of this would be necessary if there was a way of giving the shape produced by the TextField a custom name. That way, you could add as may TextFields as you liked, and then place them using the ZoneProxy code. I'm not sure if this would be possible.

Phonetic answered 2/6, 2012 at 16:23 Comment(7)
Hi. I was wondering if you could kindly share this Wingspan.Views module that you made. I searched for it in the gallery to no avail. Also say I have different zones on my theme, I want my homepage to show a given zone say 'main_content_left' which spans the whole width of the page, and my about page to show another zone 'sub_content_right' which will override the 'main_content_left' zone and span across the whole page, and so forth for my other pages, what would be the best way to do this? The module that you created or should I use layers? I'd be extremely grateful for any guidance. Thanks.Laughlin
@Mohammad Sepahvand: What Wingspan.Views does is avoid having to create a layer for every page, just to have different editable zones. In addition, Wingspan.Views makes these editable zones editable from one place in the dashboard. However, for your problem, I would use layers. The reason being that you have specific requirements for each given page as opposed to every page, or a large bunch of pages, have multiple editable zones. Haven't had time to upload Wingspan.Views, no idea when I will.Phonetic
I will, however, try to get something up in the next day or two. Maybe the weekend.Phonetic
thanks for your swift response, be great to see it. I'll use layers for now. I'm new to orchard, I tried to create the module bertrand le roy mentioned in his answer and i have no idea where to start.Laughlin
Did you ever release Wingspan.Views, or is there something you're using instead?Asir
@JamesSkemp: is there a way for you to give me your email? I'll send you the files. Sorry, completely forgot about you. My apologies.Phonetic
@awrigley, no problem! I'm on gmail.com as strivinglife. Thanks!Asir

© 2022 - 2024 — McMap. All rights reserved.