Sep 5, 2011

The Tide Client Framework Part II: Tide Subcontexts

 

Introduction

    In the last tutorial I covered contexts in the Tide Client Framework (TCF) provided with GraniteDS.  In this tutorial I want to expand on the Tide Client Framework and introduce the little known feature “Subcontexts”.  Subcontexts escalate the TCF to being a full blown micro-architecture with capabilities comparable to Parsely or Swiz, with a fraction of the setup.  While the last tutorial covered all the great things about the Tide Context, this tutorial will begin by discussing some of it’s shortfalls, and how subcontexts fill out those missing pieces.

 

 

Shortfalls of Tide with a Single Context

    Data Injection and Tide Events are fantastic capabilities that can greatly simplify your application design and code, however, as your application becomes more complex you will start to hit a wall due to the single grouping of components.

Injection:  Too much shared data requires too much application knowledge

    Using data injection with a single context starts to fall over for a number of reasons.  As your application continues to grow and components inject more and more data into the context, you run into all the problems that make global data a bad practice.

    For example, suppose in my 100KLOC stock portfolio application I want to write a control that will perform analysis based on the user’s total net worth that has been added to the context.  Which shared object should I inject into my control?  “net”, “assetsValue”, “worth”, “totalVal”, “netV”?  Without delving into all the components to see what each shared object is and how it is changed, it is not possible to know which one to use.  You would also be opening up your project to lots of hard to debug side effects caused by developers accidentally misusing the shared global data.  Those types of problems will surely bring back bad memories if you are old enough to have done much development in C.

image

Figure 1 - Example of lots of tide components that register and share lots of data

Hard to have more than one instance of the same component

    Suppose you would like to use an MVC pattern and have a controller instance for each instance of a view. For example, suppose you have 2 independent instances of “MyView”, and 2 independent instances of “MyViewController”.

image

Figure 2 - Example context with 2 instances of MyView and 2 instances of MyViewController in the same context

    Now suppose each instance of “MyView” has data called “myViewData” that is used by the corresponding controller and each view dispatches “MyViewEvent” that should be handled by the corresponding controller.  If you want to get the benefits of injection to share “myViewData” between view and controller, and use simplified Tide events to relay the “MyViewEvent” to the appropriate controller, you might think to write your code as the following:

public class MyView
{
    [In]
    public var myViewData:Object;
    …
    public function doSomething():void
    {
        var e:MyEvent = new MyEvent();
        dispatchEvent(e);
    }
}

public class MyViewController
{
    [In]
    public var myViewData:Object;

    [Observer]
    public function handleMyEvent(e:MyEvent):void
    {
        //Do something with event
    }
}

Figure 3 - Source code for View and Controller that injects “myViewData” data and dispatches “MyViewEvent” events.

    The above code, however, will not work as desired with multiple instances in one context for several reasons.

One Injected Instance per Context

    There can only be one context data entry called “myViewData”.  So in execution, all views and controllers will actually be injecting and manipulating the same piece of data, instead of working with separate instances of “myViewData”.  For example, when instance1 controller modifies its member variable “myViewData”, it is actually modifying the view data of all the views. 

image

Figure 4 – Accidental sharing the same data between all views and controls

Events are Routed to All Listeners in the Context

    When a view instance dispatches a “MyEvent”, it will be relayed to all listeners registered within the context.  This means when one view sends a “MyEvent” event, both controller instances will receive this event even though it is intended for only one of the controllers.

image

Figure 5 – Accidental relaying events to unintended listeners

 

The Root Problem

    The root problem in these examples is that with a single context there is no way to group components such that developers can restrict the scope for injection and events to specific groupings.  Everything is global to the application.   If developers could create custom groupings for their components, injection and simplified events would be able to scale nicely as applications grow in complexity.

 

The Tide Subcontext

    Tide subcontexts allow developers to arbitrarily create hierarchical groupings of components that work with injection and events.  They give developers an enormous level of flexibility in how to organize their systems while still enjoying the benefits of data injection and simplified events.  A subcontext is just like a context in that once you attach a component to it, you can share data via data injection or broadcast events to all other components in the subcontext.  Unlike the Tide Context, however, you can have an unlimited # of related or independent subcontexts.  

image

Figure 6 – An example application that has grouped components into different subcontexts

Subcontexts as a Means to Create Independent Groupings of Components

    Subcontexts give developers the ability to create independent groupings that still support data injection and simplified Tide events.  Consider the MVC example shown in figure 7.  By grouping the instances of views, controllers, and data in separate subcontexts, we are making each MVC completely independent of each other.  When instance 1 of MyControl in the subcontext “MySubcontext1” injects the “myData” object, it is getting a new and completely separate instance of “myData” than the instance in “MySubcontext2”.  And when a component in the subcontext “MySubcontext1” dispatches a Tide event, it will never be routed to a component in MySubcontext2.  In a nutshell, components within a subcontext are completely independent of components within any other sibling subcontexts.

image

Figure 7 – Example of data and components in separate subcontexts

Data Injection in a Subcontext

    Data injection in a subcontext behaves identically to data injection in the Tide Context with one minor difference.  Components in a subcontext can inject data from the global Tide context, as well as data from the current subcontext.  Consider the code example in figure 8, and imagine it were instantiated in a subcontext called “MySubcontext1” as in figure 9.  When Tide tries to inject “myOtherData” it first checks the global Tide context to see if an entry for “myOtherData” already exists.  In this example it does exist in the global context, and Tide injects a reference to the object in the global Tide context. 

    Next when Tide scans the “myData” data injection, and “myData” does not exist in the global Tide context it will check to see if it exists in the current subcontext.  In this case Tide will either create a new instance of “myData” and add it into the “MySubcontext1” subcontext, or autowire it to the instance of “myData” in “MySubcontext1” subcontext if it already exists.

    In a nutshell, components in a subcontext can inject data from the parent global Tide context, or the current subcontext, but will never be autowired data injected in a sibling subcontext.

public class MyControl
{
    [In]
    public var myOtherData:SomeOtherData; //Injected from the global Tide context.

    [In]
    public var myData:MyData;  //Injected from the current subcontext.
}

Figure 8 – Control class that will inject data from the global Tide context and the current subcontext

 

image

Figure 9 – Visual representation showing control inject data from global Tide context and current subcontext

Tide Events in a Subcontext

    Tide events behave identically to Tide events in the global Tide context demonstrated in the previous tutorial, Intro to the Tide Client Framework, with one minor difference.  When a component dispatches a Tide event it will not only be relayed to any observers in the same subcontext, it will also be relayed to any observers in the global Tide context.  For example, if “mv1” in “MySubcontext1” from figure 9 were to dispatch a Tide event, it would be relayed to any observer within “MySubcontext1” as well as any listener in the global Tide context.

 

Hierarchies of Subcontexts

    One of the most powerful aspects of subcontexts is that they are hierarchical, enabling developers to create even more complex relationships between components.  Subcontexts are in a hierarchy if they share the same ‘.’ delimited subcontext name.  For example subcontext “A” is considered the parent of subcontext “A.B”, and subcontext “A.B” is a child of subcontext  “A”.  Subcontext “A.B.C” is a child of subcontexts “A.B” and “A”, and so on.

   

image

Figure 10 – Simple example of hierarchical subcontexts

Data Injection in Hierarchical Subcontexts

    Data injection in hierarchical subcontexts behave exactly like the previous sections if you consider the global Tide context as being at the top of the hierarchy.  Components in a subcontext can inject data defined in any parent subcontext (or the global Tide context).  For example, in figure 10, “SomeControl” in subcontext “MySubcontext1.Sub1” can inject the data “myData” defined in its parent subcontext – “MySubcontext1”.  “SomeControl” can also inject “myOtherData” from it’s grandparent, the Tide context.

    To make this a little clearer consider the class definition in figure 11, and assume this class were assigned to the “MySubcontext1.Sub1” subcontext in figure 10.  Although the code is injecting 3 data items, each data item is actually injected from a different source.

[Bindable]
public class SomeControl
{
    [In]
    public someData:SomeData; //Created or injected from the data within the current

    [In]
    public myData:MyData;  //Injected from the parent “MySubcontext1” subcontext.

    [In]
    public myOtherData:SomeOtherData  //Injected from the parent global Tide context.
}

Figure 11 – Code showing data injection from multiple contexts

    Take note that sharing data via injection will only work in one direction.  A component inside a subcontext can inject any data defined in one of its parents subcontexts, but cannot inject data defined in a child or sibling subcontext.  For example, in figure 10, “mv1” in subcontext “MySubcontext1” could not inject “someData” defined in child subcontext “MySubcontext1.Sub1”.

Tide Events in Hierarchical Subcontexts

    Tide events in hierarchical subcontexts behave exactly like the previous sections if you consider the global Tide context as being at the top of the hierarchy.  When a component inside a subcontext dispatches a Tide event it will be relayed to all observers in the current context, followed by all listeners in the parent context, and so on until it reaches the global Tide context.  For example, if a component in the subcontext “A.B.C” dispatches a Tide event, it will be relayed to all observers within the “A.B.C” context.  Tide will then relay the event to all observers registered with the “A.B” subcontext, followed by all observers in the “A” subcontext.

    Take note that Tide events only propagate in one direction, up the parent chain of subcontexts.  For example, looking at figure 10, the instance of “SomeControl” in the subcontext “MySubcontext1.Sub1” will NOT receive any Tide events from components in the subcontext “MySubcontext1” or the global Tide context.

 

Creating Subcontexts

    There are two ways to create subcontexts: declaratively and programmatically.

Declaratively Assigning a Component to a Subcontext

    Using the same declarative “[Name(myComponent)]” metadata pattern used to create named components in the Tide context, you can create named components in subcontexts.  By adding a ‘.’ separated prefix to the name, the component will be added to the prefixed subcontext.  In figure 12 we are creating a component “myComponent” in the subcontext “MySubcontext1”.  When the component is instantiated by Tide, it will instantiated inside a subcontext named “MySubcontext1”.  Note: the subcontext “MySubcontext1” will be created if a subcontext of that name does not already exist.

[Name("MySubcontext1.myComponent")]
[Bindable]
Class foo
{
   …
}

Figure 12 – Using “[Name]” metadata to declaratively assign a component to a subcontext

Programmatically Assigning a Component to a Subcontext

    Alternatively you can programmatically create subcontexts and add them to the global Tide context using whatever name use wish.  You can then add any components you want to that new subcontext as shown in figure 13. 

//Programmatically create the named subcontext, and add it to Tide 
var mySubcontext:Subcontext = new Subcontext();
tideContext[“MySubcontext1”] = mySubcontext;  //Add subcontext to global context

//Programmatically create a component, and add it to the subcontext
var foo:Foo = new Foo();
subcontext[“myComponent”] = foo;

Figure 13 – Programmatically creating and adding components to a subcontext

 
    While the programmatic approach isn’t quite as elegant as the declarative approach, it is probably going to be the more common approach.  The reason is hard-coding a class to a specific subcontext will result in the same problem with a single context noted in the section “Hard to have more than one instance of the same component”.  In a future post I’ll outline a thin framework built on top of Tide that allows developers to use declarative naming, but keep the flexibility possible in the programmatic approach.

Adding Components to a Subcontext

    Adding components to a subcontext is identical to adding components to the Tide context.  Please read the Intro to the Tide Client Framework for a more detailed explanation of these 3 methods.

  1. Observed events
  2. Injection/Outjection
  3. Setting as a property on the subcontext directly

 

A Simple Example

    Before I wrap up this article I want to show a concrete example implemented using both a single Tide context vs using multiple subcontexts.  Consider an application that allows you to compare 2 stocks side-by-side as shown in figure 14.  For each stock, the application shows a chart of the stock price over time, and a stock details view that displays more details about the stock.  Ignoring “Groups” and “Labels”, the application consists of 4 views: 2 chart views, and 2 stock detail views. 

image

Figure 14 – Simple Stock Application

Simple Stock Application Using Single Tide Context

    Lets take a look at how we might develop this application using the single global Tide context.  Since our goal is to make the application as simple and easy to read as possible, we are going to try to make use of data injection and simplified Tide events.

Step 1:  Get everything into single Tide context

    In order to get our code to work with data injection and simplified Tide events, the first step is to register our components with Tide.  Given that we are trying to add multiple instances of components to the tide context, however, we will not be able to use the declarative [Name(“stockChartView”)] or [Name(“stockDetailsView”)] above our class declarations.  The reason is that it is not possible to have 2 instances named “stockChartView”, and in this application we need 2 such instances.  Instead we will have to programmatically add these views to the context.  If you’ve read my last tutorial Intro to the Tide Client Framework you know there are several ways to add components to the Tide context.  For this example, I’m just going to use injection.

public Class MyStockApplication extends Application
{
    [In]
    public var chart1:StockChartView;

    [In]
    public var chart2:StockChartView;

    [In]
    public var scc1:StockChartControl;

    [In]
    public var scc2:StockChartControl;
    …
}

Figure 15 – Adding Views and Controls to the Single Tide Context

 

image

Figure 16 – Resulting Application after adding components to single Tide context

 

Step 2: Autowire views & controls to the data

    The next step in our application is to connect the views and controllers to the stock data to be displayed.  To make our code more clean and readable we’d like to use Tide to autowire our components to the data.  Oops, with a single context you can’t autowire the views and controls to the data.  If your classes were to use data injection to autowire the “Stock” data as shown in figure 17, each instance would end up injecting in the same stock object.  This might be useful if your application were meant to allow the user to compare the SAME stock side-by-side, but not very useful in an application designed to compare DIFFERENT stocks side-by-side.

public class StockChartView
{
    [In]                                                    //Can’t do this! Both instances of StockChartView
    public var stockDataToChart:Stock;  //would be injecting same stock instance.
}

Figure 17 – Cannot use injection to autowire views to data

    Looks like we cannot benefit from the Tide micro-architecture in this case and we will need to resort to normal actionscript to manually wire the views and controllers to the specific stock instance.

 

Step 3:  Autowire view events to controllers

    As the last step in our application, lets setup the controllers such that they can manipulate the views they control based on various interactions from the user.  Lets suppose that our chart view and stock detail views are related.  For example, when the user changes the timeframe on the stock chart, the stock details view actually updates to show the stock details from that same time period.  To implement that, we need our StockDetailsControl to be able to listen to “TimeframeChanged” events dispatched from the StockChartView.

    Unfortunately Tide simplified events won’t help us much here, because everything is in the same context and the “TimeframeChanged” Tide events will get routed to both StockDetailsController instances.  Each controller would need logic to check to see if the event is coming from the chart view that is related to the details view it is managing.  And what happens if we add another view that can affect the timeframe, the details controller would need to be updated again.  All in all, we’d need to make our controls very application aware to function properly, which is a bad practice and makes for very fragile code.

 

Simple Stock Application Using Hierarchical Subcontexts

    Now lets try to assemble the same application using hierarchical subcontexts.  Again, the goal is to make our code simpler by making use of data injection and simplified Tide events whenever possible.

Step 1: Organize everything into subcontexts

    First lets organize views and controls into separate subcontexts.  Given what I’ve shown so far in this tutorial you will need to use programmatic means shown in figure 17 to organize your components into subcontexts.  However, in a future article I will provide a simple framework that will allow you to use annotations, and the framework will automatically organize things into subcontexts.

public class StockApplication
{
    [In]
    tideContext:Context;

    …
    private function onInitialize()
    {
        //Create first subcontext
        private var sub1:Subcontext = new Subcontext(“stockView1”, tideContext);

       //Create StockChartView and add to subcontext 1.
        private var scv1:StockChartView = new StockChartView();
        sub1[“stockChartView”] = scv1;

        //… Add the rest of the views and controllers to subcontext 1.

        //Create second subcontext
        private var sub2:Subcontext = new Subcontext(“stockView2”, tideContext);

        //Create StockChartView and add to subcontext 2.
        private var scv2:StockChartView = new StockChartView();
        sub1[“stockChartView”] = scv2;

        //… Add the rest of the views and controllers to subcontext 2.
    }
}

Figure 18 – Programmatically organizing views and controllers into subcontexts

 

image

Figure 19 – Stock application with components divided into two subcontexts

 

Step 2: Autowire views, controls, and data

    Wohoo!!!  All my views and controllers can inject the stock data.  If each of the views and controllers were to inject the stock data, as shown in figure 20, the resulting application would like like figure 21.  And as you can see each view and controller will be looking at the correct data.

public class StockChartView
{
    [In] 
    public var stockDataToChart:Stock; 
}

Figure 20 – Components can use injection to autowire to stock data

 

image

Figure 21 – Resulting application state after injection and autowiring

 

Step 3: Autowire view events to controllers

    Since we have isolated everything into subcontexts that are specific to a subset of views, we are perfectly setup to use simplified Tide events.  In our example, the StockDetailsControl will only receive a TimelineChangedEvent when something within the same logical grouping dispatches that event.  And if more components are added to display information about this stock, my StockDetailsControl doesn’t need to be modified to watch their changes to the timeline. 

public class StockDetailsControl
{
    ….
    [Observer]
    public function onTimelineChanged(event:TimelineChangedEvent):void
    {
         //Do something

        //Will only get called when something within this subcontext dispatches a
        //TimelineChangedEvent.
     }
}

Figure 22 – Simplified Tide events work exactly like we want, making our code cleaner

 

Conclusion

     Tide subcontexts are a fantastic capability that ultimately complete the basics of a very clean and easy to use micro-architecture.  Subcontexts allow developers to divide their application into simple logical groupings or more complex hierarchical groupings.  By using these groupings, it is possible to write clean code that uses data injection and simplified Tide events even in fairly complex applications.

    Please post or shoot me an email with comments or questions.  In the near future I’ll be outlining a simple framework built on top of the Tide Client Framework that simplifies subcontexts, and lets you use declarative annotations even in dynamic hierarchical subcontexts. 

11 comments:

Suat Gönül said...

Thanks for the article, it simplifies the understanding of the GraniteDS framework

Hua Cai said...

ray-ban sunglasses
louis vuitton bags
michael kors outlet online
lululemon uk
louis vuitton outlet
swarovski outlet
louis vuitton outlet stores
michael kors factory outlet
burberry outlet
mulberry outlet
hermes outlet store
ray ban sunglasses
michael kors handbags
cheap oakley sunglasses
hermes outlet
air max 2015
fitflops outlet sale
louis vuitton handbags
ferragamo outlet
tiffany and co
nike tn pas cher
rolex uk
mulberry sale
replica watches
bottega veneta outlet online
cheap michael kors handbags
fitflops clearance
toms shoes
christian louboutin outlet
chaussure louboutin
celine outlet online
toms outlet
cheap jordan shoes
nike blazer pas cher
ferragamo outlet
20160528caihuali

xumeiqing said...

20160620meiqing
omega seamaster
cheap jordan shoes
tiffany and co
gucci outlet
ralph lauren outlet
rolex submariner
true religion
michael kors outlet
michael kors outlet clearance
fitflops
nike store
longchamp handbag
nike air max
converse uk
louis vuitton outlet
polo outlet
jimmy choo outlet
adidas supercolor
ed hardy uk
oakley sunglasses
nike air max
instyler max
jordan shoes
nike air force white
ray ban outlet
louis vuitton outlet
kobe shoes
toms
michael kors outlet
prada outlet store
adidas stan smith
ralph lauren outlet
fitflop uk
true religion outlet
adidas trainers
cheap nba jerseys
nfl jerseys wholesale
louis vuitton factory outlet

xjd7410@gmail.com said...

kate spade
nike air max 90
louboutin shoes
tod's shoes
louis vuitton handbags
true religion jeans
retro jordans 13
oakley sunglasses
coach outlet store online
gucci handbags
cheap oakley sunglasses
nike roshe run women
gucci outlet online
burberry bags
michael kors outlet
marc jacobs handbags
louis vuitton outlet
coach factory outlet online
nike free run
coach outlet store online clearances
coach factory outlet
kate spade handbags
oakley outlet
oakley sunglasses
coach outlet store online
michael kors outlet
oakley sunglasses
adidas nmd
adidas yeezy
louis vuitton outlet
chenyingying712

dong dong23 said...

kate spade
louis vuitton outlet
ralph lauren polo
ugg boots uk
louis vuitton outlet
miami heat jerseys
michael kors handbags
louis vuitton outlet
nike tn pas cher
michael kors outlet clearance
201610.6wengdongdong

raybanoutlet001 said...

michael kors uk
kobe 9 elite
pandora jewelry
louis vuitton sacs
michael kors handbags outlet
cheap michael kors handbags
true religion jeans sale
michael kors handbags outlet
michael kors handbags
birkenstocks

raybanoutlet001 said...

reebok outlet
michael kors handbags wholesale
miami heat
versace
san diego chargers jerseys
indianapolis colts jerseys
armani exchange
ralph lauren
michael kors outlet
polo ralph lauren outlet

dada24 Xu said...

kate spade outlet
michael kors outlet canada
timberland shoes
abercrombie
christian louboutin shoes
chaussure louboutin
cheap jordan shoes
true religion jeans outlet
polo ralph lauren outlet online
nike air huarache
zhi20161209

dong dong23 said...

gucci outlet
louis vuitton handbags
prada outlet
pandora uk
ugg outlet
timberland boots
ralph lauren outlet
celine outlet
nike air max pas cher
ralph lauren
201612.27wengdongdong

Meiqing Xu said...

birkenstocks
levis outlet online
louis vuitton handbags
coach factory outlet
true religion
fitflops
red bottoms shoes
christian louboutin shoes
fake rolex
toms outlet
20170209CAIYAN

LCc 03 said...

longchamps
adidas stan smith shoes
hogan outlet online
yeezy shoes
harden shoes
yeezy shoes
links of london
hermes belt
nike roshe run
kobe basketball shoes