Using the “layouts” feature in Aurelia

What’s a layout?

I was quite recently involved in a discussion in the Aurelia Gitter chatroom in which Rob Eisenberg (master of Aurelia) and an Aurelia user were discussing a feature which would allow “layouts” for any given route.

To set the scene: a common pattern seen in the chatroom was developers wanting to use a vastly different screen layout for their application depending on if the user was logged in or not.

MVC and ASP.NET have had this since the early days in the form of layouts/master pages.

The Aurelia framework was missing this feature, and attempting to do this with child routers was a bit more painful than it should have been.

I volunteered to have a go at implementing this feature, having had a bit of experience with the internals of the framework.

How it works

A layout replaces the view that would have been loaded into the router-view component when navigating, it then projects content from the former into the layout using the new Shadow DOM v1 slots implementation.

Think of it as a sheet of paper with holes cut out where you want your content to fit.

Your layout view should have one or more slot tags to represent the placeholders where you want to project content. It might look something like this:

<template>
  <div class="a-lovely-container">
    <slot name="content1"></slot>
  </div>
  <div class="some-other-container">
    <slot name="content2"></slot>
  </div>
</template>

The view you want to load into the layout should specify which content goes where by adding a slot attribute to each element to specify which slot it should be projected to:

<template>
  <p slot="content1">Some text 1</p>
  <p slot="content2">Some text 2</p>
</template>

In the above scenario, the resulting HTML would be:

<template>
  <div class="a-lovely-container">
    <p>Some text 1</p>
  </div>
  <div class="some-other-container">
    <p>Some text 2</p>
  </div>
</template>

Things to note

  • You don’t have to name your slot if there is only one of them, but if you don’t specify a slot name with multiple slots, don’t expect to see more than 1 of your bits of content, only 1 unnamed slot is supported
  • The order of elements in your original viewmodel doesn’t matter as they will be lifted from that particular DOM fragment and re-positioned in the slot that matches the attribute you specified

Ok, so how do I do that?

In order to use a layout, you add one or more additional properties on your route config. These are essentially identical to the compose element (just with a layout prefix):

  • layoutViewModel – you can specify a viewmodel to load instead of just using a view for your layouts. This viewmodel can have logic associated (so you could inject some configuration store object and hide/show parts of the UI etc).
  • layoutView – you will probably use this the most. This specifies which view to render as the layout or overrides the view used when specifying a viewmodel.
  • layoutModel – this is an additional parameter that will be sent to the layout viewmodel’s activate hook. I haven’t found a good reason to use this yet, but since compose has it, why not?

Any of the above parameters can also be set on the router-view element which will be used as a default e.g.:

<template>
  <h1>My Cool Page</h1>
  <router-view layout="default-layout.html"></router-view>
</template>

You can also specify a layout per-viewport by adding the above parameters to each viewport specified in your route config:

{ route: 'page1', viewports: { left: { layoutView: 'left-layout.html' }, right: { ... etc

Examples

With the following route config:

export class App {
 configureRouter(config, router) {
 config.map([
   { layoutView: 'your-layout-view.html', route: 'page1', moduleId: 'page1' },
 ]);
 }
}

You would see your-layout-view.html loaded up with your content projected accordingly.

Here’s a live example:

https://gist.run/?id=677f5ca6a20b315a10d77d224be2cebb

The bad news is, this was an early implementation using content selectors instead of the new Shadow DOM v1 implementation in the latest framework release, but at least it gives you an idea.

Note: the above demo should have a navigation bar, if it doesn’t resize the right hand preview pane to be larger. If you navigate between the two pages, you can see that the same module is loaded for both pages, but the appearance of the page is completely different (content is loaded into different places).

MVC4 and JQuery DataTables

The preamble…

So I’m messing with MVC4 lately and I wanted to use the jQuery DataTables plugin (http://www.datatables.net/).

First I had this working by loading rows using the HTML DOM. Great, it just loaded everything up and I could page/sort etc. Whilst the speed was impressive for the DOM manipulation that was happening behind the scenes, it started to feel a little sluggish over 5000 rows (and this is running on a new Core i7 decent piece of kit so…)

I looked at the documentation and I wanted to use an Ajax request to retrieve the data as JSON from a controller in my application. Eventually I came up with a ViewModel implementation which communicated nicely with the DataTables implementation and made it abstract/generic so that it would work with any data type. I’m still looking at using expression trees to discover properties on the data model, as at the moment I’ve had to resort to using the System.Linq.Dynamic add-on that I found on MSDN (it adds some Linq style extension methods that let you query using strings instead of expression trees)

Using the implementation

With the right implementation and an extension method or two the usage is very straightforward and intuitive

Example View

@using JMC.MVC.DataTables 
@model JMC.Dynamics.GP.Expenses.Models.DebtorListViewModel 
@{     
    Layout = null; 
} 
@Html.Raw(Model.ToJson(x => new { x.CustomerNo, x.Contact, x.CustomerName }))
// Extension method here just loops through the rows and runs a callback for each one so you can select the properties. Best to use this format as it's what DataTables expects

And the ViewModel

    public class DebtorListViewModel : DataTablesRequestViewModel
    {
        protected override IEnumerable<Debtor> GetDataInternal()
        {
            // We want to use the data sent by the datatables jQuery plugin to decide how to show the data
            // The request ViewModel will automatically parse the request and fill in the values that the datatables jquery plugin has created
            using (var ctx = new DynamicsContext())
            {
                // Create the query
                IQueryable qry = ctx.Debtors;

                // Add any sorts in the request - use Dynamic Linq library here... either that or figure out how to do this with expression trees!
                foreach (var sort in Sorts)
                    qry = qry.OrderBy(sort.ToString());

                // If a search string is included, add the where clause
                if (!string.IsNullOrWhiteSpace(sSearch))
                    qry = qry.Where(x => x.CustomerName.Contains(sSearch)
                                    || x.CustomerNo.Contains(sSearch));

                // Count the total rows in the data set 
                TotalRowCount = ctx.Debtors.Count();

                // Count the filtered rows our query returns
                FilteredRowCount = qry.Count();

                // Send the request viewmodel with the data to the view 
                // Get the data, skipping the offset requested and returning the number of items requested
                if (Sorts.Any())
                    return qry.Skip(iDisplayStart)
                        .Take(iDisplayLength)
                        .ToList();
                else
                    // Should never really get here, but when testing you can remove the AjaxCall attrib from the controller and run the action to test the JSON and 
                    // it's likely you'll hit this because no sort info will be passed across in the querystring
                    return qry.ToList();
            }
        }
    }

I’ve left my original comments in there but basically, you inherit from the generic DataTablesRequestViewModel<T> and then override the GetDataInternal() method. In this method you query your data (I’ve used Linq to Entities here) and return what DataTables asked for. You can inspect the properties on the ViewModel which will tell you how many rows to offset by and how many to return, and also the search term passed in by the user amongst other things (the implementation could probably use a few more properties from DataTables as it does send a lot of information about what the user requested!)

A few (small) requirements and notes:

  • You need to provide column names when building the DataTable using the DataTables API in order for the data to map correctly to the columns.
  • You also need to use a custom ModelBinder class in order to map the querystring values that DataTables passes into a meaningful collection of column names for sorting (DataTables passes the column indexes and the sort orders as sSort_0 to sSort_10 etc so you need to parse this)
  • The data fetch implementation currently relies on System.Linq.Dynamic – obviously this isn’t a problem in the DataTables view model, it just provides a list of DataTableSort objects which contain a PropertyName and a SortDirection, but I’d like to try and get this working with expression trees so that I can parse the column name from the values DataTables provides.

Also: there isn’t much error handling in the DataTables implementation so it needs testing for potential breaks when strange values are passed

An example jQuery DataTable:

$(document).ready(function () {

        // Create a data table and enable jQuery UI themeing
        $("#dataTable").dataTable({
            "bJQueryUI": true,

            // The Ajax URL to call
            "sAjaxSource": '@Url.Action("List")',
            // Set some grid options, make the processing box appear when the page loads and enable server side processing (required for the ajax requerying)
            "bProcessing": true,
            "bServerSide": true,
            // You need to provide column names for the data to map
            "aoColumns": [
                            { "mData": "CustomerNo", "sName": "CustomerNo", "sTitle": "Account Code" },
                            { "mData": "CustomerName", "sName": "CustomerName", "sTitle": "Client Name" },
                            { "mData": "Contact", "sName": "Contact", "sTitle": "Contact Name" }
            ]
        });

    });

And the result is a fully searchable, sortable grid which is lightning fast, easy to implement and supports any data scenario.

Now if I can just work out how to add attachments to this post…

Aha: https://www.dropbox.com/s/f7xtopj6hbbrog3/JMC.MVC.DataTables.zip


Upgrading NAV Classic Reports to Microsoft Dynamics NAV2013

Recently I had been tasked with an upgrade project for Microsoft Dynamics NAV; namely upgrading one of our clients from their old NAV5 installation to a brand new NAV2013 implementation.

I say brand new implementation because it was almost exactly that – a reimplementation of their existing ERP solution in a new piece of technology.

I won’t list the all of the differences between the versions but one of the key issues is the removal of the ‘classic’ client and the transition to a fully fledged role-tailored client (RTC) which runs outside of the development environment (in older versions of NAV such as NAV5 and lower there was only a classic client which also served as the development platform).

The upgrade path for NAV is quite like the path to Mordor – fraught with danger. It’s a very manual process in places, a lot of the upgrade work must be done via text compare tools in order to ensure that bespoke code is preserved during the switch. I’m sure a lot of people from a .NET background who find themselves working with NAV at some point will agree that the current toolset is lacking (no intellisense, method browser in a separate pop-up window which you need to access a menu item to get to, variables also declared in this window…!)

In addition to the removal of classic NAV, Microsoft have also completely removed the proprietary report rendering engine and replaced it with SQL Server Reporting Services. If you are familiar with reporting services you will know that although it is a great reporting platform it is not without its gripes. The reports don’t actually run on the server. The report data is streamed to the client as a giant XML based dataset, and the report rendering happens on the client machine (using RDLC reports). This means that you need to build your data set in NAV with a clunky tree based interface which has been made more difficult to work with in NAV2013 by the addition of the data fields into the tree (previously only the data items were present but not the field names). You can’t collapse just the fields; collapsing a data item hides all child items!

The proprietary engine worked more like Crystal Reports in that it was quite easy to determine how a page was going to render without any guesswork (Crystal even allows you to edit a report preview meaning that you can quite easily position elements etc). Reporting services also doesn’t really understand the concept of ‘a page’. You can’t create one or more data items that appear at the bottom of a page. You can have a footer, but headers/footers are not allowed to house data containers. You can use expressions to lift data off data containers in the body area, but that data must be rendered on the page you are viewing or your expression will return a blank value (funnily enough you can make a section invisible and still get the data from it, but only if it’s invisible on the current page!?). Luckily you can use aggregate functions to grab the first/last/max/min data item for your header/footer which can work around most issues.

Due to the above, you can’t have dynamically sized page headers and footers (the header and footer always takes up the same space no matter the content) yet the body of the report can expand and contract based on what is present. This means it’s not that easy to replicate what a client once had in their previous NAV installation in the new reporting engine, leading to longer development time and a lot of ‘tweaking’.

It doesn’t seem to be uncommon to spend a couple of days chopping and changing a single report and then to have to compromise on the final output. Microsoft bundled a tool to create layouts for SSRS reports based on classic NAV reports, and though this works for simple table based reports (which to be fair will usually be the majority), it doesn’t do a very good job for document layouts such as quotes and delivery notes. In fact, I’d find myself starting again from scratch for some reports as the upgrade had made a massive mess of them.

I suppose the morale of the story is: if you find yourself in a similar situation and are quoting for NAV2013 report upgrades, make sure to look into the amount of documents with complex layouts and give yourself at least a couple of days for each one!

Navigating in an MVVM environment

One of the things that IoC tries to to do is make required dependencies part of the class constructor for your service implementations. One of the issues with this is that instantiation of the VM is handled by the container – meaning that you can’t just put your args into a set of overloaded constructors (well you could but I think that adding a load of constructors can complicate simple classes and it just doesn’t feel right)

Recently I was trying to find a good way of allowing a ViewModel to depend on certain services, but to still allow some sort of ‘setup’ method to initialise it.

An interface based approach was the first thing that came to mind – I could create an interface which allowed arguments to be passed into the VM after instantiation. I realised that this approach would also require a service to ensure that the correct lifecycle was followed when building the VM.

I ended up borrowing from Caliburn Micro’s Event Aggregator implementation and coming up with the following:

First an interface which VMs would take as a dependency which is used to invoke a navigation event in the application


public interface INavigationService
{
    void Navigate(Type viewModelType, object modelParams);
}

Now we need the interface that will allow parameters to be served to initialise the VM


// This is just to help with some reflection stuff
public interface IViewModelInitialise { }

public interface IViewModelInitialise<T> : IViewModelInitialise
{
    // It contains a single method which will pass arguments to the viewmodel after the nav service has instantiated it from the container
    void InitialiseViewModel(T modelParams);
}

This is the implementation of the navigation service interface


public class NavigationService : INavigationService
{
    // Depends on the aggregator - this is how the shell or any interested VMs will receive
    // notifications that the user wants to navigate to someplace else
    private IEventAggregator _aggregator;

    public NavigationService(IEventAggregator aggregator)
    {
        _aggregator = aggregator;
    }

    // And the navigate method goes:
    public void Navigate(Type viewModelType, object modelParams)
    {
        // A flag to track if we found a setup method to invoke or not
        bool found;

        // Resolve the viewmodel type from the container
        var viewModel = IoC.GetInstance(viewModelType, null);

        // Inject any props by passing through IoC buildup
        IoC.BuildUp(viewModel);

        // Check if the viewmodel implements IViewModelInitialise and call accordingly
        var interfaces = viewModel.GetType().GetInterfaces()
                         .Where(x => typeof(IViewModelInitialise).IsAssignableFrom(x) && x.IsGenericType);

        // Loop through interfaces and find one that matches the generic signature based on modelParams...
        foreach (var @interface in interfaces)
        {
            var type = @interface.GetGenericArguments()[0];
            var method = @interface.GetMethod("InitialiseViewModel");

            if (type.IsAssignableFrom(modelParams.GetType()))
            {
                // If we found one, invoke the method to run InitialiseViewModel(modelParams)
                method.Invoke(viewModel, new object[] { modelParams });
                found = true;
            }
        }
        
        // If we didn't find a valid method to invoke, throw an exception
        if(!found)
            throw new InvalidOperationException("Could not find viewmodel initialisation method for given arguments");

        // Publish an aggregator event to let the shell/other VMs know to change their active view
        _aggregator.Publish(new NavigationEventMessage(viewModel));
    }
}

If you are curious about NavigationEventMessage it just looks like this:


public class NavigationEventMessage
{
    // It actually depends on CM IScreen implementations as the shell conducts IScreen but you can
    // use whatever is required here
    public IScreen ViewModel { get; private set; }

    public NavigationEventMessage(IScreen viewModel)
    {
        ViewModel = viewModel;
    }
}

Now in order to maintain the developer experience (so that they aren’t fishing around in the interfaces to try to work out how a VM can be initialised) I decided that params could be nested classes.

This leads to VMs that define their own params and makes the usage feel more natural

public class ExampleViewModel : Screen, 
    // We can navigate to this using DefaultNavigationArgs...
    IViewModelInitialise<DefaultNavigationArgs>, 
    // or SomeNavigationArgs, both of which are nested classes...
    IViewModelInitialise<SomeOtherNavigationArgs>
{
    public class DefaultNavigationArgs
    {
        public string Value { get; private set; }

        public DefaultNavigationArgs(string value)
        {
            Value = value;
        }
    }

    public class OtherNavigationArgs
    {
        public int Value { get; private set; }

        public DefaultNavigationArgs(int value)
        {
            Value = value;
        }
    }

    public void InitialiseViewModel(DefaultNavigationArgs modelParams)
    {            
        // Do something with args
        DisplayName = modelParams.Value;
    }

    public void InitialiseViewModel(OtherNavigationArgs modelParams)
    {            
        // Do something with args. this time they are int!
        DisplayName = modelParams.Value.ToString();
    }

This means your navigation calls are refactor friendly

Navigate(typeof(ExampleViewModel), new ExampleViewModel.DefaultNavigationArgs("hello"));

// Or

NavigationService.Navigate(typeof(ExampleViewModel), new ExampleViewModel.OtherNavigationArgs(15));

Please feel free to give your opinion or suggest alternative approaches/things that could make this better!