Orchard CMS - Display a list of items in view from model - orchardcms

I am an Orchard CMS beginner and I do not understand how to get and display a list of items in a view.
I've read the article Writing a ContentPart but the examples show how to use 2 properties instead of a list:
Driver
return ContentShape("Parts_Map", () => shapeHelper.Parts_Map(
Longitude: part.Longitude,
Latitude: part.Latitude));
View
<img alt="Location" border="1" src="http://maps.google.com/maps/api/staticmap?
&zoom=14
&size=256x256
&maptype=roadmap
&markers=color:blue|#Model.Latitude,#Model.Longitude
&sensor=false" />
I want to use a list of items in a view.
I've also read the article Orchard CMS Custom Widget View but i do not understand how it works, especially the following line:
var files = (IContentQuery<FilePart>)Model.Files;
Where can i find additional examples?
Is it below correct? I Use IContentManager contentManager for transfer data to view.
Driver:
public class MyModuleWidgetPartDriver : ContentPartDriver
{
private readonly IContentManager contentManager;
public MyModuleWidgetPartDriver(IContentManager contentManager)
{
this.contentManager = contentManager;
}
protected override DriverResult Display(MyModuleWidgetPart part, string displayType, dynamic shapeHelper)
{
var MyModuleItems = this.contentManager.List<MyModulePart>(MyModulePart.ContentTypeName).ToArray();
// List of items
var MyModuleItemsViewModel = MyModuleItems.Select(MyModule => new MyModuleItemsViewModel
{
Title = MyModule.Title,
Html = MyModule.Html
});
return ContentShape("Parts_MyModules", () => shapeHelper.Parts_MyModules(
promo: Json.Encode(MyModuleItemsViewModel)));
}
MyModulePart:
public class MyModulePart : ContentPart
{
public const string ContentTypeName = "MyModule";
public string Title
{
get { return this.As<ITitleAspect>().Title; }
}
public string Html
{
get { return this.As<BodyPart>().Text; }
}
}
View\Parts\MyModule.cshtml:
#{
var MyModuleItems = Model.promo;
}

You can set anything you need in the view onto the shape you're creating from your driver. Do make sure that whenever you perform a "heavy" operation such as querying a database, you do so from within the shape factory lambda (the lambda that actually creates the shape). If you do this outside of the lambda, your query will execute even if your shape won't be displayed, which would obviously be inefficient. For example:
Driver
return ContentShape("Parts_Map", () => {
return shapeHelper.Parts_Map(
MyList: _someService.GetMyListOfData(); // The MyList property will be added on the fly to the Parts_Map shape you're creating, and will be available in the view, whose model is in fact this Parts_Map shape.
Longitude: part.Longitude,
Latitude: part.Latitude);
});
View
<ul>
#foreach(var item in Model.MyList){
<li>#item.SomeProperty</li>
}
</ul>
Remember: Shapes are dynamic objects to which you can add properties on the fly. When "rendering a shape", what really happens is that Orchard locates the appropriate Razor view based on metadata stored with the shape (each shape has a Metadata property), and sets the Model of that view to the shape object.

Related

How to efficiently return multiple DriverResults from the Display method?

This article describes how to write efficient DisplayDrivers for your Parts so that expensive code is only executed when the shape is actually displayed.
protected override DriverResult Display(MyPart part, string displayType, dynamic shapeHelper)
{
// return the shape
return ContentShape("Parts_MyPart", () => {
// do computations here
return shapeHelper.Parts_MyPart();
});
}
Now I'd like to make a Part that returns multiple DriverResults using the Combine method, with each DriverResult containing mostly the same data, which is fetched from the database. The problem is I can't think of a good way to make it efficient, since Combine doesn't take a Func parameter.
protected override DriverResult Display(MyPart part, string displayType, dynamic shapeHelper)
{
var data = ... // expensive query specific to the part
return Combined(
ContentShape("Parts_MyPart_A", () => shapeHelper.Parts_MyPart_A(
Data: data
)),
ContentShape("Parts_MyPart_B", () => shapeHelper.Parts_MyPart_B(
Data: data
)),
ContentShape("Pars_MyPart_C", ...
);
}
Can I achieve the same result so that the query is not executed if nothing is displayed and only executed once if multiple shapes are displayed?
I want to do this so I can display the same data on a ContentItem's Detail in different zones with different markup and styling. An alternative approach could be to return one shape that in turn pushes other shapes into different zones but then I would lose the ability to use Placement to control each of them individually.
I'd probably add a lazy field to your Part.
public class MyPart : ContentPart {
internal readonly LazyField<CustomData> CustomDataField = new LazyField<CustomData>();
public CustomData CustomData {
get { return CustomDataField.Value; }
}
}
public class CustomData {
...
}
public class MyPartHandler : ContentPartHandler {
private ICustomService _customService;
public MyPartHandler(ICustomService customService){
_customService = customService;
OnActivated<MyPart>(Initialize);
}
private void Initialize(ActivatedContentContext context, MyPart part){
part.CustomDataField.Loader(() => {
return _customService.Get(part.ContentItem.Id);
});
}
}
It will only be calculated if it is loaded and all the shapes will share the calculated value.

Check if ListBoxFor selectedValues is null before display in view?

I have a number of ListBoxFor elements on a form in edit mode. If there was data recorded in the field then the previously selected items are displaying correctly when the form opens. If the field is empty though an error is thrown as the items parameter cannot be null. Is there a way to check in the view and if there is data to use the ListBoxFor with the four parameters but if there isn't to only use three parameters, leaving out the selected items?
This is how I'm declaring the ListBoxFor:
#Html.ListBoxFor(model => model.IfQualityPoor, new MultiSelectList(ViewBag.IfPoor, "Value", "Text", ViewBag.IfQualityPoorSelected), new { #class = "chosen", multiple = "multiple" })
I'm using the ViewBag to pass the ICollection which holds the selected items as the controller then joins or splits the strings for binding to the model field. The MultiSelectLists always prove problematic for me.
Your question isn't entirely clear, but you're making it way harder on yourself than it needs to be using ListBoxFor. All you need for either DropDownListFor or ListBoxFor is an IEnumerable<SelectListItem>. Razor will take care of selecting any appropriate values based on the ModelState.
So, assuming ViewBag.IfPoor is IEnumerable<SelectListItem>, all you need in your view is:
#Html.ListBoxFor(m => m.IfQualityPoor, (IEnumerable<SelectListItem>)ViewBag.IfPoor, new { #class = "chosen" })
The correct options will be marked as selected based on the value of IfQualityPoor on your model, as they should be. Also, it's unnecessary to pass multiple = "multiple" in in your htmlAttributes param, as you get that just by using ListBoxFor rather than DropDownListFor.
It's even better if you use a view model and then add your options as a property. Then, you don't have to worry about casting in the view, which is always a good way to introduce runtime exceptions. For example:
public class FooViewModel
{
...
public IEnumerable<SelectListItem> IfQualityPoorOptions { get; set; }
}
Then, you set this in your action, before returning the view (instead of setting ViewBag). Finally, in your view:
#Html.ListBoxFor(m => m.IfQualityPoor, Model.IfQualityPoorOptions, new { #class = "chosen" })
Much simpler, and you'll never have any issues doing it that way.
UPDATE (based on comment)
The best way to handle flattening a list into a string for database storage is to use a special property for that, and then custom getter and setter to map to/from. For example:
public string IfQualityPoor
{
get { return IfQualityPoorList != null ? String.Join(",", IfQualityPoorList) : null; }
set { IfQualityPoorList = !String.IsNullOrWhiteSpace(value) ? value.Split(',').ToList() : null; }
}
[NotMapped]
public List<string> IfQualityPoorList { get; set; }
Then, you post to/interact with IfQualityPoorList, and the correct string will be set in the database automatically when you save.

How to use Orchard CMS template module

There is a module in Orchard CMS that allows for editing templates within the dashboard, however it is unclear how to properly utilize the output. What is the correct way to access a template in an MVC module controller, pass the template a model, and then render it within a View cshtml file?
You can build any shape with the shapefactory:
public MyController(IShapeFactory shapeFactory) {
Shape = shapeFactory;
}
public dynamic Shape { get; set; }
public ActionResult Index() {
var someModel = new SomeModel();
// You can build your shape here:
var shape = Shape.TheTemplateShapeName(SomeData: someModel.SomeProperty);
// or: Shape.New("TheTemplateShapeName").SomeData(someData);
// you can just chain on the dynamic shape
var viewModel = Shape
.ViewModel() // simple orchard method to build a viewmodel with chaining
.MyShape(shape)
.SomethingElse("Some string");
return View(viewModel);
}
Then in your MyController/Index.cshtml:
<h1>#Model.SomethingElse</h1>
#Display(Model.MyShape)

How to send Model to _Layout.cshtml (I need to send another model to Index view too)

I need to send 2 different Models, one to Index view and another one to _Layout.cshtml, how I can do it?
My HomeController:
[Route("")]
public ActionResult Index()
{
HomeViewModel model = new HomeViewModel();
model.A = _repoA.GetLatest(4);
model.B = _repoB.GetLatest(4);
model.C = _repoC.GetLatest(4);
return View(model);
}
I don't like using ViewBag, ViewData & ..., I'm looking for passing the model in same way as we passing model to Views.
You can place this in your Layout to load a partial each time... Pretty useful for loading in a piece of a dynamic menu or a widget on each page.
Along with this line in your layout you can just do your Index page as you normally would.
#{ Html.RenderAction("_widget", "Home"); }
You'll need to send it along in the ViewBag. I found the best bet was to make an abstract controller:
public abstract class ApplicationController : Controller
{
protected ApplicationController()
{
UserStateViewModel = new UserStateViewModel();
//Modify the UserStateViewModel here.
ViewBag["UserStateViewModel"] = UserStateViewModel;
}
public UserStateViewModel UserStateViewModel { get; set; }
}
Then, have all of your controllers inherit from this abstract controller.
In your _Layout.cshtml (or whatever you called it), you'll need to include the following at the top:
#{
var userState = (UserStateViewModel)ViewBag.UserStateViewModel;
}
Duplicate but refined from the 2nd answer to ASP.NET MVC Razor pass model to layout.

ASP.NET MVC DropDownListFor not selecting the correct value

I'm new in the ASP.NET Framework, I've read the fundamental and have some understanding(theory) on the framework but not much in practice.
I'm struggling with the dropdownlistfor helper method, it comes down to having a weird behavior when i attempt to change the value of the selected item programatically.
In my controller i have the Index action method that receives a parameter of type Tshirt, inside this action method i set the property named Color of the Tshirt object with a value of 2.
In the view (strongly typed) i have a list of colors and pass this list as an argument for the constructor of the ColorViewModel class that will be in charge of returning SelectListItems for my list of colors.
In the view I then set the Selected property of my ColorViewModel object with the value coming from model.Color, now i have set everything so that when i call
#Html.DropDownListFor(x => x.Color, cvm.SelectItems, "Select a color")
I will have my dropdown displayed with the selected item.
When the request(GET) is performed the page is rendered by the browser and the dropdownlist appears with the correct value selected that was established with the value 2 in the Index action method, the dropdown displays "Blue" this is correct.
Then if i select a different item in the dropdownlist (RED, having an id of one) and submit the form, the Save action method is called and i know that the model is reaching the action method with a model.Color=1, which is correct.
Then i decide to redirect to the index action method passing the model object, the index action method changes the Color property back to 2, so when the page is rendered it should display again the value of Blue in the dropdown, but it doesn't, it displays Red.
if you comment out the following line in the Save action method you will get a different behavior.
//tshirt.Color = 3;
i know this logic im following doesnt make much sense from a bussines logic perspective, im just trying to understand what i am doing wrong to not get the expected result.
Given the following model
public class Color
{
public int Id { get; set; }
public string Description { get; set; }
}
I Create the following view model
public class ColorViewModel
{
public int Selected { get; set; }
private List<Color> Colors;
public IEnumerable<SelectListItem> SelectItems { get { return new SelectList(this.Colors, "Id", "Description", this.Selected); } }
private ColorViewModel() { }
public ColorViewModel(List<Color> colors)
{
this.Colors = colors;
}
}
This is my Controller
public class HomeController : Controller
{
[HttpGet()]
public ActionResult Index(Tshirt tshirt)
{
tshirt.Color = 2;
Tshirt t = new Tshirt();
t.Color = tshirt.Color;
return View(t);
}
[HttpPost()]
public ActionResult Save(Tshirt tshirt)
{
//tshirt.Color = 3;
return RedirectToAction("Index", tshirt);
//return View("Index",tshirt);
}
}
And Finally my View
#{
List<Color> colors = new List<Color>(){
new Color(){Id=1, Description="Red"},
new Color(){Id=2, Description="Blue"},
new Color(){Id=3, Description="Green"}
};
ColorViewModel cvm = new ColorViewModel(colors) { Selected = Model.Color };
}
#using(#Html.BeginForm("Save","Home")){
#Html.DropDownListFor(x => x.Color, cvm.SelectItems, "Select a color")
<input type="submit" />
}
I have uploaded the complete code: VS Solution
Because when you redirect, you are passing the updated model
return RedirectToAction("Index", tshirt);

Resources