Skip to main content

Back-End Development

Building a single-application component using Sitecore MVC, JS MVVM Framework and SCORE – PART 1

Kid With Bricks@1x.jpg

In this series of posts, I would like to show our readers how to build out a single-application component in Sitecore using one of the many popular MVVM JS frameworks on the market – in this case Knockout, together with SCORE. Even if you don’t use SCORE in your project, you can still follow along with this post as SCORE is only utilized for initializing our JavaScript files and coupling them to our Razor Views. Knockout is also not mandatory and can be replaced by any other JS MVVM Framework.

Let’s say that we want to build a decision tree component which navigates users through options on the page represented as buttons. Each button acts as a dynamic link that navigates the user to a different set of content without refreshing the page. Each of these transitions displays new content with new choices (button links), and drives the user deeper into the decision tree. Eventually the user ends up on a result page based on their navigational input. I think you get the point…
decision step one

Here is how we could architect our Sitecore content item structure:

Content Tree

 

In order to build this component with dynamic content loading functionality, there are a couple of things we have to solve and think about first:

  1. How do we navigate through the desired choices?
  2. How do we switch between content that’s currently presented on-screen and newly loaded content?
  3. How do we ‘extract’ content added to the placeholder?
  4. How do we know the current position in the decision tree and JSON navigational structure? This is important if we want to build previous/next navigation as a part of our component.

In this blog article, we will look at how to get components placed into non-dynamic placeholders and try to answer these questions. Let’s focus first on the controller action and how we can retrieve components inserted into non-dynamic placeholders on the page by using Sitecore’s render pipeline.

 

Controller

1public JsonResult GetChoicePage(string id) //passing id of the item which contains content for displaying
2{
3Database db = Sitecore.Context.ContentDatabase ?? Sitecore.Context.Database;
4Item item = db.GetItem(new ID(id)); //get page item
5if (item != null)
6{
7PageContext.Current.Item = item; //set current page context to point to the page we retrieved
8StringWriter viewWriter = new StringWriter();
9//get ContextService object and push 'dummy' view context based on controllercontext
10ContextService.Get().Push<ViewContext>(new ViewContext(this.ControllerContext, PageContext.Current.PageView, this.ViewData, this.TempData, viewWriter));
11//here is where we render placeholder based on the full name path
12string temp = RenderPlaceholder(string.Format("Page Content/{0}", "NAME_OF_THE_NON_DYNAMIC_PLACEHOLDER_WITHIN_PAGE_CONTENT"));
13 
14return new JsonResult() { Data = temp, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
15}
16 
17return Json(string.Empty);
18}
19 
20// private method that renders content of the placeholder using "mvc.renderPlaceholder" pipeline
21public static string RenderPlaceholder(string placeholderName)
22{
23StringBuilder sb = new StringBuilder();
24StringWriter argsWriter = new StringWriter(sb);
25 
26RenderPlaceholderArgs args = new RenderPlaceholderArgs(placeholderName, argsWriter);
27args.PageContext = PageContext.Current;
28 
29CorePipeline.Run("mvc.renderPlaceholder", args);
30return sb.ToString();
31}

 

In SCORE 2.1, you can use Score.Custom.Utils.RendererUtil class like so:

1var rendererUtil = new Score.Custom.Utils.RendererUtil();
2var html = rendererUtil.RenderPlaceholder(item, "placeholderKey");

As an added bonus, you can do this from outside of a Web context (such as in an agent, in a custom computed field, etc.).

Building the Front-End

Now that we know how to render placeholder content, the next step will be to create a non-complex JSON structure for our UI front-end that will contain all possible choice elements with just a few properties, like page ID, level, depth, title, etc.

I’ve used recursion to get items and their children from the Sitecore content tree:

1public ActionResult InitDecisionTree()
2{
3Database db = Sitecore.Context.ContentDatabase ?? Sitecore.Context.Database;
4 
5ChoiceItemDto dataStructure = new ChoiceItemDto();
6int level = -1;
7CreateNavigationStructure(ContextItem, ref dataStructure, ref level);
8 
9return View("_ViewName", new DecisionTreeRenderingModel()
10{
11DataStructure = dataStructure
12ErrorContent = RenderingContext.Current.Rendering.Item.Fields["Error Content"].ToStringOrEmpty()
13});
14}
15 
16private static void CreateNavigationStructure(Item currentItem, ref ChoiceItemDto startLevel, ref int level)
17{
18startLevel.Name = currentItem.Fields["Page Title"].ToStringOrEmpty();
19startLevel.Id = currentItem.ID.ToString();
20 
21var children =
22currentItem.GetChildren().Where(
23a => a.TemplateID.Equals(new ID("CHOICE_PAGE_TEMPLATE")));
24 
25foreach (var item in children)
26{
27level++;
28ChoiceItemDto nextLevel;
29nextLevel = new ChoiceItemDto {Name = item.Name, Level = level, Id = item.ID.ToString()};
30startLevel.Children.Add(nextLevel);
31CreateNavigationStructure(item, ref nextLevel, ref level);
32 
33level--;
34}
35}
36 
37public class ChoiceItemDto
38{
39public string Name { get; set; }
40public string Id { get; set; }
41public int Level { get; set; }
42 
43public List<ChoiceItemDto> Children = new List<ChoiceItemDto>();
44 
45public int Depth
46{
47get
48{
49// Completely empty menu (not even any straight items). 0 depth.
50if (Children.Count == 0)
51{
52return 0;
53}
54// We've either got items (which would give us a depth of 1) or
55// items and groups, so find the maximum depth of any subgroups,
56// and add 1.
57return Children.OfType<ChoiceItemDto>()
58.Select(x => x.Depth)
59.DefaultIfEmpty() // 0 if we have no subgroups
60.Max() + 1;
61}
62}
63}

During Component Initialization, the navigation structure DTO is passed to the model; then it is passed by SCORE CCF (SCORE Component Communication Framework) to our JavaScript.

View

In the Razor MVC View example shown below, I have stripped additional knockout bindings in order to focus on 3 things:

  1. How the “slider-wrapper” div (main application div holder) is bound with our knockout decisionTreeViewModel;
  2. How the Navigation Data Structure DTO, URL for retrieving items, and Error messages are passed to the DecisionTree component’s JavaScript;
  3. How the div with id “decision-tree-content” is used for inserting dynamic content retrieved from the ‘GetChoicePage’ JsonResult Controller Action.
1@using Sitecore.Globalization
2@model DecisionTreeRenderingModel
3 
4@using (Html.BeginUXModule("Components/DecisionTree",
5new
6{
7Data = Model.DataStructure,
8Link = Url.Action("GetChoicePage", "DecisionTree", new { id = string.Empty }),
9Error = Model.ErrorContent
10},
11new
12{
13@class = "decision-tree " + Model.RenderingWrapperClasses,
14@style = Model.RenderingModelStyles
15}))
16{
17if (Sitecore.Context.PageMode.IsPageEditorEditing)
18{
19<!-- Handle Editor Experience Mode -->
20}
21else
22{
23 
24<div class="slider-wrapper" data-bind="with: decisionTreeViewModel">
25<div class="slider-inner">
26<div class="slides intro-wrapper">
27<div class="container">
28 
29<div class="cg-logo">
30 
31</div>
32</div>
33</div>
34 
35<div class="slides decision-tree-content-wrapper">
36<div class="container">
37<div id="decision-tree-content-header">
38 
39</div>
40</div>
41<div class="decision-tree-content-outer">
42<div class="container">
43<div id="decision-tree-content">
44<!-- dynamically inserting placeholder content-->
45 
46</div>
47</div>
48</div>
49<!-- back/next buttons & progress bar -->
50<div class="slider-footer">
51 
52</div>
53</div>
54</div>
55</div>
56}
57}

 

In part 2 of this post series, I am going to cover the JavaScript pieces of this puzzle and how a knockout model can be used to leverage front-end functionality.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Ivan Omorac

Ivan is a certified Sitecore developer with nearly 10 years of web development experience and a Microsoft Certified Professional Developer (MCPD) in both ASP.NET Developer 3.5 and Web Developer 4. His development work is guided by a personal mission--to make it simple, efficient and easily maintainable--and Ivan is always looking for ways to improve. His skill set spans ASP.NET, C#, MVC.NET, Javascript and jQuery but, no matter what he is programming in, his goal is to make the code bullet-proof and highly stable. Ivan enjoys finding solutions to challenging problems and sharing knowledge with the team at BrainJocks, and see this blog as a natural extension of that collaboration to the broader Sitecore community. When he is not in the Skybox you’ll probably find him playing guitar or hanging out at one of the local blues joints.

More from this Author

Follow Us