How do I add a custom part to a ShapeResult?

Topics: Writing modules
May 27, 2011 at 11:04 PM

Hi there,

In my module controller I have an Action that returns a ShapeResult.

Looks like this:

var shape = _orchardServices.New.Register();
// I want shape to contain my custom part AioiProfile, how do I do this?
return new ShapeResult(this, shape);
 
In my module I define a custom part: AioiProfilePart

I want to include an AioiProfilePart as part of the ShapeResult that is returned above.
By doing this I expect the appropriate Display/Edit drivers on my custom part to be called.

Thx!   

Coordinator
May 27, 2011 at 11:07 PM

You should call BuildDisplay to build the shape. Look for examples in existing modules.

May 27, 2011 at 11:23 PM

WOW! That was fast :-) Thx!

I am now doing this:

              AioiProfilePart dummy = new AioiProfilePart();
              var widgetShape = _contentManager.BuildDisplay(dummy);

but

 DefaultContentDisplay.BuildDisplay(IContent content, string displayType, string groupId)

is throwing a NullRefException because content.ContentItem is null.

 Any ideas?

Coordinator
May 27, 2011 at 11:24 PM

Yes, parts are not meant to live on their own. They must always be part of a content item.

May 27, 2011 at 11:37 PM

I had created a custom part: AioiProfilePart and added it to the User ContentType (using the Profile module from the gallery). This part contains the info which users must fill out in order to register for the site.
So I guess I have to get to the AioiProfilePart via the current user, correct?
As I understand the Part must always be contained in a ContentItem.
The current user is not authenticated, but I guess that should not be a problem.

I'll try to figure out how to get the current user now...

Coordinator
May 27, 2011 at 11:39 PM

Yes. You can get the user from the work context.

May 27, 2011 at 11:47 PM

IUser user =_orchardServices.WorkContext.CurrentUser;

user is null. User is trying to register for the site.

What should I do?

BTW, thx for all the help!! 

Coordinator
May 27, 2011 at 11:48 PM

Well, if he hasn't registered, there is no user. Am I missing something?

May 27, 2011 at 11:57 PM

I just need to add an AioiProfilePart to the ShapeResult returned from the Register action.

As you pointed out, I must call  _contentManager.BuildDisplay with an instance of my AioiProfile part.

I cannot simply do:
var dummy = new AioiProfilePart(); -> this part does not belong to a contentItem.

Because of this I was trying to get to the AioiProfile part via the user (user is the contentItem).

How do you suggest that I get the AioiProfile part added to the ShapeResult?
I hope the problem I face is now clear... 

Coordinator
May 28, 2011 at 12:00 AM

But your part is entirely empty from what you show. Don't see the point. Couldn't you just create a new user if you can't find the current one?

May 28, 2011 at 12:15 AM
Edited May 28, 2011 at 12:21 AM

Keep in mind that we are dealing with the Register page.

The only way I can create a new user (AFAIK) is to call:  IMembershipService.CreateUser(CreateUserParams createUserParams);

But I cannot do this yet! At this point I have had NO INPUT from the user about to register (username, pswd, etc...).
He is visiting this page to register and provide the info. 

The reason why I am doing a custom registration page is because the user MUST submit additional information (which must be validated) before being accepted on the site.
This additional information is contained in the AioiProfilePart. This is the KEY!!
Therefore I MUST display an empty AioiProfilePart when someone tries to register (current user is null)! 

I would like to continue to use the Profile module and have the AioiProfilePart be part of the User ContentType, but I am really stuck here.


Thx! 

Coordinator
May 28, 2011 at 12:26 AM

A user instance. You can get that from the content manager. Doesn't mean you are going to persist it of course.

May 28, 2011 at 2:14 AM

Thx Bertrand!
Finally got it!
With the code below I am successfully calling BuildDisplay on a AioiProfilePart.

Using the debugger I can see the shape.AioiProfilePart is ok.
However the HTML output that I wish to have originating from the AioiProfilePart shape is nowhere to be seen.

I guess it's because the variable:  shape.AioiProfilePart is not named correctly.
Anyhow I'll try to figure that out now.
Getting closer... 

 

              var user = _orchardServices.ContentManager.New<UserPart>("User");
              if (user != null)
              {
                  foreach (var part in user.ContentItem.Parts)
                  {
                      var aioiProfile = part as AioiProfilePart;
                      if (null != aioiProfile)
                      {
                          shape.AioiProfilePart = _contentManager.BuildDisplay(aioiProfile);
                      }
                  }
              }

 

 

May 28, 2011 at 2:20 AM

_contentManager.BuildDisplay builds an entire content display from the User content item. Passing in the part will still build a display of the containing content item. Instead you push a shape into that display via the Driver for your part.

May 28, 2011 at 2:36 AM
Edited May 28, 2011 at 2:47 AM

Pete,

I understand what you say. Unfortunately I have no idea how to 'push a shape into the display via the Driver'.
It's not clear to me how I can get my Driver called?
What is the trigger? Do I need to subscribe somewhere?

Still quite confused but slowly learning...

Thx! 

** EDIT **

I do understand how to push the shape from within the Driver. Once the driver has been called.
My driver is implemented and working.
I just don't know how to trigger the driver to be called.
It is called when I call BuildDisplay with an AioiProfile, but as you explained that will build the display for the entire UserPart.

Hope it is clearer now. 

Coordinator
May 28, 2011 at 3:02 AM

I wouldn't do anything that relies on assumed knowledge of what the "right driver" is, like instantiating it and calling it manually. Nothing says that there aren't other drivers for the same part that you don't know about. BuildDisplay will have the same effect and is the natural way of doing these things.

May 28, 2011 at 3:22 AM

OK, with what I just learned I think this might be the right approach.
 
I have defined a new ContentType: 'RegisterUser'. It contains an AioiProfilePart.

In my controller I call:

          public ActionResult Register()
          {
            // ...
              var shape = _orchardServices.New.Register();

              var registerUser = _orchardServices.ContentManager.New("RegisterUser");
              if (registerUser != null)
              {
                  shape.HOW_DO_I_NAME_THIS = _contentManager.BuildDisplay(registerUser); // QUESTION HERE <--
              }
           
              return new ShapeResult(this, shape);
          }

By doing this I see my driver.display is called. However no HTML from the 'RegisterUser' ContentItem is generated.
I guess it's because HOW_DO_I_NAME_THIS is wrong (see code above).
What is the correct name for that sub-shape?
If that is not the issue, what else could it be? 

 

Coordinator
May 28, 2011 at 3:27 AM

It can be whatever you want but the name of that shape will determine what the name of the template that renders it will have to be. On the other hand I'm wondering why you don't make the economy of the Register shape and don't directly assign what BuildDisplay returns as the shape that you send into ShapeResult.

May 28, 2011 at 3:39 AM
Edited May 28, 2011 at 3:55 AM

Bertrand,
your last comment is not quite clear to me.

With the code posted above only the Register.cshtml template is rendered.

If I do the following, only the AioiProfile.cshtml is rendered:

shape.AioiProfile = _contentManager.BuildDisplay(registeredUser);
return new ShapeResult(this, shape.AioiProfile);

 

 I need both the Register.cshtml and the AioiProfile.cshtml to be rendered.
This is where I am now stuck.
How do I need to compose my ShapeResult for this? 

Thx a lot!! 

Coordinator
May 28, 2011 at 4:12 AM

Ah, ok then.  Well, then name that thing however you want, and just include @Display(Model.WhateverYouNamedIt) somewhere in Register.cshtml. In WhateverYouNamedIt.cshtml, do what you have to do.

May 28, 2011 at 4:31 AM

THANK YOU SO MUCH!!!!

It is working now! And I have a much better understanding of Orchard.

Once it rendered I realized I needed to use _contentManager.BuildEditor instead of  _contentManager.BuildDisplay for the RegisterPage.

May 29, 2011 at 12:42 AM

Where do you put the Register.cshtml file?

May 29, 2011 at 3:39 PM

Santiago I'll be pleased if you can post your solution. I also try to create a custom registration page for my website.

 

Thank you.

May 30, 2011 at 1:03 AM

Sherleydev
The Register.cshtml file goes in the Views/Account folder.

Yes, once I have finished it I can make it available to you.
My goal is to now make this functionality generic, so it can become a module that anyone can use.

For this I need to figure out how the model binding works in Orchard -> How can I automatically populate a content item from the form values.
Once I have that then we are very close to having a module for this.

I'll try to work on this later tonight. 

May 30, 2011 at 1:02 PM

Model binding pretty much works automatically. You just need to receive the post and call ContentManager.UpdateEditor, everything will be persisted to the content item.

May 30, 2011 at 2:44 PM

I pretty much got it all working yesterday evening.
I will need to make a new module out of it. I will do this within the next 2-3 days and upload it as a module to the gallery.