In the view for the page, create a new partial view that renders the tab menu only (the ul - li structure) based on property on content area (loop over the filtered items in it or create a separate viewmodel for it).
The view for the block should only render the content in the div below...
Personally I would probably extend the viewmodel with a tabnavigation property and handle that logic in controller and the create a new partial view for rendering it in the view for the page. (right above the Html.PropertyFor() that renders the contentarea).
Creating a custom html helper and use it in the view for the page is another option but I prefer to have logic like that in a controller. It's a matter of taste though...
So, in first scenario..
I would create a block with all atributes that I need(title, id, text)
create partial view for that block?
render textual content in it,
then create new view for rendering ul li tabs,
and use render partial to render them but in render partial I would have to send all blocks again to render partial somehow? right?
public class StandardPageViewModel : PageViewModel<StandardPage> { public StandardPageViewModel () : this(null) { } public StandardPageViewModel ViewModel(StandardPage currentPage) : base(currentPage) { } public IEnumerable<Tab> TabNavigation { get; set; } } public class Tab { public string Name {get;set;} public string Id {get;set;} }
I would create a custom view model like above for the page and then in controller you can fill the TabNavigation property of the viewmodel by iterating over your ContentArea.FilteredItems collection. Blocks need to have those properties as well. Preferably you let block implement an interface IHasTabNavigation or similar and check for that.
Hi Haris,
It all depends on how advance you want this to make.
Do you want to implement "on page edit" functionality, or is it ok to use "all properties" view? If you don't need "on page edit", then you don't have to create partial views / controllers for blocks at all.
Simply create two foreach loops in your page view. Render tabs in the first loop, and tab content in the second one.
I suppose you don't need ID property in Block Model. It can be quite confusing for the editors.
I would create a ViewModel for each tab, and have the ID property there. You can set that ID in Page Controller. I suppose it's ok to use GUID here (can be unique for every request, right?).
When you get this working, then you can implement "on page edit".
Aj sa srecom :)
Hi Dejan,
I get the idea, two loops in view one for navgation, one for content. But I dont understand for each of what ? I got into Episerver 6-7 days ago, still struggling to get to know all what this cms does :)
If I have content area, with many blocks in it. Can I say in view of a page foreach(block in content area? ) shomehow?
P.S. E hvala puno :)
You can do it in views as well, but we usually put that logic in controllers.
Let's say you have defined the following viewmodel for every tab:
public class TabItemViewModel { public Guid Id { get; set; } public string Title { get; set; } public string Text { get; set; } }
Then in Page Controller, index action, you should have something like this:
// this collection should be used in foreach loops var tabItems = new List<TabItemViewModel>(); // we want to filter out unpublished blocks, visitorgroups stuff, etc. var contentAreaItems = currentPage.MyContentArea.FilteredItems.ToList(); var contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>(); foreach (var contentAreaItem in contentAreaItems) { // get an instance of Tab Block // If you didn't set any restrictions, ContentArea can contain anything. // We need to check if blockData is of type MyBlock var blockData = _contentLoader.Get<MyBlock>(contentAreaItem.ContentLink); if (blockData == null) continue; tabItems.Add(new TabItemViewModel { // no need to keep the ID in Epi Id = Guid.NewGuid(), Title = blockData.Title, Text = blockData.Text }); }
If you want to have the code in view or in controller is a matter of taste really. I prefer to have that kind of code in controller too to keep it clean. But I would probably accept seeing it in a well structure html helper extension as well if I saw it in a code review...
Hi,
Should my Index action recieve currentPage? Right now its set to default index() And then, i send back tabitems to page view, right? sorry.. totall newbie :)))
public class StandardPageController : Controller { public ActionResult Index() { var tabItems = new List<TabViewModel>(); var contentAreaItems = currentPage.MyContentArea.FilteredItems.ToList(); var contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>(); foreach (var contentAreaItem in contentAreaItems) { var blockData = _contentLoader.Get<PageTab>(contentAreaItem.ContentLink); if (blockData == null) continue; tabItems.Add(new TabViewModel { // no need to keep the ID in Epi Id = Guid.NewGuid(), Title = blockData.Title, Text = blockData.Text }); } return View(); // Should I return tabitems here ? } }
If you're just starting with EPiServer, then you should take a look at Alloy (MVC) sample site.
You can create one in Visual Studio when you select File / New / Project / EPiServer
Every page controller has to inherit PageController<MyPageType>, and index action needs to be declared like this:
public ActionResult Index(MyPageType currentPage)
EPiServer will populate currentPage for you :)
You can also take a look at https://github.com/episerver/AlloyDemoKit for more samples.
Your tabItems collection should be a part of the page view model.
For example here: https://github.com/episerver/AlloyDemoKit/blob/master/src/AlloyDemoKit/Controllers/ProfilePageController.cs
You have ProfilePageViewModel as a page view model, and Profile (this can be your tabItems collection) as a property of page view model.
Makes sense?
Dejan,
I think it does makes sense.. I wrote a code.. but I get error on line
var contentAreaItems = currentPage.MyContentArea.FilteredItems.ToList();
Ive tried to check ifcurrentPage.MyContentArea.FilteredItems.ToList().Any() ,
currentPage.MyContentArea.IsEmpty .. but it stills throws me error "Object reference not set to an instance of an object"
Hi Haris,
I didn't test the code :) If a content area does not contain any items, then currentPage.MyContentArea will return null.
if (currentPage.MyContentArea != null) { var contentAreaItems = currentPage.MyContentArea.FilteredItems.ToList(); // ... }
Thank you Dejan :) you helped me kickstarting my episerver experience :) :) :)
I have following problem, Im building tabs for page. I defined content area for tabs, now I'm not sure how to generate html for this "tab block" that im trying to create. The block should contain title of tab, tab unique ID, and text(like on picture) and each block that is create should be render those values like in image below.
The problem is, html of tabs in separate html tags, there are tabs buttons in unsorted list and theres content itself in div under this list. Im not sure how to implement this, since each block generates its own elements separately. I need to find a way to tell this block.. to merge content in different parts of html. I understand that I should use loop somewhere to generate html like in picture, but i dont have a list of blocks that are contained inside contentarea to say for example for ( contentarea.count()) and then render it.. as I can understand, I get each individual block in view, not a list.