Tuesday, 30 June 2009

EPiServer Composer - How to load content functions defined on a different page

This in another example of interesting EPiServer Composer (version 3.2.5) use case - on homepage there is a content area called "Bottom Area" which contains number of content functions. I want to display this whole content area on other arbitrary pages.

Here is a piece of code which can do this for us:

   1:  var structure = PageDataManager.LoadPageStruct(homepage.PageLink);
   2:  var contentArea = structure.GetContentAreaById(AreaName.BOTTOM_AREA_ID);
   3:  for (var i = 0; i < contentArea.ContentFunctions.Count; i++)
   4:  {
   5:      var data = contentArea.ContentFunctions[i];
   6:      var contentFunction = PageDataManager.InitContentFunctionControl(ExtensionGeneric.ViewMode.ExtensionNormalMode,
   7:                                                                       Page, data);
   8:      plhContainer.Controls.Add(contentFunction);
  10:      var element = contentFunction as IListItemPosition;
  11:      if (element == null) continue;
  13:      element.IsLastElement = IsLastElement(i, contentArea);
  14:      element.IsFirstElement = IsFirstElement(i);
  16:  }

  1. In first two lines I'm using PageDataManager to get instance of ExtensionPageData class for homepage and then I can easily access required content area. Please note that to load data from a different page you just need to provide reference to a different page.
  2. Heaving an instance of ContentAreaData class (contentArea variable) I have access to the list of all content functions (contentArea.ContentFunctions)
  3. In lines 5-8 I'm instantiating each content function and then it can be added to the PlaceHolder which is a container for all content functions.
  4. In lines 10-14 I'm detecting first and last element as those elements may require some special CSS classes. You can read more about IListItemPosition interface and how to figure out which content function is first/last in my previous post.
To get everything working you still need those two simple methods:

   1:  private static bool IsFirstElement(int i)
   2:  {
   3:      return i == 0;
   4:  }
   6:  private static bool IsLastElement(int i, ContentAreaData contentArea)
   7:  {
   8:      return i == contentArea.ContentFunctions.Count - 1;
   9:  }

I think it's fairly simple but if you have any questions don't hesitate to leave a comment.

Sunday, 28 June 2009

EPiServer Composer - How to figure out which content function is first (or last) within a content area

In my previous post (EPiServer-based site in 4 weeks?) I have presented number of quite high level thoughts regarding our last EPiServer project. In this and a few forthcoming post I would like to focus more on technical details and present several more interesting EPiServer Composer (version 3.2.5) use cases.

How to figure out which content function is first (or last) within a content area.

Imagine following piece of user interface:

It's an area where editors can define four arbitrary components. Components can be a text, image + text or just an image. Here is a typical html for this use case:

   1:  <div class="options">
   2:     <div class="option first"> ... </div>
   3:     <div class="option"> ... </div>
   4:     <div class="option"> ... </div>
   5:     <div class="option last"> .... </div>
   6:  </div>

Div with class options is a wrapper for all components. Each component has a class option. Quite typically in situation like this, first and last elements are a bit different. Depending on site design it's required to add some visual effects and that is why additional classes are added to the first and last element. This is very flexible approach as changes in html are minimal and final visual effect depends fully on CSS.

The problem for developer is that EPiServer Composer allows to simply drag and drop any number of content functions and each function is fairly autonomous. Content function is responsible for displaying html, it has no knowledge about context in which it was used. It doesn't know anything about content area to which it belongs, also it doesn't know anything about other content functions within the same content area. That is why to get information about context you need to operate one level above the functions.

But firstly, each content function need to implement following interface:

   1:  public interface IListItemPosition
   2:  {
   3:      bool IsLastElement { get; set; }
   4:      bool IsFirstElement { get; set; }
   5:  }

And now tricky part - changes on the higher level. A few things need to be added to the page/class which inherits from ExtensionBaseTemplate.

You need to override OnInit method:

   1:  protected override void OnInit(EventArgs e)
   2:  {
   3:      CurrentExtensionPageHandler.LoadContentArea += CurrentExtensionPageHandler_LoadContentArea;
   4:      CurrentExtensionPageHandler.LoadContentFunction += CurrentExtensionPageHandler_LoadContentFunction;
   6:      base.OnInit(e);
   7:  }
Above code adds our handlers to the following events:
  • LoadContentArea - this way we will get information about all available content areas and what is more important - count of content functions for each area!
  • LoadContentFunction - Composer fires this event for each content function. Having a total number of content functions for each area we can easily detect first and last elements.

Here are implementations for the handlers:

   1:  #region ContentAreaFunctionCounter
   3:  protected ContentAreaFunctionCounter ContentAreaFunctionCounter = new ContentAreaFunctionCounter();
   5:  protected void CurrentExtensionPageHandler_LoadContentFunction(object sender, ExtensionPageEventArgs e)
   6:  {
   7:      var element = e.ContentFunction as IListItemPosition;
   8:      if (element == null) return;
  10:      element.IsFirstElement = IsFirstElement(e);
  11:      element.IsLastElement = IsLastElement(e);
  12:  }
  14:  private static bool IsFirstElement(ExtensionPageEventArgs e)
  15:  {
  16:      return e.ContentArea.Controls.Count == 0;
  17:  }
  19:  private bool IsLastElement(ExtensionPageEventArgs e)
  20:  {
  21:      var numberOfElements = ContentAreaFunctionCounter.GetContentAreaFunctionCount(e.ContentArea.ID);
  22:      return e.ContentArea.Controls.Count == (numberOfElements - 1);
  23:  }
  25:  protected void CurrentExtensionPageHandler_LoadContentArea(object sender, ExtensionPageEventArgs e)
  26:  {
  27:      var functionsCount = e.ContentAreaData == null ? 0 : e.ContentAreaData.ContentFunctions.Count;
  28:      ContentAreaFunctionCounter.SetContentAreaFunctionCount(e.ContentArea.ID, functionsCount);
  29:  }
  31:  #endregion

Missing part is class ContentAreaFunctionCounter which is responsible only for keeping information about available content areas and functions count:

   1:  public class ContentAreaFunctionCounter
   2:  {
   3:      private readonly IDictionary<string, int> contentAreaFunctionCount = new Dictionary<string, int>(); 
   5:      public void SetContentAreaFunctionCount(string contentArea, int functionCount)
   6:      {
   7:          contentAreaFunctionCount[contentArea] = functionCount;
   8:      }
  10:      public int GetContentAreaFunctionCount(string contentArea)
  11:      {
  12:          if (contentAreaFunctionCount.ContainsKey(contentArea))
  13:          {
  14:              return contentAreaFunctionsCount[contentArea];
  15:          }
  17:          return 0;
  18:      }
  19:  }

I would recommend creating a base class encapsulating this functionality. Each content function which needs to be aware of it's position just need to implement the IListItemPosition interface. And that's it! :)

If it's necessary you can easily extend IListItemPosition interface to contain int Position {get; set;} property and this way give all functions idea about it's position (not just first and last one).

Thursday, 18 June 2009

EPiServer-based site in 4 weeks?

I'm happy to share with you story of a EPiServer-based project that we have delivered a few weeks ago. I have been working on quite a few EPiServer projects but this one was special for number of reasons:
  • We had only four weeks to build the site.
  • EPiServer Composer was used to build almost whole functionality.
  • Extensive usage of Model-View-Presenter design pattern.
  • We have put into use idea which is getting more and more popular within Cognifide - Content First.
Let go though those main points in details ...

Four weeks deadline

Well, that was the biggest challenge, from the very first workshop with the client to the launch day we got only four weeks. Client provided initial designs, but actual PSDs were not ready yet. So we had to understand what client needs, build it and also give client a chance to enter actual content before the deadline. Avoiding incorrect assumptions and misunderstandings is always important but with time frame like this we knew that a single bigger misunderstanding will slow us down enough to miss the deadline. Therefore we decided to use following subset of SCRUM practices:
  • daily scrum meetings - everyday in the morning we were meeting and briefly talking about 1) progress which was made the day before, 2) about plans for given day and 3) about blockers. We considered something as blocker if something was stopping us from finishing the task. It could have been unclear requirement, lack of html template or simply tech problem (with EPiServer Composer for instance).
  • retrospective meetings after each iteration - we decided to have one week sprints as it was giving us 3-4 iterations and also 3-4 chances to stop for a while and think about improving our process. After each iteration we were also delivering the latest version to the client.
I think I can safely say that without daily meetings and retrospective delivering the site before the deadline would not be possible.

EPiServer Composer

Before that project we were mostly only playing with EPiServer Composer. Since the EPiServer Dev Summit 2008, when I had the chance to see X3 presentation, I always wanted to give it a try.
If you don't know anything about EPiServer Composer then check this video to get an idea how it can change the way you work with EPiServer:

What is my impression after this project?
  • It's great for small and medium sites, very intuitive, editors were able to understand it, learn it and use it very fast.
  • From developer's perspective impression is also positive. It's really great that we can build a function/component that is completely "standalone" and can be reused many times. Although, don't think that you can build for example single generic flash component and reuse it absolutely everywhere. If usability is your main objective then probably you are going to need more the one version of flash component. But still, I like to think about it this way: we can reuse generic flash component every time when it's needed to get something working quickly and then refine it if necessary.
Composer was sometimes giving us a hard time but now, when we know how to use it I will be happy to see more projects with Composer on board.


Another fairly new thing for us. We felt that separation of concerns which code behind files offer is not sufficient. In order to get loosely coupled architecture which is later easy to maintain we decided to use MVP design pattern. We don't have any hard, unbiased metrics which can prove it, but my feeling is that MVP gives much better separation, which affects things like:
  • much higher code reusability rate
  • much easier refactoring
  • because presentation logic is separated from controls it's much easier to spot common parts of code that should be extracted
  • presentation logic has no dependency on ASP.NET runtime therefore it can be unit tested (more on that in one of my old posts - TDD with EPiServer)
  • it's easier to catch what given module is doing, code readability is increased
Before that project MVP was an interesting design pattern for me, now I know that it works in practice. Try it once and you won't be able to live without it ;)

Content First

Content First is almost a buzz word within the Cognifide. But seriously, it really makes sense. In our case, it was a requirement to give editors a possibility to work with system before the final release. Editors needed about 2-3 weeks to check how EPiServer works, learn about provided by us functionality, think about content structure and finally for entering "production" content. We had to use Content First approach and work with editors in parallel. What are the implications?
  • Increased complexity - for a few reasons:
    • we were not working with test data therefore we had to think about backward compatibility or about upgrading content to make sure that it works with the latest code. There was no option like "forget about that component which we delivered last week and start using this new one ... by the way .. you need to recreate all pages that use old component".
    • additional environment to deal with - editors need separate server, you need to updated this server frequently. Lots of things can be automated here but there are always some upgrades (new properties, required configuration etc.) that will require your time.
    • editors will always find a way to break something, again, you have to deal with it.
  • Greater chance to deliver what is really needed - sometimes you can find important requirements only when working with real content, things like incorrect layout of some random components caused by too long title etc. Content itself might be an issue and while working on "Lorem ipsum" data you can't really discover this issues until the very last moment before the deadline.
  • Site is well tested - before the deadline site was checked by developers, testers and the client. Client has everything in place, level of confidence that system won't break in a last few moments before the deadline is high.
  • Dev team has more time to deliver whole functionality - because client can work with the system there is no need to deliver "final" release two or three weeks before going live and then praying for small number of critical bugs. Editors and developers can work in parallel which gives both sides additional time.
It was a cool project to work on, very dynamic, very agile and successful. It was also very busy time with a lot of pressure on us. But in the end we have learnt a lot and I think we all have a feeling that it was a decent achievement.