Weld on the fly

Topics: Writing modules
Developer
Jun 4, 2011 at 10:34 AM

Hey guys... probably quite an easy on... Lets take this line of code

var thread = _orchardServices.ContentManager.New<ThreadPart>("Thread");

And say I was to Weld a BodyPart to the Thread on the fly... So that the BodyPart is displayed on the screen... Any Ideas on the correct was to do this?

Coordinator
Jun 6, 2011 at 8:28 PM

thread.Weld(theBodyPart) But why do you want to do it on the fly?

Developer
Jun 6, 2011 at 9:00 PM

Yeah ive tried that.. it has some odd behaviour going on with that!! and that one is...

thread.Weld(field)

Yiu mean

thread.ContentItem.Weld(theContentPart)

The wierd behaviour is that if you do..

var thread = _orchardServices.ContentManager.New<ThreadPart>("Thread");
// The post has a Body part attached to it..
var post = _orchardServices.ContentManager.New<PostPart>("Post");
// post.As<BodyPart>().Text = null
thread.ContentItem.Weld(post)
// post.As<BodyPart>().Text = nullreference exception

Coordinator
Jun 6, 2011 at 9:13 PM

Why is the content type name "Post" if you are going to weld it onto a thread?

So why do you want to do this on the fly rather than just create a content type from a migration?

Developer
Jun 6, 2011 at 9:16 PM

A Thread is a list of Posts right? But a Post doesnt have a route... So When creating a Thread... You also want to create a Post right? So if I weld it on the fly, then I dont have to have to permantly attached, and I also dont have to problem with a double Sumbit button on the UI.

Coordinator
Jun 6, 2011 at 9:25 PM

This sounds all kinds of wrong. It just sounds like you would rather want the shape to have additional stuff for allowing the user to enter the first post as the thread is being created, and then handle that from your driver or controller action, I don't know, and only then create the thread, and separately, the first post, and then attach one to the other.

What you are describing sounds like a horrible hack (and I'm kind of glad that Orchard blew up in horror seeing that) :D

Not sure what you mean with that double submit thing.

Developer
Jun 6, 2011 at 9:34 PM

Thats what I have actually done... in the last day or two.. I have a BodyPart permantly welded to a Thread... which is okay... but not totally what I wanted!..

I tried lots of different ways to do it..
1 is Weld a BodyPart to a Thread, and then return with it... and create the Post on the fly... that didnt work,
2. I tried to Display two ContentTypes.. like so..

            dynamic threadModel = _orchardServices.ContentManager.BuildEditor(thread);
            dynamic postModel = _orchardServices.ContentManager.BuildEditor(post);

            dynamic viewModel = Shape.ViewModel()
                .ThreadModel(threadModel)
                .PostModel(postModel);

            return View((object)viewModel);

But this will display two Content_Edit zones which also means two save buttons :(

I also tried to customise the Edit area... but I found out that doesnt work either... i.e. Content-Thread.Edit - Orchard doesnt pick this up

Coordinator
Jun 6, 2011 at 9:50 PM

I think you should work on 2 and override whatever templates you have to in order to get rid of those extra save buttons. This is definitely a presentation problem that should be solved at the presentation level.

Developer
Jun 6, 2011 at 10:01 PM

Thats the thing.. it is a Presentation layer issue.. but I took a week trying to solve that problem whilst also talking to Sebastian on it, but I still didnt get it working exactly the way I wanted it to...

I got it quite close like so..

dynamic threadModel = _orchardServices.ContentManager.BuildEditor(thread);
dynamic postModel = _orchardServices.ContentManager.BuildEditor(post);
var shape = Shape.Parts_ThreadPost_Create
                              .Thread(threadModel)
                              .Post(postModel);
return new ShapeResult(this, shape)

And then doing this in the view

@Display(Model.Thread.Content)
@Display(Model.Post)

This meant the Post would display the save button and the Thread would display just the relevant fields... but that seemed horrible.. I need to have more of a think of how I want to do it - im sure there is a way to display two ContentParts nicely on the same view....

Coordinator
Jun 6, 2011 at 10:07 PM

How much do you want this to be built with BuildEditor? Maybe one possibility would be to build the editor for the post and then change the resulting shape, adding stuff, changing the name, etc.?

Developer
Jun 6, 2011 at 10:08 PM

Have you got an example?

Coordinator
Jun 6, 2011 at 10:13 PM

Well, no, but the shape type is Metadata.Type, and you can add arbitrary properties to it.

Developer
Jun 6, 2011 at 10:22 PM

Hmm... sorry im not completely sure what you mean?

Coordinator
Jun 6, 2011 at 10:25 PM

I mean massage the shape you got from building the editor for the post and add what you need for it.

Developer
Jun 6, 2011 at 10:27 PM

Ahh okay.. do you mean mess with the threadModel.Alternates ??

Coordinator
Jun 6, 2011 at 10:29 PM

That's one possibility.

Developer
Jun 6, 2011 at 11:57 PM

Ive had another mess around with this... but at the moment I dont see a nice way to do it.

I was reassigning the Type on the Metadata and then showing the new shapes... Could you tell me know to remove certain things? i.e. the Owner field is appearing twice.. is there a way to make it appear just the once?

            dynamic threadModel = _orchardServices.ContentManager.BuildEditor(thread);
            threadModel.Metadata.Type = "Parts_Threads_Create";
            dynamic postModel = _orchardServices.ContentManager.BuildEditor(post);
            postModel.Metadata.Type = "Content_Edit__Post";

            var viewModel = Shape.ViewModel()
                .Thread(threadModel)
                .Post(postModel);

            return View((object)viewModel);

??

Coordinator
Jun 7, 2011 at 12:21 AM

With this code, you still have essentially two editors built. What I was wondering was if you could somehow merge those. Other than that, well I suppose you could use placement, or remove stuff from what's inside Thread or Post.

Jun 7, 2011 at 2:05 AM

Lol, I am working on forum module as well and am stuck on exactly the same problem at the moment! - displaying editor for post as well as thread in the same view. I think maybe I should contribute to your one Jetski, just am too lazy to learn Mercurial and Codeplex at the moment.

Jun 7, 2011 at 2:18 AM

I posted asking for help with this under the tile: 'InvalidCastException when building editor for a part'.

Ok, so using something like <Place Parts_RoutableTitle_Summary="Nowhere"/> for the stuff we don't want to appear in the ViewModel for the combined editors should work?

Will try this now, thanks guys.

Jun 7, 2011 at 3:28 AM

OK, this leads me to the question of how you do ViewModels in Orchard.

I'm assuming I need to make a driver for it to render the ViewModel's view(s), but won't this require it to be defined as a part, and a ViewModel conceptually shouldn't be a part should it?

Jun 7, 2011 at 3:35 AM

I've been doing some work on this kind of thing. The Origami module has its own kind of drivers called "ModelDrivers" which you can use to build editor models out of any objects, not just content items. I've been using it to composite various UI bits and pieces for Mechanics and Media Garden. It's part of the Science Project stuff (http://scienceproject.codeplex.com) - give me a shout if you want some help using it, there's no documentation yet :)

Jun 7, 2011 at 4:09 AM

What about just defining a ThreadPart as consisting of a PostPart in the Migrations class and then removing any unwanted/duplicate elements in the placement file as Bertrand suggests?

Or does Orchard support an inheritance hierarchy for parts? Because isn't a Thread just a special case of a Post in which case you could inherit Thread and Post from a common parent Part?

Coordinator
Jun 7, 2011 at 4:26 AM
Edited Jun 7, 2011 at 4:26 AM

Now that I think a little more about it, you really want to let the content manager build the editors so that people can add parts and you take that into account naturally. The problem here is that you are trying to put two feet into a single shoe. I think the missing link here is just that you need to send one of the shapes into a zone "manually" from the controller.

Jun 7, 2011 at 5:41 AM

And how do you do that? Could you give an example Bertrand?

Coordinator
Jun 7, 2011 at 5:45 AM

You can get the layout from the work context and do layout.WhateverZoneName.Add(yourShape).

Developer
Jun 7, 2011 at 10:24 AM

Thats the same as saying

@Display(Model.Thread)
@Display(Model.Post)

I tried what you said though doing

_orchardServices.WorkContext.Layout.Content.Add(model);
_orchardServices.WorkContext.Layout.Content.Add(model);

But the problem where I end up with two Content_Edit areas still exists, and therefore I end up with two Save buttons.

Jun 7, 2011 at 11:06 AM

That wasn't what Bertrand was saying; but I think he was talking about Display shapes, not Editors.

The problem is, a Thread isn't really a special case of Post - it's actually the parent of a set of Posts. In a forum you have a hierarchical model: Forum->Thread->Post. The Thread shouldn't have a BodyPart - just a title, the Posts should have the body.

By trying to edit two items on the same page you'll run into a limitation of the ContentManager, that it supports no way to specify a field prefix. So there's no way in your controller action that you can differentiate the fields from the two distinct items.

This is exactly one of the limitations that I've been tackling in Origami and Mechanics; a forum was one of my original use cases. It's a system of hierarchical connectors, and it enables multiple nested editors, whilst still being completely extensible and supporting Placement. It was all pretty hard to do :)

Developer
Jun 7, 2011 at 7:37 PM

If we speak in terms of Parts, the Thread should have a RoutePart + CommonPart (though there are fields I dont want shown to end users - so I will need to look at this), the Post should have a BodyPart + CommonPart

The problem is that when you create a thread, you create a post.. maybe I should look at the code under hooking ContentManager and see if I can manipulate that. Will let you know how I get on.

Coordinator
Jun 7, 2011 at 7:42 PM

Mmm. Maybe the key is to use a different display type for the post, and build the display yourself. OTOH, I haven't rellay dug into the code producing the save button. Maybe there is a way to suppress it. But still, as Pete is saying, the prefix problem remains. Building the display yourself with a new display type, you could control that, and you could still use placement and all UI composition.

Developer
Jun 7, 2011 at 7:48 PM

I got that working where I could suppress the Save button by removing

@if (Model.Sidebar != null) {
<div class="edit-item-sidebar group">
    @Display(Model.Sidebar)
</div>
}

from a custom Content-Thread.Edit template. The problem is that I still have duplidated fields for example 'Owner' from the CommonPart. If you think that using Placement and controlling this with a Displaytype is the right way of doing this, then I will give that a go...

Coordinator
Jun 7, 2011 at 7:57 PM

Just a suggestion... You won't know until you try.

Developer
Jun 8, 2011 at 9:44 PM

Is there an example in Orchard where you are using the DisplayType with BuildEditor?

Coordinator
Jun 8, 2011 at 9:48 PM

You are pretty much in unexplored territory.

Developer
Jun 8, 2011 at 9:49 PM

Excellent! Ill let you know how I get on :)

Developer
Jun 8, 2011 at 10:34 PM

Hmmm it looks like DisplayType is not actually built in to the Editor stuff... Hmm..

Developer
Jun 8, 2011 at 11:15 PM

Okay im not seeing a way of doing this... I could do this....

dynamic threadModel = _orchardServices.ContentManager.BuildEditor(thread);
threadModel.Sidebar.Items[0].Metadata.DisplayType = "Edit";
threadModel.Sidebar.Items[0].Metadata.PlacementSource = "Whatever"

Not sure if this will work yet. But to me this feels like a massive hack! Im going to keep digging....

Coordinator
Jun 8, 2011 at 11:26 PM
Of course, no matter what you do it will be a hack: you are trying to pretend two items really are one. That in itself is a hack.

Sent from my TI-99/4A

From: Jetski5822
Sent: Wednesday, June 08, 2011 3:15 PM
To: Bertrand Le Roy
Subject: Re: Weld on the fly [orchard:260183]

From: Jetski5822

Okay im not seeing a way of doing this... I could do this....

dynamic threadModel = _orchardServices.ContentManager.BuildEditor(thread);
threadModel.Sidebar.Items[0].Metadata.DisplayType = "Edit";
threadModel.Sidebar.Items[0].Metadata.PlacementSource = "Whatever"

Not sure if this will work yet. But to me this feels like a massive hack! Im going to keep digging....

Developer
Jun 8, 2011 at 11:29 PM

Well I dont think editing two items at once is a 'Hack' - I think its a story that hasent been needed until now... right?

The Hack is trying to implement it!! :)

Coordinator
Jun 8, 2011 at 11:31 PM

Editing two at once is not a hack, it's pretending they really are a single one that is. At least it looks like that to me. I'm not saying it's not useful.

Developer
Jun 8, 2011 at 11:35 PM

Exactly. The hack is the implementation. Oh well...

Jun 10, 2011 at 5:28 AM

So, Jetski, have you managed to fit two feet into a single shoe yet?

Jun 10, 2011 at 10:18 AM
Jetski5822 wrote:

Hmmm it looks like DisplayType is not actually built in to the Editor stuff... Hmm..


True...
I created an issue for this a while ago, but i guess people didn't understood me
http://orchard.codeplex.com/workitem/17425

Also look at my thread i started
http://orchard.codeplex.com/discussions/246448

Jun 10, 2011 at 12:45 PM

If BuildEditor had DisplayType and Prefix parameters, it would have made certain things I'm trying to do much easier :)

But I've already been through this whole process and realised the limitations of content manager when it comes to editing. Which is why I've written Origami, so I can break out of Orchard's UI composition and do some more advanced things (whilst still leveraging the whole shapes system, Placement etc.)

In the end I'll probably write my own separate version of content display to work within Origami and do the kind of nested content editors you guys are talking about. It already works for my ModelDrivers - a prefix property gets passed in at each stage so you can build any number of levels of UI. The problem is, it's just not compatible with ContentPartDrivers, so you can do content editing type stuff but you have to reimplement existing drivers UI that you want. This is something I want to address!

Jun 11, 2011 at 4:07 PM

Is it possible to place parts in parts with Origami? :)

Doesn't seem like the team is going to implement parts in parts funcionality

Coordinator
Jun 12, 2011 at 3:04 PM

The "parts in parts" functionality can be faked using the "multiple driver pattern" Technically, you can implement as many drivers as you want for a specific Part. The result is that all drivers are called, and aggregate in the UI. If you look at the source code for 1.2, we use this technique on CommonPart. And each sub-part can be enabled using a part setting. This means you can even extend existing parts for other modules.

Jun 12, 2011 at 5:45 PM
rfcdejong wrote:

Is it possible to place parts in parts with Origami? :)

Doesn't seem like the team is going to implement parts in parts funcionality

Simple version: Yes.

Long version: Origami has a facility for "nested UI"; which means building child editor templates that inherit the prefix from the parent. This is "models within models". Now, what I haven't done yet is provide some specific support for Orchard's content models; Origami currently works with any arbitrary model but you'd have to do some extra work to get it to behave nicely with actual Content Parts. In general I've been using it for custom UI rendering where I didn't need ordinary drivers and parts to get involved (but you can still extend that UI by using Origami's own drivers).

To fully do content hierarchy editing, I need to reimplement ContentManager's display system in Origami, so I can pass in a parent prefix.

Does that make sense? Sorry it's all a bit abstract.

Developer
Jun 19, 2011 at 3:52 PM

Since upgrading to Orchard 1.2 my forum module seems to be gettng the error "The Id field is required." on post back... hmm This happens when doing _orchardServices.ContentManager.UpdateEditor(thread, this);

Just incase anyone is looking at the forum mod