Render blocks customly in content area

Vote:
 

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.

#150490
Edited, Jun 20, 2016 15:54
Vote:
 

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...

#150491
Edited, Jun 20, 2016 16:17
Vote:
 

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?

#150504
Edited, Jun 21, 2016 10:36
Vote:
 
   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. 

#150510
Jun 21, 2016 13:54
Vote:
 

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 :)

#150512
Jun 21, 2016 14:03
Vote:
 

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 :) 

#150517
Jun 21, 2016 14:48
Vote:
 

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
    });
}
#150519
Edited, Jun 21, 2016 15:02
Vote:
 

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...

#150523
Jun 21, 2016 15:19
Vote:
 

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 ?
        }
    }



#150525
Edited, Jun 21, 2016 15:35
Vote:
 

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?

#150528
Jun 21, 2016 15:53
Vote:
 

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"

#150543
Edited, Jun 22, 2016 10:37
Vote:
 

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();
    // ...
}
#150567
Jun 22, 2016 15:40
Vote:
 

Thank you Dejan :) you helped me kickstarting my episerver experience :) :) :)

#150571
Jun 22, 2016 16:11
This topic was created over six months ago and has been resolved. If you have a similar question, please create a new topic and refer to this one.
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.