Monday, July 12, 2010

What a Game! Now Reports…

Just watched the cup final – Congrats Spain – and I feel for the Dutch. But we Swedes did not even qualify so I will not mention soccer ever again.

So how about a few words on Reports?

Reports have been a returning question for ECO – how do we do them? The standard answer from our side has always been : do them the same way you would without ECO. After all ECO objects are first class .net domain objects so what is the fuss about?

The problem is that there are very few ideas out there on how to take domain objects to paper. Almost all report generators goes smack into the database; and even if that is fully possible in an ECO based system as well, it makes you feel so dirty… Can we fix it? Yes we can! (Citation from Bob the Builder)

I have been on and on about my fascination for Xaml, WPF and Silverlight and you will not be surprised when the reporting solution I present today is purely Xaml.

I have also been on and on about how important I think ViewModels are; especially the declarative kind of ViewModel we presented with ECO5 – of course you can hand code viewmodels like suggested by many other .net developers – but hey – will not that be a lot of work and a lot of code to maintain? But what can they do? If they are not using a model centric framework like ECO they will always have to little information to create them declaratively.

Suppose you have a Model like this:

Model driven development with ECO

It is not an uncommon case I think and contains just the enough details to make it interesting.

  • I want a multipage report with details about the client on page one.
  • I want the Invoice rows grouped by the Product category.
  • I want a sum of number of items and a sum of price per Category
  • I want a Total sum after all Invoice rows
  • I want a Header and a Footer to be placed on each page
  • I want to be able to put boiler plate texts anywhere on the report as I see fit

So given this information it is clear to me that I will need do some rendering to transform the data readably available in the Model to get the Sums and Groups that I require. As always (when it comes to me) my weapon is a declarative OCL defined ViewModel:

image

Most of the transformations here are straight forward; like Customer_Name is defined by self.Customer.Name – even my dog will get that (actually I do not have a dog, but if I did I would make a point out of learning it basic UML and navigation with OCL because EVERYONE needs it to get their thought process in gear).

But what is up with the transformation I marked with a big 1. CategoryGroupedRows defined as self.InvoiceRow->GroupBy(x|x.Product.Category).

(If you are confused about the x in the expression like in “x|x” here comes a short explanation:
The x is what is called a loop-variable. It could have been anything like a ”y” or “theLoopVar”. The “|” (pipe-sign)  is the separator between the loop variable definition and the expression. 
The loop variable is optional; you need not have it… At least in theory you need not have it… The reason for us needing it now is that leaving it out would give an expression of “…->GroupBy(Product.Category)” which would be fine unless we have a valid navigation named Product AND a valid Type named Product so we get an ambiguity here that the OCL editor will warn us about. All of this is in the OCL specification available over at the OMG’s site  if you are up for some seriously boring reading)

GroupBy is an OCL function that operates on a list and groups into sub lists all items that are equal when applying the GroupBy expression on them. So in this case all my InvoiceRows will be grouped into sub lists based on the Category they refer to (via the product association).

Check out the ViewModel association from the 1 to the CategoryGroupedRow ViewModelClass what is up with that? CategoryGroupedRow does not even have a type defined? What is going on? Are these rhetoric questions driving you crazy? YES! The GroupBy operation is one of two operations in OCL that creates Tuples (the other one is Collect) – Tuples are temporary constructs, they are types just as valid as any other type, but they are not Modeled types. The ViewModel-editor solves the missing modeled type by saying “Derive type from referring column”.

In this particular case the type will be defined like this: {Category:Category,List:Collection(InvoiceRow)}. You need not know or remember any pattern here – just open the OCL-Editor on the expression and read “Current type is: Collection(Category:Category+List:Collection(InvoiceRow))”

So the expressions for the CategoryGroupedRow down at the 4 can use these properties of the tuple; self.List.NoOfItems->Sum is actually saying “In the List where every InvoiceRow has the same Category – sum the NoOfItems up for those please” (Actually the OCL implementation does not use the word “please” I just added that to make it sound softer– The OCL implementation ECO use is written by Jonas Högström and he never makes anything soft; in fact his software is more like hardware (?WTF) ).

From the CategoryGroupedRow we have a ViewModelAssociation following the very List that the GroupBy created up at 1,  and at the 5 we are back at the InvoiceRow so that we can define some rendered values there to. Like Price that multiplies the NoOfItems with the product price.

Also check out the expressions at 2and 3 they use the Collect operation that is the other OCL function that creates tuples – it does so by taking any number of expressions separated by a comma sign and produce a Tuple.

Actually I am a bit worried about the #3 expression; not that it does not do its job – I know it does – No, what worries me is that Jonas will see this and come up with a much shorter expression that finds the most used category. And if it is not Jonas, it will probably be Peter Morris that finds a shorter expression… This is nagging me… I will leave some space here:

Hans Karlsen has the expression at #3.
Jonas has this: ___________________________________
Peter has this: ____________________________________

There. Much nicer. Now I can say I made my expression that way to trigger some feedback…

Anyway – I have my data – now I want it on paper. I have this client that constantly says “How hard can it be?” suggesting that nothing is hard or complex. Boy does it show that he is not a software developer. I think a much more relevant statement is “How hard should it be” – thus if something is overly complex we need to make it simpler.

I create a WPF usercontrol and create my ReportTemplate:

image

 

In Xaml it looks like this (cut for brevity ):

The ViewModel:

   1: <vm:BindableViewModel x:Key="vm1" 
   2:                       ViewModelName="VMInvoice" 
   3:                       EcoSpaceType="{x:Type eco:EcoProject12EcoSpace}" 
   4:                       Cursored="false"></vm:BindableViewModel>

and the head:

   1: <Grid>
   2:     
   3:     <Report:ReportTemplate x:Name="TheReport" 
   4:                   DataContext="{StaticResource vm1}">
   5:         
   6:         <Report:Header  x:Name="Header">
   7:             <StackPanel>
   8:                 <TextBlock  Text="Invoice" 
   9:                    HorizontalAlignment="Center" 
  10:                    FontSize="46" 
  11:                    FontFamily="Stencil"></TextBlock>
  12: ...

Things go on defining stuff like the lists, notice the Sublist reference to a new listContent object defined in the Resource section:

   1: <Report:ListContent ItemsSource="{Binding Path=VMInvoice/CategoryGroupedRows}" 
   2:             Header="{Binding ElementName=ListContentHeader}" 
   3:             HeaderRepeated="{Binding ElementName=ListContentHeaderRepeated}"
   4:             ItemTemplate="{StaticResource ListItemContent1}"
   5:             SubList="{StaticResource InvoiceRows}">
   6:     
   7: </Report:ListContent>

The code to instansiate the print template and to Render the FixedDocument looks like this:

   1: Invoice inv=CreateTestData();
   2:  
   3: UserControlPrintInvoice uc = new UserControlPrintInvoice();
   4: BindableViewModel bvm = 
   5:               (uc.Resources["vm1"] as BindableViewModel);
   6: bvm.SetEcoSpace(ecoSpace);
   7: bvm.RootObject = inv;
   8:  
   9:  
  10: Eco.Report.ReportTemplateRenderer repRend = 
  11:             new Eco.Report.ReportTemplateRenderer();
  12: FixedDocument fd = new FixedDocument();
  13: repRend.RenderFixedDoc(uc.TheReport, fd);
  14: documentViewer1.Document = fd;

And the result looks like this:

image

I especially like the last page that has some flow text in it with mixed databindings:

   1: <Report:Content>
   2:     <TextBlock TextWrapping="Wrap" Margin="30">
   3:     <Span FontStyle="Oblique">
   4:         <Run>Oh and please note that since most of stuff you ordered falls into the category of </Run>
   5:         <Run FontWeight="Bold" Text="{Binding Path=VMInvoice/LargestCategory}"></Run>
   6:         <Run> you cannot expect us to take anything back...</Run>
   7:         <Run>As you may or may not be aware of, things that fall inside the </Run>
   8:         <Run FontWeight="Bold" Text="{Binding Path=VMInvoice/LargestCategory}"></Run>
   9:         <Run> category really ruins anything it comes close to.</Run>
  10:         <Run Background="Yellow"> FOR THIS REASON NO RETURNS. </Run>
  11:         <Run> As you may have noticed no one else in the whole world sells this crap but us...</Run>
  12:         <Run> Once you open your package you will probably understand why.</Run>
  13:         <Run> Anyway we want to thank you </Run> 
  14:         <Run FontWeight="Bold" Text="{Binding Path=VMInvoice/Customer_Name}"></Run>
  15:         <Run> for shopping with us. Welcome back!</Run>
  16:     </Span>
  17:     </TextBlock>
  18: </Report:Content>

And the last page comes out like this:

image

Further more the reports fullfills this:
everything is controlled by databinding
page numbers
nested lists in as many levels as I see fit
headers and footers of all the lists
a header that will repeat if the data spills over to another page
a design time environment where I can see the report layout

This complete sample will be in the Demos folder.

Comments appreciated.

1 Comments:

Blogger Lars said...

Finally!

I've been waiting for years for not having to go stone age (that is SQL) for reporting.

Nice!
Lars

July 18, 2010 at 11:31 AM  

Post a Comment

Subscribe to Post Comments [Atom]

<< Home

 
Contact Us | Terms of Use | Privacy Statement © 2009 CapableObjects