Extending your Tab – Your code, your plugins, part 3

So far we have covered a couple of different cases around how you could introduce custom Tabs into your system to gain a better understanding of how your system is operating (Creating a simple Tab – Your code, your plugins, part 1 and Creating a typical Tab – Your code, your plugins, part 2).

These tabs have been great to get up and running, but there is a chance that after a while, you may want to get more from your tab. Specifically, to extend its look and feel, and even to enhance the data that you are seeing inside the plugin.

New Concepts

  • Controlling the layout of your tab
  • Render compound/nested objects
  • Pivoting the root table layout

Use Case 3: Show the content of a shopping cart style component you might have written, using a custom layout

This time around, it’s the same use case that we saw in part 2 but we are adding on the fact that we would like to control the layout. We want to augment what we see so the layout is clear and the data representation matches our mental model, not just the object structure we happen to be dealing with. We want to control the headers/titles, order of the columns, prefixes/postfixes, styling, etc.

Existing code
I’m going to assume that you have seen how we got the base plugin up and running (if you haven’t, checkout part 2). The code in question is that of a typical Tab that returns an object collection.

public class TabCart : AspNetTab
{
    public override string Name
    {
        get { return "Cart"; }
    }

    public override object GetData(ITabContext context)
    {
        var cart = ShoppingCart.GetCart(context.GetHttpContext());
        var items = cart.GetCartDetials();

        return items;
    }

    public override RuntimeEvent ExecuteOn
    {
        get { return RuntimeEvent.EndSessionAccess; }
    }
}

Controlling the layout of your tab
Problem
By default when we return data from our ITab.GetData() method, the object contains no styling information. The rendering engine gets us pretty close to what we want to see but still doesn’t get us all the way there.

Solution
As it turns out Glimpse supports an extension model which favours Interface Segregation. This is just a fancy way of saying that extra functionality can be added to a Tab by making a Tab implement a given interface. This interface will augment the Tab and allow it to perform functions that a base Tab would would otherwise not be able to.

This is what we would like to see:
LayoutTab
You will note that it looks similar to the previous version of the tab we had, but its layout is different.

Sample Code
In this case we are going to add on the ITabLayout interface which requires that we add on a GetLayout() method. This method should return an object which describes how the data is to be structured or laid out. To help controlling these layouts we have a fluent API (which was created by Kristoffer Ahl of Fluent Security fame). The following gives you a basic taste of what you could do to achieve the above:

using Glimpse.AspNet.Extensibility; 
using Glimpse.AspNet.Extensions;
using Glimpse.Core.Extensibility;
using Glimpse.Core.Tab.Assist;
using MvcMusicStore.Models;

namespace MvcMusicStore.Framework
{
    public class TabCart : AspNetTab, ITabLayout
    {
        //NEW CODE
        private static readonly object Layout = TabLayout.Create()
                .Row(r =>
                {
                    r.Cell("{{albumTitle}} ({{albumId}})").AsKey().WithTitle("Album (Id)");
                    r.Cell("albumPrice").AlignRight().Prefix("$").WidthInPixels(100).WithTitle("Price");
                    r.Cell("genreName").WithTitle("Genre");
                    r.Cell("artistName").WithTitle("Artist");
                    r.Cell("count").Class("mono").WidthInPixels(70).WithTitle("Count");
                    r.Cell("dateCreated").WithTitle("Added");
                    r.Cell("recordId").WithTitle("Record Id");
                    r.Cell("cartId").WithTitle("Cart Id"); 
                }).Build();
        //NEW CODE
         
        public override string Name
        {
            get { return "Cart"; }
        }

        public override object GetData(ITabContext context)
        {
            var cart = ShoppingCart.GetCart(context.GetHttpContext());
            var items = cart.GetCartDetials();

            return items;
        }

        public override RuntimeEvent ExecuteOn
        {
            get { return RuntimeEvent.EndSessionAccess; }
        }

        //NEW CODE
        public object GetLayout()
        {
            return Layout;
        }
        //NEW CODE
    }
}

Render compound/nested objects
Problem
Having seen what the above has produced and the fact that we are starting to gain a real insight into how our shopping cart is operating, we have decided that we want to take things to a whole new level. We don’t want to repeat the shopping cart ID for every row (as we only want to see it once) and we want to see other aggregated information about out cart inside the tab (i.e. total value, etc).

Solution
As it turns out, Glimpse is capable of being able to render nested objects. When it detects a property which is a complex vs. primitive type, it recursively rips through that object and starts the rendering process again. For us, this means that we could construct an object that has the data we want listed above the table of cart entries.

Here is the next iteration of what we would like to see:
SummaryCartTab

Sample Code
Knowing that we want to show some more data, we can go back to the Tab and make some tweaks. Looking at the below, we can see that we have extended the model that we are returning and have added some more details to the meta data that’s returned.

public class TabCart : AspNetTab, ITabLayout
{
    //UPDATED CODE
    private static readonly object Layout = TabLayout.Create()
            .Cell("items", TabLayout.Create().Row(r =>
                {
                    r.Cell("{{albumTitle}} ({{albumId}})").AsKey().WithTitle("Album (Id)");
                    r.Cell("albumPrice").AlignRight().Prefix("$").WidthInPixels(100).WithTitle("Price");
                    r.Cell("genreName").WithTitle("Genre");
                    r.Cell("artistName").WithTitle("Artist");
                    r.Cell("count").Class("mono").WidthInPixels(70).WithTitle("Count");
                    r.Cell("dateCreated").WithTitle("Added");
                    r.Cell("recordId").WithTitle("Record Id");
                })).Build();
    //UPDATED CODE
     
    public override string Name
    {
        get { return "Cart"; }
    }

    public override object GetData(ITabContext context)
    {
        var httpContext = context.GetHttpContext();

        var cart = ShoppingCart.GetCart(httpContext);
        var items = cart.GetCartDetials();

        //UPDATED CODE
        var root = new
        {
            CartId = ShoppingCart.GetCartId(httpContext), 
            Total = items.Any() ? items.Sum(x => x.AlbumPrice).ToString() : "--", 
            Items = items
        };
        //UPDATED CODE

        return root;
    }

    public override RuntimeEvent ExecuteOn
    {
        get { return RuntimeEvent.EndSessionAccess; }
    }

    public object GetLayout()
    {
        return Layout;
    }
}

Pivoting the root table layout
Problem
As fate would have it, the above still isn’t good enough. We like the fact that the layout has progressed forward but, having seen other Tabs within Glimpse that are displayed more succinctly, we want to do more. In addition, the above rendering kinda squashes the Items table.

Solution
Another nice feature that the Glimpse rendering engine supports is the ability to pivot the way in which key/value Objects are displayed. When you do this, instead of seeing a key and value column, headings are rendered for the keys and the values are rendered in block under the corresponding header.

This time around, the below is what we would like to see:
PivotLayoutTab

Sample Code
Knowing that Glimpse can deal with nested objects and that we have the ability to pivot, we are going to group together the summary details. You will note that the pivot functionality is added by having our Tab implement the ILayoutControl interface. This is another example where we are using Interface Segregation to add functionality.

using System.Linq;
using Glimpse.AspNet.Extensibility; 
using Glimpse.AspNet.Extensions;
using Glimpse.Core.Extensibility;
using Glimpse.Core.Tab.Assist;
using MvcMusicStore.Models;

namespace MvcMusicStore.Framework
{
    public class TabCart : AspNetTab, ITabLayout, ILayoutControl
    {
        private static readonly object Layout = TabLayout.Create()
                .Cell("items", TabLayout.Create().Row(r =>
                    {
                        r.Cell("{{albumTitle}} ({{albumId}})").AsKey().WithTitle("Album (Id)");
                        r.Cell("albumPrice").AlignRight().Prefix("$").WidthInPixels(100).WithTitle("Price");
                        r.Cell("genreName").WithTitle("Genre");
                        r.Cell("artistName").WithTitle("Artist");
                        r.Cell("count").Class("mono").WidthInPixels(70).WithTitle("Count");
                        r.Cell("dateCreated").WithTitle("Added");
                        r.Cell("recordId").WithTitle("Record Id");
                    })).Build();
         
        public override string Name
        {
            get { return "Cart"; }
        }

        //NEW CODE
        public bool KeysHeadings
        {
            get { return true; }
        }
        //NEW CODE

        public override object GetData(ITabContext context)
        {
            var httpContext = context.GetHttpContext();

            var cart = ShoppingCart.GetCart(httpContext);
            var items = cart.GetCartDetials();

            var root = new
            {
                //UPDATED CODE
                Details = new {
                        CartId = ShoppingCart.GetCartId(httpContext), 
                        Total = items.Any() ? items.Sum(x => x.AlbumPrice).ToString() : "--"
                    },
                //UPDATED CODE
                Items = items
            };

            return root;
        }

        public override RuntimeEvent ExecuteOn
        {
            get { return RuntimeEvent.EndSessionAccess; }
        }

        public object GetLayout()
        {
            return Layout;
        }
    }
}

Working Sample
If you want to run what we have in the first sample, here is the link (94e9f99eda), or for the modified Tab, here is the link (e1fb2ca944), or for the finial pivoted Tab (e6d5eb04b2). As usual feel free to play around with it and modify.

8 thoughts on “Extending your Tab – Your code, your plugins, part 3

  1. Matthew Nichols

    Very nice. I was able to use these 3 posts to surface information I needed immediately. Thanks for creating these last few posts explaining the extensibility.

    Are there any client-side extensibility points? In a tab I created it would be nice to add some filtering. Where is the best place to hook in that sort of code in js?

    Thanks for a great product/labor of love
    Matthew

    Reply
    1. Anthony van der Hoorn Post author

      Thanks for the feedback and great I see that you have been able to use the information. The series isn’t finished yet and the definitely is a client side plugin model. I will be going into these soon. Let me know if you want/need this info sooner.

      Reply
  2. Marc Dürst

    Agree with Matthew – very nice posts and they helped a lot. I kind of stuck on how to style a cell of a complex object; not a table column. In your last example; how would you align the value of “Total” right? Is this possible at all?
    In my case I have a nested object where some of properties are SQL strings which I like to format with .AsCode(CodeType.Sql).

    Reply
    1. Anthony van der Hoorn Post author

      Currently this isn’t available. There is some basic formatting that you can do on a cell by cell basis, but not via altering the metadata. It might just be possible if the list index position of your total value was alway known and the same.

      Reply
      1. Marc Dürst

        Thanks for your clarification. I’ll try another approach for my data so I don’t end up with the need to style individual cells.

        Reply
        1. Anthony van der Hoorn Post author

          As a side note, with v2 for those who are wanting more control like yourself, we will most likely be supporting full HTML templates. Meaning that you will be able to provide a handlebars or mustache template and we will render that would with the data supplied.

          Reply
  3. robert

    Very nice tutorial, thank you very much.
    I’m looking for a tutorial or some documentation about extending a custom tab with some client-side functionality. Can you please point me to some resources about how to do it, I’ve been searching for a while now with very little success.

    Reply

Leave a Reply

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