Multiple Widgets based on one content type

Topics: Customizing Orchard, Writing modules
Dec 10, 2012 at 7:54 AM

Hi there.

I've been wrestling with this for a couple days now and I think maybe I'm going about this the wrong way.

I'll give you an example of the end result I'm trying to achieve:

Say I have a Part I've created called BallPart and it's being used by the Ball content type. One of the characteristics of this ball is its color: blue, red, green. Now I want widgets I can place that will filter the Ball content types by color--BlueBallWidget, RedBallWidget, GreenBallWidget.

That's the desired end result.

I've created a Module that creates the BallPart and the Ball content type. This I've gotten to work. It's been trying to wire up the collection of Balls to a widget that's got me running in circles.

Migration.cs

            ContentDefinitionManager.AlterPartDefinition("BlueBallsWidgetPart", cfg => cfg
                .Attachable()
            );
            ContentDefinitionManager.AlterTypeDefinition("BlueBallsWidget",cfg => cfg
                .WithPart("BlueBallsWidgetPart")
                .WithPart("CommonPart")
                .WithPart("WidgetPart")
            );

            ContentDefinitionManager.AlterPartDefinition("RedBallsWidgetPart", cfg => cfg
                .Attachable()
            );
            ContentDefinitionManager.AlterTypeDefinition("RedBallsWidget", cfg => cfg
                .WithPart("RedBallsWidgetPart")
                .WithPart("CommonPart")
                .WithPart("WidgetPart")
            );

            ContentDefinitionManager.AlterPartDefinition("GreenBallsWidgetPart", cfg => cfg
                .Attachable()
            );
            ContentDefinitionManager.AlterTypeDefinition("GreenBallsWidget", cfg => cfg
                .WithPart("GreenBallsWidgetPart")
                .WithPart("CommonPart")
                .WithPart("WidgetPart")
            );

Models\Balls.cs

    public class BallsPart : ContentPart {
        public readonly LazyField<IList<BallPart>> _blueBalls = new LazyField<IList<BallPart>>();
        public readonly LazyField<IList<BallPart>> _redBalls = new LazyField<IList<BallPart>>();
        public readonly LazyField<IList<BallPart>> _greenBalls = new LazyField<IList<BallPart>>();

        public BallsPart() {
            BlueBalls = new List<BallPart>();
            RedBalls = new List<BallPart>();
            GreenBalls = new List<BallPart>();
        }
        public IList<BallPart> BlueBalls {
            get { return _blueBalls.Value; }
            set { _blueBalls.Value = value; }
        }
        public IList<BallPart> RedBalls {
            get { return _redBalls.Value; }
            set { _redBalls.Value = value; }
        }
        public IList<BallPart> GreenBalls {
            get { return _greenBalls.Value; }
            set { _greenBalls.Value = value; }
        }
    }

Handlers\BallsHandler.cs

    public class BallsPartHandler : ContentHandler {
        public BallsPartHandler(IContentManager contentManager) {
            OnInitializing<BallsPart>((ctx, x) => {
                x.BlueBalls = new List<BallPart>();
                x.RedBalls = new List<BallPart>();
                x.GreenBalls = new List<BallPart>();
            });

            OnLoading<BallsPart>((context, balls) => {
                balls._blueBalls.Loader(list => contentManager
                    .Query<BallPart, BallRecord>()
                    .Where(x => x.Type == BallType.BLUE)
                    .List().ToList());

                balls._redBalls.Loader(list => contentManager
                    .Query<BallPart, BallRecord>()
                    .Where(x => x.Type == BallType.RED)
                    .List().ToList());

                balls._greenBalls.Loader(list => contentManager
                    .Query<BallPart, BallRecord>()
                    .Where(x => x.Type == BallType.GREEN)
                    .List().ToList());
            });
        }
    }
And then I got to the Driver and I realized I had no idea how to proceed. Do I use the same driver, or should I create a separate driver for each widget? I thought I might get the community's thoughts before I spent another couple of days going down the wrong path. I need this all to work in the module so I can't create types via the gui.

Any ideas?

Developer
Dec 10, 2012 at 8:13 AM
Edited Dec 10, 2012 at 8:14 AM

First of all I am curious as to why you are creating a BallWidgetPart for each color, instead of a single BallWidgetPart with a Color property?

Secondly, you are initializing the list of balls in the BallsPart 3 times: once in the constructor, once in the Initializing method of the handler and once on the OnLoading method. It's not a problem, it just seems a bit wasteful (however minimal it may be).

Thirdly, it is confusing to me why you defined the different colored BallWidgetParts using the ContentDefinitionManager, but omitted the BallsPart. Now I have no idea if you want to create a Balls widget or not.

Your title is also somewhat confusing. It looks like you want to create multiple widgets based on one content type, and yet you created several content types. Which in itself makes sense, since a widget is in itself a content type in its own right.

In summary, it's still not very clear to me what it is you want to do with all of the different parts. However, since you created a BallsPart class, the logical next step would be to write a driver for the BallsPart. Since you didn't create part classes for the colored ball widget parts, you can't write drivers for them.

Dec 10, 2012 at 7:03 PM

First off, thanks for responding. Sorry for the confusing title. I was trying to avoid craming my entire issue in there so I made briefer than it should have been.

Most tutorials/examples I have seen have one piece of data--a record, with one Part, and is displayed with one widget. What I need are three widgets that display different aspects of the same data: a Ball.

Your points:

2. Ya it does seem wasteful to me too. However, I used Orchard.Comments as the base for my module since it seemed to do roughly the same thing I wanted to do. Figured if they did it like that...

3. Not really sure why I did it like that. Seemed like a good idea at the time, I'm sure. Like I said, I was trying to use Orchard.Comments as the template for my module and I may have tried to make some square pegs fit in that round hole.

So, I have a Part and a Record. I can create content types to my heart's content. I'm trying to figure out a way to feed different sets of that content to different widgets. What would be the best way to go about that? Based on what I found in Orchard.Comments, I believe I can use just one model for the different widgets. It's the interaction of the Parts, Handler, and Driver that I'm struggling with. Based on your comments, here's my revised thinking:

I have a BallsPart that I need to include in with each of the widgets in the ContentDefinitionManager. My Model and Handler are rough but ok. And I'm going to need a Driver for each widget.

Is that a better approach?

Thanks!

Developer
Dec 11, 2012 at 6:28 AM
Edited Dec 11, 2012 at 6:29 AM

I see. Ok, so if you need a Driver for each widget, you will have to write a Content Part class for each of them if you want different behavior and shapes for each of them. This class could derive from ContentPart instead of ContentPart<T> if you don't need a record class.

So if you have for example 3 widget content types:

  1. RedBallWidget
  2. GreenBallWidget
  3. BlueBallWidget

And if you want a unique driver for each of them, you need a unique content part class for each of them:

  1. RedBallWidgetPart : ContentPart
  2. GreenBallWidgetPart : ContentPart
  3. BlueBallWidgetPart: ContentPart

And of course you can attach the BallsPart part to all three  widget content types.

I'm still not entirely sure if I understand your requirements completely, but just ask away if anything is unclear.

Dec 15, 2012 at 2:45 AM

Thanks for the tips. I appreciate it.

I'll give it a try this weekend.