Calculated Properties

Exo supports defining rules that automatically calculate property values for model entities on the server, on the client, or via projection on both the client and server.  The following example demonstrates how a server model property can be calculated via a rule written in C# and how this rule is automatically projected to the client as a Javascript rule.

/// <summary>

/// A friendly description of the relative age of the item.

/// </summary>

[NotMapped]

public string AgeDescription { get; private set; }

 

/// <summary>

/// Calculates the description of the age of the item.

/// </summary>

static Rule CalculateAgeDescription = Rule<ListItem>.Calculate(

       item => item.AgeDescription,

       item => "created " + (

              item.Age.Ticks < 0 ? "in the future" :

              item.Age.TotalMinutes < 1 ? "just now" :

              item.Age.TotalMinutes < 2 ? "1 minute ago" :

              item.Age.TotalHours < 1 ? item.Age.Minutes + " minutes ago" :

              item.Age.TotalHours < 2 ? "1 hour ago" :

              item.Age.TotalDays < 1 ? item.Age.Hours + " hours ago" :

              item.Age.TotalDays < 2 ? "yesterday" :

              item.Age.TotalDays < 7 ? item.Age.Days + " days ago" :

              item.Age.TotalDays < 14 ? "last week" :

              item.Age.TotalDays < 35 ? Math.Round(item.Age.Days / 7.0) + " weeks ago" :

              "a long time ago"));

The CalculateAgeDescription rule automatically calculates the value of the AgeDescription property when it is first accessed or whenever one of its dependencies change.  In this case, the rule is dependent on the Age property.  The Rule<T>.Calculate method takes two parameters--the first specifies which property is being calculated and the second specifies the calculation to use for the property.  Both parameters must be expression trees, so the lambdas must be expressions, not statement blocks.   This allows Exo to support automatic determination of dependencies to control when the rule runs, as well as automatic conversion of .NET-based rules into Javascript.

This FireBug view of the JSON type data sent to the client by Exo shows the implementation of this server rule after translation to the corresponding Javascript implementation.  The client-side ListItem type will have a corresponding CalculatedPropertyRule defined using this information, triggered on access or change of the Age property, to symmetrically calculate the property value.  Since the Age property is also automatically calculated based on the User.CurrentTime property, which is periodically updated by the UI, the declaratively linked UI automatically refreshes the displayed age of each item as they get older.

This example shows the ideal case of a rule defined on the server that automatically exists on the client.  However, there are a wide variety of alternative scenarios that are also supported, including:


  1. Insert the following code snippet into ListItem.cs:
    		// The age of the item relative to the curent time of the user.
    		[NotMapped]
    		public TimeSpan Age { get; private set; }
    
    		// Calculates the age of the item relative to the current time.
    		static Rule CalculateAge = Rule<ListItem>.Calculate(
    			item => item.Age,
    			item => item.DateCreated == null ? TimeSpan.Zero : item.List.User.CurrentTime.Subtract(item.DateCreated.Value));
    
    		// A friendly description of the relative age of the item.
    		[NotMapped]
    		public string AgeDescription { get; private set; }
    
    		// Calculates the description of the age of the item.
    		static Rule CalculateAgeDescription = Rule<ListItem>.Calculate(
    			item => item.AgeDescription,
    			item => "created " + (
    				item.Age.Ticks < 0 ? "in the future" :
    				item.Age.TotalMinutes < 1 ? "just now" :
    				item.Age.TotalMinutes < 2 ? "1 minute ago" :
    				item.Age.TotalHours < 1 ? item.Age.Minutes + " minutes ago" :
    				item.Age.TotalHours < 2 ? "1 hour ago" :
    				item.Age.TotalDays < 1 ? item.Age.Hours + " hours ago" :
    				item.Age.TotalDays < 2 ? "yesterday" :
    				item.Age.TotalDays < 7 ? item.Age.Days + " days ago" :
    				item.Age.TotalDays < 14 ? "last week" :
    				item.Age.TotalDays < 35 ? Math.Round(item.Age.Days / 7.0) + " weeks ago" :
    				"a long time ago"));
    
  2. Insert the following code snippet into User.cs:
    		// Represents the current time for the user, which is used to determine the relative age of list items for the user.
    		[NotMapped]
    		public DateTime CurrentTime { get; private set; }
    
    		// Sets the current time of the user to the current system date and time.
    		static Rule CalculateCurrentTime = Rule<User>.Calculate(
    			user => user.CurrentTime,
    			item => DateTime.Now);		
    
  3. Insert the following code snippet into Home/Index.cshtml after the script references:
    @*
    Miscellaneous scripts
    *@
    
    <script type="text/javascript">
    	$exoweb({
    		contextReady: function (context) {
    			// Automatically update User.CurrentTime 
    			window.setInterval(function () { context.model.user.set_CurrentTime(new Date(Date.now())); }, 20000);
    		}
    	});
    </script>
    
  4. Replace the static age description "Item Age Description (eg, one minute ago)" with a binding expression {# AgeDescription} in Home/Index.cshtml:

    <div class="board-list-item-age-description">{# AgeDescription}</div>