Widget partial view from custom controller?

Topics: Customizing Orchard, Writing modules, Writing themes
May 6, 2011 at 1:06 PM

I'm presently building up a basic e-commerce type site and I'm looking for some advice on how to update a widget with AJAX and keep it themeable etc.

I'll try to explain what I'm doing. I have pages that are setup to understand they are products, this results in a link being added to the page that allows the user to add the product to their shopping basket. Adding a product to the basket is done with AJAX, they click the link, it fires an action in my custom basket controller, and it returns a JSON result to indicate success or failure. 

On all the pages, there is also a common, basket status widget - a standard widget that just renders out the number of items in the basket and the total price with a link to checkout/view the basket (both of which just navigate to my custom controller actions).

What I need to happen is when you add a product to the basket, using the AJAX method above, the jQuery would then invoke another controller action to get the basket status partial view. In standard MVC I'd just have a standard controller action that returns the rendered partial view and the jQuery simply replaces the content in the DOM on the client side. What I'm struggling to figure out is how to do this the Orchard way - how can I write a controller action that returns the html fragment for the content part and ensure it's still uses the theme templates etc? Somehow I need to build some shapes I assume and ask orchard to render the view and these shapes using the themes etc, but I have no idea where to start.

So, the process is;

  1. A page is rendered which contains a link to add a product to the basket.
  2. On the page is the basket status widget, presently showing "there are no items in your basket"
  3. User clicks the add to basket link for the product.
    1. jQuery fires a request to my custom controller to add the product to the session's basket
    2. returns JSON result indicating success
    3. jQuery fires a request to my custom controller to get the basket status widget
      1. What does the controller need to do to render that widget?
    4. returns HTML result
    5. jQuery replaces the basket status widget with the updated version

I hope that makes sense.

thanks,

Tony

May 8, 2011 at 9:08 PM

The easiest way is probably to build your own shape, and render that shape both from the widget and from your controller.

You need an IShapeFactory and call factory.MyShape(). There are some examples of this in various areas of Orchard.

May 9, 2011 at 8:27 AM

Thanks for this - no doubt a newbie question, but what do I return from my controller to render the (themed) partial view with my custom shape? 

May 9, 2011 at 11:04 AM

return new ShapeResult(shape);

You can see this happening for instance in Orchard.Core\Routable\Controllers\ItemController.cs. It's also used for various account views in Orchard.Users\Controllers\AccountController.cs.

May 9, 2011 at 12:11 PM

 

** Solved **

Thanks for the help. In order to get this working, and to allow the shape to be rendered from a custom controller action, I simply did this;

1. Created a migration to define my partless, driverless widget;

 

ContentDefinitionManager.AlterTypeDefinition("BasketInfoWidget",
			cfg => cfg
				.WithPart("WidgetPart")
				.WithPart("CommonPart")
				.WithSetting("Stereotype", "Widget"));

 

2. Created a handler to inject my custom shape whenever this widget is rendered;

 

	public class BasketInfoWidgetHandler : ContentHandler
	{
		private readonly IOrchardServices _orchard;

		public BasketInfoWidgetHandler(IOrchardServices orchard)
		{
			_orchard = orchard;
		}

		protected override void BuildDisplayShape(BuildDisplayContext context)
		{
			base.BuildDisplayShape(context);
			
			if (context.ContentItem.ContentType == "BasketInfoWidget")
			{
				dynamic packageDisplay = _orchard.New.BasketInfo(
					BasketCount: 10,
					BasketTotal: 699.57
				);

				context.Shape.BasketInfo = packageDisplay;
			}
		}
	}

 

3. Created a template for the widget itself that just chains into rendering the custom shape (MyModule/Views/Widget-BasketInfoWidget.cshtml);

 

@Display(Model.BasketInfo)

 

4. Created a template for the BasketInfo shape (MyModule/Views/BasketInfo.cshtml);

 

@if (Model.BasketCount < 1)
{
	<div>Your shopping basket it empty</div>
}
else
{
	<div>Your shopping basket contains @Model.BasketCount items (@Model.BasketTotal)</div>
}

 

Now, adding the basket info part onto a page renders as expected. Next job, getting a custom controller action to render the same thing turned out to be quite easy.

5. In my custom controller;

 

/// <summary>
/// Partial view that will render the basket summary
/// - number of items in the basket and total value
/// </summary>
/// <returns></returns>
public ActionResult BasketSummary()
{
	dynamic packageDisplay = _orchard.New.BasketInfo(
		BasketCount: 10,
		BasketTotal: 689.57
		);
			
	return new ShapeResult(this, packageDisplay); 
}

 

The end result - my widget can be added to pages, the info shape can be themed, and I can render the same, themed partial from my custom action using AJAX.

Firstly, thanks for all the help, secondly can someone "in the know" (bertrand/pete) just validate this approach is sound.

Cheers

Tony