6

I have a nested component structure, where my root element has a placeholder, which contains zero to many sub-components. Each sub-component has it's own price, which gets calculated inside the sub-component itself.

On the root component, I want to calculate the sum of the sub-component's individual prices. One way to go about this is to have the root component access all the sub-components found inside the given placeholder, fetch the price and calculate the sum. However, I feel that it's not really the proper way to do this. Instead, I would like to have some sort of pub/sub system, where the calculating of each sub-component price would trigger an event, that the root component could listen to etc. The problem is however, that the root component exists multiple times on the same page item, meaning that I somehow need to have a unique identifier that tells which root component each sub-component lives under.

Using Sitecore 8 with MVC, my question is how this best can be done, while still having a loose coupling between the subcomponents and the root components.

5

I've seen (and tried) many different approaches to this problem. I'm going to propose one, but there will be quite many alternatives. Let's see what the community comes up with.

Your first problem is the execution order of your components. In your scenario; the container component will execute its Action before any of the sub-components. There really isn't much that can be done about this; so you're going to have to break down your container component into two separate actions. I would suggest Index and Sum. I am also assuming ControllerRendering.

To deal with execution order - this approach can be used:

<div>proceed to output your container html here<div>
<div class="container">
    <div class="child-elements">
      @Html.Sitecore().Placeholder("main")
    </div>

    <div class="summary">
      @Html.Sitecore().Placeholder("sum")
    </div>
</div>

Why does this work?

Essentially because the Razor file is processed top-down, we know the "main" Placeholder() call will get executed before the "sum" Placeholder call.

With this in place; you are ready to communicate. The very simplest approach uses HttpContext.Current.Items. At the end of your Index Action method; do something like HttpContext.Current.Items["pricesummary"] = new SummaryResults() (or even just set it to 0d). Your child elements would then do something like

if (HttpContext.Current.Items["pricesummary"] != null)
    HttpContext.Current.Items["pricesummary"] += myPrice;

And lastly; your Sum() Action method would output the sum.

In this example, you could replace the "sum" Placeholder with a direct call to your action method, if you prefer.

Lastly: Jeremy Davis wrote a very decent summary of this problem, with a few more advanced solutions to the problem. Find his post here: Getting MVC Components to Communicate.

5

Given the complexity of the available solutions, and the changing (changed?) nature of web applications, in my opinion the "KISS" approach to this is to do it client side.

A subcomponent price change should trigger an AJAX JSON post to recalculate pricing for the root component in which the subcomponent lives. Using a framework like angular or react should allow you to isolate the scope of each "root" component.

Example fiddle: https://jsfiddle.net/j1aduqwo/2/

Contents below as well. This would definitely be better done with a Javascript framework that supports data binding (Angular, React, Knockout), and you would want to push all price calculation server-side.

Markup

This would be the output generated by your MVC components.

<div class="root" id="root1">
  <h2>
     Root 1
  </h2>
  <p>
    <strong>Price: <span class="price">$0.00</span></strong>
  </p>
  <div class="subcomponent">
    Subcomponent Qty: <input type="number" id="subcomponent1" class="subcomponent-qty" data-pricetype="1" />
  </div>
  <div class="subcomponent type2">
    Subcomponent Qty: <input type="number" id="subcomponent2" class="subcomponent-qty" data-pricetype="2" />
  </div>
  <div class="subcomponent type2">
    Subcomponent Qty: <input type="number" id="subcomponent3" class="subcomponent-qty" data-pricetype="2" />
  </div>
</div>

<div class="root" id="root2">
  <h2>
     Root 2
  </h2>
  <p>
    <strong>Price: <span class="price">$0.00</span></strong>
  </p>
  <div class="subcomponent">
    Subcomponent Qty: <input type="number" id="subcomponent1" class="subcomponent-qty" data-pricetype="1" />
  </div>
  <div class="subcomponent type2">
    Subcomponent Qty: <input type="number" id="subcomponent2" class="subcomponent-qty" data-pricetype="2" />
  </div>
</div>

Script

My jQuery is a little rusty but this should give the general idea.

$(".subcomponent-qty").change(function() {
    var data = {
    components: []
  };

  //construct JSON to represent root component state
  var root = $(this).closest(".root");
  data.rootId = root[0].id;
  var prices = root.find(".subcomponent-qty").each(function(index,value) {
    var component = {
        componentId: value.id,
      quantity: value.value,
      type: $(value).attr("data-pricetype")
    };
    data.components.push(component);
  });

    //calculate pricing -- this would be replaced with a service call
  var price = 0;
  for (var i = 0; i < data.components.length; i++) {
    var component = data.components[i];
    var componentPrice = 0;
    switch (component.type) {
        case "1":
        componentPrice = 1.99;
        break;
      case "2":
        componentPrice = 2.99;
    }
    componentPrice = componentPrice * component.quantity;
    price += componentPrice;
  }
  price = price.toFixed(2);

  //display new price
  root.find(".price").text("$" + price);
});
  • 1
    Client side is definitely a viable approach. Even better if you could provide an example of what this could look like? :-) – Mark Cassidy Sep 26 '16 at 12:17
  • This is inline with my recommendation that I gave the poster on Slack. Creating a singleton control service that provides the calculations and pricing as needed, and use AJAX from the various controls to query for information. This allows you to have the pricing information not just available to the front end ui for display, but also available to backend controls and queries for reuse. – Pete Navarra Sep 26 '16 at 14:48
0

I like the solution from Mark Cassidy better and I never used the following solution myself.

You could hide the price in the markup.
Call the placeholder and save the output in a variable. Extract the prices form the markup and add them up.

@{var placeholder = Html.Sitecore().Placeholder("main");
int sum = XyzHelper.GetSumFromPlaceholder(placeholder);}
<div class="maincomponent">
  @placeholder
</div>

It's fast implemented and you don't have to change the rendering structure.
Or instead of calculating the sum in the backend calculate it in the frontend.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.