Abstract
DaST pattern and ASP.NET DaST Rendering Engine is a further development of the ASP.NET
Scopes Framework and its underlying server page technology based on fully templated
data scope trees which I proposed in my
first article in November 2010. From now on the pattern will be called DaST
which stands for Data Scope Trees. After working with DaST for a while, my opinion
is that this approach opens unmatched web development opportunities and has all
chances to completely replace standard ASP.NET Forms and MVC patterns in the near
future.
This article consists of 4 parts. First of all, I'll present a new VideoLibrary
DEMO application which demonstrates full power of DaST pattern on a real web site.
This application will serve as a good how-to sample and a DaST technology demonstrator
for all further releases of ASP.NET DaST framework. In the second part I'll give
a brief overview of the DaST concept itself and will decompose a VideoLibrary page
to a scope tree, so that those who see this technology for the first time could
get an idea about the new DaST approach and its advantages. In the third section I'll
take a closer look at the demo application back-end architecture and implementation
and will highlight all important syntax and design changes that took place since
1st Alpha release in November. This section is targeted only for those, who read
my previous article.
And in the final section I'll share some framework development plans for the near
future.
One more thing :) As many of you criticized my previous article for its length,
I'll keep this one short, lean and mean. But if you come across this framework for
the first time and you wish to try it in your projects, current article will not
be enough and you'll have to read my more detailed and deep
Scopes Framework article from November.
Currently, all DaST releated news and events are posted on the
DaST development site. Check this site
for framework updates and important changes. The latest version of ASP.NET DaST
Rendering Engine as well as the VideoLibrary DEMO source code are located in
the download section of the site.
!!!UPDATE (May 11, 2011): ASP.NET DaST Rendering Engine project has officially become open-source starting from May 9th!
We're looking for open-source developers, so if you feel you can contribute the project, you're welcome to contact the DaST team. DaST
open-source information site is here. DaST project console home on SourceForge is here. Public DaST discussion forums are here (anynimous
access is enabled)
Contents
VideoLibrary DEMO - Technology Demonstrator
To demonstrate advantages of DaST web development pattern over the standard Forms and MVC approaches,
I created a sample application, VideoLibrary, which will serve us as an example, tutorial, and technology
demonstrator in the same time. VideoLibrary DEMO is a prototype of a video portal built on ASP.NET DaST
framework. Having complex and dynamic UI, this sample application includes most of typical functional elements
used to build rich Web 2.0 sites and demonstrates how these functionalities could be achieved using DaST
Rendering Engine. Here are some nice and very popular Web 2.0 features that are implemented in VideoLibrary:
- Numerous partially updated areas with random and complex nesting structure.
- Delayed loading with partial update to display content that requires data access.
- Details dialog box with delayed loading of its body content.
- Numerous jQuery client widgets + jQuery theming
The following are some application screenshots (Fig. 1 and Fig. 2) and brief description
of how the application operates on a user level. Although the application does not
do much meaningful things by itself and serves mostly as demonstrator, it can be
used as a prototype for a similar applications in a real world. Note that Fig. 1
and Fig. 2 show the application in 2 different jQuery UI themes.
Fig. 1: VideoLibrary DEMO initial look
The application displays paged selection of a number of video packshots (see Fig. 1).
Of course, there are no real videos -- we just use black rectangle with colored
text on it. Color of that text defines the category of the video: red category,
green category etc. Each packshot has "+" button in the top-right corner. Clicking
on it, the video is added to the playlist on the left side and button turns to "-".
Clicking "-" removes video from the playlist. On mouse hover, the packshot displays
"i" button which invokes the details dialog window.
Fig. 2: VideoLibrary DEMO details dialog
The dialog window (see Fig. 2) displays some more details and paged list of video
packshots in the same category (i.e. with the same color). Packshots in this list
work the same way as in the playlist and the selection grid. The dialog window is
tabbed. You can switch to another tab to add video reviews and ratings. Note that
all changes you make are saved within your current session only. You can also play
with application look-and-feel by selecting your favorite jQuery UI theme in the
top-left corner.
All application data comes from data layer simulated by XML file and LINQ. Since
this application is a DEMO, I've made it so that all changes you make (add reviews,
ratings, playlist, etc) are persisted within the current session only and are
discarded when the session ends. On Fig. 3 you can see a part of XML file used as
a data source. It's structure is trivial and you can get an idea how VideoLibrary
data is organized.
Fig. 3: Back-end data XML
Although, beautiful and attractive UI elements is an important part of the application,
all the advantages of DaST web development pattern become obvious only when we look
at the application back-end code. Being clear, transparent and uniform, the DaST
architecture of the VideoLibrary back-end results in unmatched front-end flexibility
and allows almost unlimited complexity of the web application UI.
If you wish to run the live test of VideoLibrary DEMO application immediately, you
can do this on the DaST Development Site
where I'll always deploy the latest version of this application. The entire VideoLibrary
DEMO source code is available for download as attachment to this article. I suggest
that you get the source code, browse and get familiar with it, as for the rest
of this article I'll use this sample code for all my explanations.
DaST Concept Overview
Do you think it is possible to create an ultimate server page rendering engine that
outperforms standard Forms and MVC in easiness, architecture, flexibility, performance,
presentation separation, and all other important web development attributes? As
the answer to this question I'd like to present the ASP.NET DaST Rendering Engine
based on a new DaST web development pattern. The name of the pattern stands for
Data Scope Trees and you'll understand what this means later. Even if you're an
experienced web application programmer or architect, and not familiar with this
pattern yet, I promise that the rest of this article will change your idea about
the server-page based web application design and development.
The entire DaST concept departs from a very simple thought that every web site in the
world is nothing, but a bunch of data produced by server-side logic and presented
to the user in the client browser. All you see on a web site is just some data wrapped
into HTML text! This means that the entire page rendering process can be viewed
as two separate steps: 1) generation of some data values and 2) presentation of
these data values in the form of HTML to the client.
To accomplish data generation on the first step we need some kind of codebehind
class. Speaking abstractly, the output of such class would be a set of string values.
In DaST framework this class is called a controller (like in MVC). The second rendering
step is more interesting. After all string values are generated by the controller,
we need to mix them with some markup and give the HTML output to the client. Sounds
simple, but how to accomplish this technically? Every experienced developer knows
how standard Forms or MVC platforms render their pages by going though the complicated
and resource consuming page lifecycle, rendering multiple individual server controls,
applying master pages, calling data binding implementations for repeating controls,
processing events, etc. etc. This process is quite complex and we will try to approach
the rendering task from another side. In order to render our prepared data into
HTML, we will just take a complete and valid HTML template and insert the prepared
string values into this template at the right spots. The spots where data values
get inserted are marked with special placeholders which are replaced on rendering
stage by real data values. And this is it! We just need a pure HTML template and
nothing else -- no server controls, no page lifecycle, no data binding -- we just
dumped all that complexity at once!
Look at our demo application on Fig. 1. Now imagine if we had a list of prepared
data values (like all those names, descriptions, page numbers, etc) and a complete
HTML template with placeholders at the locations where values are to be inserted.
What would our rendering process be? Well, the entire rendering would be reduced
to a trivial search-and-replace operation for substituting placeholders by the real
values. And this is basically all the DaST does to render your pages!
Data Scope Trees
Of course, I abstract away some technical details by just saying that a bunch of
data gets applied to the template. In reality, this process has to be more granular,
because we also need some way to manage our data values so that they get applied
at the right location only. Having placeholders is not enough, because we simply
don't want to produce all data values at once and, moreover, we may not be able to.
It's much better to, first, produce values for one area of the page, then for another,
and so on. Plus we need some way to provide data for repeated content that can be
nested in another repeater at any level. And what about Ajax and partial page updates?
Thinking about all these requirements I finally came to the idea of the data scopes
and data scope trees which became the core of the DaST pattern (hence, the pattern
name) and the framework based on it.
Data scope is simply a group of cohesive data values. Cohesion criteria is chosen
by the developer. Data scope can be applied to the specific area of an HTML template,
meaning that placeholders in that area are replaced by the corresponding data values
from data scope. As a result, we get the final HTML output for this specific piece
of the template. Extending this technique for the entire page, we can have multiple
data scopes applied to all of the areas of HTML template to render the
template
completely and output the page to the client. Data scopes can also be nested to
form hierarchical structure called data scope tree. And finally, I assert the
every
page of any complexity can be viewed as an HTML template with a data scope tree
applied to it. Voila! :)
So, first thing that a web developer must accomplish is to decompose the application
UI to a set of randomly nested data scopes. From my own experience, choosing the
right data scope nesting structure requires some DaST specific skills that come
after several exercises. Let's do this for a part of our demo application UI shown
on Fig. 1. The resulted data scopes are shown on Fig. 4.
Fig. 4: VideoLibrary partial UI decomposition into data scopes
First of all, there is always a root scope (NULL scope) that serve as a parent of
all other scopes. Then we have PageSizeRepeater and SiteThemeRepeater to output
items for page size and for jQuery theme drop-down lists (see Fig. 1). Next we have
several other data scopes on the left side to form playlist UI. PlaylistHeader scope
shows current pager info. PlaylistPager scope wraps a playlist item pager.
PlaylistRepeater
is needed to display multiple video packshots represented by a nested VideoItem scope.
Each VideoItem scope has 2 more nested scopes: AddButton and RemoveButton. The idea
is to show only AddButton scope when item is not in the playlist, and only
RemoveButton
scope otherwise. Next, on the right side of the page we have a video item selection
grid. Structure for this grid is absolutely the same as for playlist except that
data scopes on the same level have different names. Now, if we depict our nested
data scopes on Fig. 4 as tree, we will get a data scope tree identifying our page.
A complete data scope tree for the VideoLibrary application is shown on Fig. 5.
Upper part of the tree corresponds to UI on Fig. 1 which we have just decomposed.
The bottom subtree coming from DetailsPopup scope corresponds to UI on Fig. 2.
Data scopes with attached controllers have red
backgrounds and this will be explained later.
Fig. 5: VideoLibrary complete data scope tree
Data Scopes in Template
Ok, after we logically decomposed the UI into data scopes, next question
is how to point the specific data scope at the specific area in the template.
The solution here is straightforward. Since our data scope tree is built
so that it represents the logical structure of the document, we just use the same
structure in the HTML template by wrapping corresponding areas into valid
DIV container elements representing boundaries within which data scopes are
are applied. This means, that these containers in the HTML template have
the same hierarchical structure defined by the data scope tree. Each data
scopeis identified by its name e.g. "PlaylistPager" or "VideoItem". The same
name should be specified in the scope
attribute of the corresponding
HTML container element. So, the system does not care how complex your template
is is and what type of markup it has -- it only cares about nested scope DIVs i.e.
the data scope tree inside this template. For example, if we wanted to create
a valid template for data scope tree on Fig. 5, a part of this template could look
like the markup on Listing 6.
Listing 6: Possible part of valid template
your markup here ...
<div scope="PageSizeRepeater">your markup here ...</div>
<div scope="SiteThemeRepeater">your markup here ...</div>
<div scope="PlaylistHeader">your markup here ...</div>
<div scope="PlaylistPager"></div>
<div scope="PlaylistRepeater">
your markup here ...
<div scope="VideoItem"></div>
your markup here ...
</div>
rest of template follows ...
NOTE that using only DIVs for scope containers is a limitation of the current version
of DaST Rendering Engine and will go away in one of the next versions of the framework.
Controller Overview and Data Binding
To generate real data for template the developer has to implement a controller class.
The process of generating data for the corresponding template I call data binding.
For every data scope in the tree and the corresponding scope DIV in the
template
the controller contains a data binding function called binding handler. The output
of a binding handler is basically a set of values for a current data scope.
In the simplest case, there is a single controller responsible for generating
data for all data scopes in the scope tree. In the normal case, there are
multiple controllers and each one of them is responsible only for a part of a
scope tree. When a controller is attached to a data scope, it becomes
responsible for generating data for this scope and all child scopes in the
subtree unless those child scopes have their own controllers attached.
Look at Fig. 5 again -- data scopes on the red background are the ones that have
controllers attached. There is always at least one controller attached to a NULL
scope called a root controller. Other controllers are child controllers. Just
like user controls in Forms or partial views in MVC, child controllers in DaST
should be used whenever it is appropriate to factor out a certain common piece
functionality. One of the examples in our demo application is
PagerController
attached to PlaylistPager, VideoItemPager, AlikeVideosPager, and
CommentsPager
data scopes (see Fig. 5).
Rendering Process
Now I will redefine the DaST rendering process in terms of controllers and
binding
handlers. To render the entire page, the framework starts from generating data for
the NULL data scope in the tree by invoking the corresponding binding handler in
the root controller and recursively repeats this operation for all data scopes in
the tree invoking appropriate handlers on responsible controllers.
There is always
a specific and consistent order in which data scopes are processed. In terms of
tree walking algorithms, the framework uses post-order walk with top-to-bottom in-order
traversal. Recalling graphs from high school, it's actually called right-to-left
in order traversal, but because it is more appropriate to depict scope tree horizontally
(like I did on Fig. 5), I call it top-to-bottom. Such traversal order is chosen,
because the actual scope DIV tags in HTML template are encountered exactly in this
order.
So, all the developer needs to do to render a page using DaST Rendering Engine is
to 1) create an HTML template similar to the one on Listing 6 and then 2) implement
a controller for it (or multiple controllers for multiple
templates). Listing 7 shows a high-level outline
for the root controller (VideoLibraryController.cs) of our demo application. This
root controller is the one attached to the NULL scope on Fig. 5.
Binding handlers
producing values for corresponding scopes are on lines 87-193. Names of these handler
functions are chosen by the developer. On line 10 you see the
SetTemplate()
function
- that's how we tell the controller which template to use.
SetModel()
function
on line 15 is needed to assign binding handlers or child controllers to
data scopes
in the tree. Finally, lines 45-73 are action handlers invoked in response to user
actions (think of these as a counterpart of events).
Listing 7: VideoLibrary root controller outline
8 public class VideoLibraryController : ScopeController
9 {
10 public override void SetTemplate(SetTemplateArgs template) { ... }
14
15 public override void SetupModel(ControllerModelBuilder model) { ... }
43
44
45 private void Action_PageSizeChanged(ActionArgs args) { ... }
58
59 private void PlaylistPager_PageChanged(ActionArgs args) { ... }
65
66 private void VideoItemPager_PageChanged(ActionArgs args) { ... }
72
73 private void VideoItem_PlaylistUpdated(ActionArgs args) { ... }
85
86
87 private void ROOT_DataBind() { ... }
104
105 private void PageSizeRepeater_DataBind() { ... }
118
119 private void SiteThemeRepeater_DataBind() { ... }
130
131 private void PlaylistHeader_DataBind() { ... }
165
166 private void PlaylistRepeater_DataBind() { ... }
181
182 private void VideoItemGridInfo_DataBind() { ... }
192
193 private void VideoItemRepeater_DataBind() { ... }
208 }
Actions and AJAX
A huge advantage of data scope trees is that they allow for the most clear
definition of partial page updates and the simplest usage of AJAX capabilities
in web applications comparing to all other existing frameworks. The underlying
idea of DaST Ajax is as simple as everything else in DaST -- every scope in data
scope tree can be individually refreshed at any time during the async postback!
When data scope is refreshed, all its child data scopes in the scope tree are
refreshed too. In terms of data binding this means that the system re-invokes
binding handlers corresponding to the refreshed scopes to regenerate data
values, update parts of the template, and output them back to the client.
Data
scope tree traversal order is kept on the postback, maintaining the consistent
order of binding handler invocation.
Next, async postbacks do not occur randomly, but in response to some events in the
browser i.g. button clicks, timer ticks, etc. DaST Rendering Engine provides a simple
and lightweight mechanism allowing to raise events on the client side, and handle
those events on the postback inside the controller classes. In DaST a counterpart
of event is called action. Each action has a name and a string argument. This is
not a limitation, because string argument can contain just any possible JSON-serializable
object. When action occurs, async postback comes to the controller and appropriate
action handler is executed (see lines 45-73 on Listing 7). Action handler is the place
where we can do some processing and instruct the specific data scopes to refresh.
DaST Pattern Résumé
OK, at this point you should have a clear top-level picture of what the DaST
pattern and DaST Rendering Engine actually are and how they differ from other
patterns and frameworks available to the developers today.
Below I will summarize the most obvious benefits of using DaST pattern in your
web applications.
- Maximum possible level of separation between presentation and back-end code.
- As the result of the above, outstanding application back-end testability and easiness
of TDD.
- Usage of W3C compliant pure HTML templates to control every single
byte of the UI markup.
- As the result of the above, unmatched UI flexibility, maintainability, and simplicity
in the same time. Instead of delving into markup of server controls, you work with
trivial HTML!
- Ability to change your page layout and look-and-feel entirely in 10 minutes by
playing with HTML markup in the template. Obviously, this DOES NOT require
any site recompilation!
- Minimum framework learning period. No need to read 400 page books to start creating
highly dynamic and complex sites -- all you need to know is how to decompose your
UI into i>scope trees and how to implement action and binding handlers
inside the controller classes.
- All back-end design becomes simple, clear, and uniform. No matter how complex
your application UI is, no matter how big your data scope tree is, in the
back-end it will always be the same -- just a controller with a list of
action and binding handlers!
- No page lifecycle. No server controls. No more wondering how to make the control
render this way or that way -- you HTML template and binding handlers
control your rendering process entirely from the beginning to the end.
- Native support of AJAX partial update without single line of code or additional
markup. Just compare to update panels in Forms :)))
- Binding handlers are re-invoked on the postback ONLY for the refreshed
part of the page meaning that your code is executed ONLY when it needs to! Again,
compare to Forms, where a page goes through the entire lifecycle executing tons
of unnecessary code.
- DaST does not deny any of standard Forms features, but rather compliments them.
Our favorite session management, application cache, authentication classes, etc.
are still available within action and binding handlers inside the
controllers. Standard Forms based pages can coexist with DaST based pages
is one application allowing smooth step-by-step transition from Forms to DaST.
- Summarizing all this, and based on my own experience, application development
time decreases by up to 90% (not a joke).
Main purpose of the current article is to give an overview of important changes
in the new version of the framework. If you see this concept for the first time
and want to learn in details how to create HTML templates and implement
controllers, you'll have to read
this article which is much more deep and detailed. Or you can wait until documentation
and tutorial becomes available on DaST dev site at
http://www.rgubarenko.net, but I can't promise any dates.
Important Framework Changes. VideoLibrary Back-End.
In this section I'll quickly go through all the important syntax and design changes
since the previous version of the framework. I'll not delve into much back-end details,
because the entire mechanism has already been described in
my previous article in November, I'll only highlight the differences for
those who read that article and who is already familiar with the Scopes Framework.
So, the following sub-sections is just a list of differences between 2 versions.
1) Binary and namespace
Changes happened to the framework binary and namespace are the following:
- Name of Name of the framework DLL is changed to AspNetDaST.dll (located in demo site Bin folder).
- All public classes used by the developers are located in
AspNetDaST.Web
namespace.
2) ScopesManagerControl is gone
In Scopes Framework I used a temporary solution that we had to add a
ScopesManagerControl
to an ASPX page to make it work with the framework. Now this is gone. In the DaST
Rendering Engine we inherit pages from DaSTPage
class from
AspNetDaST.Web
namespace
and implement only one ProvideController()
method. You dont have to add any markup
to the ASPX page -- DaSTPage
parent class takes care of it. Listing 8 shows the listing
of VideoLibrary.aspx.cs codebehind class. Everything should be clear: we instanciate
the root controller on line 14 and enable partial updates on line 15. Note that currently,
EnablePartialUpdates
must always be set to
TRUE
. This is due to the fact that I
did not decide yet if we need to support full postback or not. In Forms the reason
to make async postbacks optional was that UpdatePanel controls add significant complexity
and performance overhead. In DaST, partial refreshes are native and I don't see any
reason to make them optional.
Listing 8: VideoLibrary.aspx.cs listing
8 using AspNetDaST.Web;
9
10 public partial class VideoLibrary : DaSTPage
11 {
12 protected override void ProvideController(PageSetup setup)
13 {
14 setup.RootController = new VideoLibraryController();
15 setup.EnablePartialUpdates = true;
16 }
17 }
3) Data binding changes
As you may notice on Listing 7, arguments in binding handlers are eliminated. Handler
argument was used to add placeholder replacements for the current scope. From now
on, placeholder replacements are added directly to the scope instance which you
can access using CurrentPath
and
ControlPath
tree navigators. The meaning of these
rendered scope tree navigators has also slightly changed:
ControlPath
used inside action or binding handler always points the
scope to which
current controller is attached.
CurrentPath
used inside binding handler points to the currently bound
scope. CurrentPath
used inside action handler points to the scope specified in client-side JavaScript
Action(..)
call as target scope.
Just look at Listing 9 that shows one of binding handlers from DetailsPopupController.cs
class. On lines 202-203 instead of calling Replace(..)
on the binder object passed
as a parameter like it was before, we call Replace(..)
directly on the
scope instance,
using CurrentPath
that points to the current ReviewsInfo scope.
Listing 9: Some of binding handlers in DetailsPopupController controller.
195 private void ReviewsInfo_DataBind()
196 {
197 decimal rating = CurrentPath.Scope.Param<decimal>("Rating");
198 int commCount = CurrentPath.Scope.Param<int>("CommCount");
199
200 CurrentPath.Scope.ShowConditionArea("comm-empty:yes", commCount <= 0);
201 CurrentPath.Scope.ShowConditionArea("comm-empty:no", commCount > 0);
202 CurrentPath.Scope.Replace("{Rating}", rating.ToString("0.0"));
203 CurrentPath.Scope.Replace("{CommCount}", commCount);
204
205 CurrentPath.Rew(1).Fwd("ReviewsInfo2").Scope.ShowConditionArea("comm-empty:yes", commCount <= 0);
206 CurrentPath.Rew(1).Fwd("ReviewsInfo2").Scope.ShowConditionArea("comm-empty:no", commCount > 0);
207 CurrentPath.Rew(1).Fwd("ReviewsInfo2").Scope.Replace("{Rating}", rating.ToString("0.0"));
208 CurrentPath.Rew(1).Fwd("ReviewsInfo2").Scope.Replace("{CommCount}", commCount);
209 }
At the first look it may seem that data binding syntax became more complex, but
actually this design gives us a huge programming benefit, because now we are able
to databind scopes from inside the binding handlers of other scopes. This is great,
because if a scope is simple and contains just a couple of values, why would I flood
my controller class creating a separate binding handler for it? This new feature
is used on lines 207-208 of DetailsPopupController
class (see Listing 9) where we use
navigator to point at another ReviewsInfo2 scope and add same placeholder
replacements to it.
4) Working with scope instance
In the new version of DaST Rendering Engine all framework calls became more uniform.
Now all methods and properties related to manipulation with the specific scope are
accessible through the scope instance object. Data binding functions (some of them
I already talked about in the previous section) is one group of such methods. Another
group of methods provide operations on scope context parameters. Diagram on Fig. 10
shows public interface of RenderedScopeInstance
class.
Fig. 10: Public interface of RenderedScopeInstance class.
Below is a brief explanation of all class members. Most of them should be well
familiar to you from Scopes Framework. Even though the names are changed, the
meaning is still the same. Members marked "(NEW)" are the ones that were added in the
current version of the framework.
- RenderTyRenderType (NEW) - gets or sets the type of scope rendering. Explained
later.
- ScopeClientID - id of the scope container as you see it on a rendered page.
- HasParam - indicates if parameter with specified name is already set.
- InitParam (NEW) - sets parameter if only it's not set yet.
- Method (NEW) - calls method in scope context. Explained later.
- Param (NParam (NEW) - gets strongly typed parameter.
- Refresh - causes partial update on the target scope.
- Repeat - repeats content of the current scope.
- Replace, ReplaceRange - adds placeholder replacements.
- RestartRepeater (NEW) - resets content repeating to 0. Explained later.
- SetParamSetParam, SetParams - set single or range of parameters for target
scope. Parameter can be any object serializable to JSON.
- ShowConditionArea (NEW) - beatiful feature allowing direct manipulation of the
scope markup. Explained later.
5) Scope content repeating
In DaST framework content of each scope is output one time by default meaning that
the resulting HTML fragment is just whatever this scope container has in the
template.
In order to repeat scope content, we should call Repeat(..)
function on the target
scope. We also have a RestartRepeater(..)
method (see Fig. 10) that resets repeating
count to 0. If this method is called and is not followed by calls to
Repeat(..)
,
the scope
will not output any content. Listing 11 shows how
PlaylistRepeater scope
outputs its list of video packshots. First, on lines 168-170 we retrieve pager values
from PlaylistPager scope to which PagerController
is attached. Then we get list
of video items from simulated data layer. Then, on line 173, we have a call to
RestartRepeater(..)
and, finally, on lines 174-179 we simply loop through the list of objects calling
Repeat(..)
on each run and passing the video item object to
VideoItem scope with
VideoItemController
attached to it.
Listing 11: PlaylistRepeater scope binding in VideoLibraryController class.
166 private void PlaylistRepeater_DataBind()
167 {
168 int startItemIdx = CurrentPath.Rew(1).Fwd("PlaylistPager").Scope.Param<int>("StartItemIdx");
169 int pageSize = CurrentPath.Rew(1).Fwd("PlaylistPager").Scope.Param<int>("PageSize");
170 int itemTotalCount = CurrentPath.Rew(1).Fwd("PlaylistPager").Scope.Param<int>("ItemTotalCount");
171
172 object[] videoItems = DataLayer.GetPlayItems(startItemIdx, pageSize);
173 CurrentPath.Scope.RestartRepeater();
174 for (int i = 0; i < videoItems.Length; i++)
175 {
176 CurrentPath.Scope.Repeat();
177 CurrentPath.Fwd(i, "VideoItem").Scope.SetParam("ItemIndex", startItemIdx + i);
178 CurrentPath.Fwd(i, "VideoItem").Scope.SetParam("VideoItemObject", videoItems[i]);
179 }
180 }
One more new feature in this version of the framework is that we can save generic
objects as scope parameters. On line 178 in listing on Listing 11 we set the entire
video item object as a parameter for the VideoItem scope. The system accepts any
object as scope parameter as long as this object is serializable to JSON i.e. can
be saved to a simple string.
6) Scope visibility
In the previous version of the framework a data scope could be made
invisible meaning that output of this scope was an empty string. In the current
version each data scope has more visibility options and this is set using
RenderType
property of the scope instance (see Fig. 10). This
property is of enumeration type and its possible values are summarized below:
Normal
- scope is rendered normally.
Empty
- scope is rendered as a container only i.e. the output of such
scope is
an empty DIV container. Binding handlers for this scope and all scopes in a subtree
are NOT called. Parameters for this scope ARE persisted. Parameters for
scopes in a subtree
are NOT persisted.
None
- same as Empty, except that it is rendered as an empty string and its
parameters are NOT persisted.
Note that when scope is refreshed, its RenderType
is automaticaly reset to
Normal
.
Also, it's important to understand that if visibility is set to
None
, you will not
be able to refresh this scope, simply because there is no HTML container that would
be able to accept the new content. Finally, you have to plan which scopes in the
scope tree should be used to save parameters, because when you set scope visiblity
to Empty or None, all its child scope parameters are discarded.
Let's look at the example how delayed loading effect is made on details dialog.
Listing 12 shows the most important back-end functions participating in the delayed
loading. On the client side, when you click "i" button on any video packshot, jQuery
Dialog plugin is used to popup the dialog box. Initially dialog box has only
animated loader
icon and no other content, because in ROOT_DataBind()
method we set
DetailsContent
scope render type to Empty
. This means that none of
binding handlers for child scopes
of DetailsContent scope are invoked on the initial load. When dialog opens, "LoadContent"
action is raised and handled on line 51. Inside this action handler we refresh the
DetailsContent scope and set its "VideoID" param to the id passed as action argument
from client side. Next, since DetailsContent scope is refreshed, the system re-invokes
its binding handler as well as binding handlers of all its child scopes
in traversal
order. So, DetailsContent_DataBind()
on line 170 gets invoked. "VideoID" populated
in action handler is retrieved and used inside binding handler to retrieve actual
video item and use its values to add placeholder replacements. So, after this
binding
handler is executed, the system carries updated ouput to the client and DetailsContent
scope is updated with real video item info i.e. our delayed loading works exaclty
as it should. Notice lines 174 and 175 where we hide CommentsContent and
AddCommentScreen
to apply the same delayed loading technique to them, but this time within the open
dialog box.
Listing 12: Part of DetailsPopupController class.
51 private void Action_LoadContent(ActionArgs args)
52 {
53 string videoID = (string)args.ActionData;
54
55 ControlPath.Fwd("DetailsContent").Scope.Refresh();
56 ControlPath.Fwd("DetailsContent").Scope.SetParam("VideoID", videoID);
57 }
...
160 private void ROOT_DataBind()
161 {
162 ControlPath.Fwd("DetailsContent").Scope.RenderType = ScopeRenderType.Empty;
163
164 CurrentPath.Scope.Replace("{DetailsContent_ScopeID}", CurrentPath.Fwd("DetailsContent").Scope.ScopeClientID);
165 CurrentPath.Scope.Replace("{CommentsContent_ScopeID}", CurrentPath.Fwd("DetailsContent", "CommentsContent").Scope.ScopeClientID);
166 CurrentPath.Scope.Replace("{AddCommentScreen_ScopeID}", CurrentPath.Fwd("DetailsContent", "AddCommentScreen").Scope.ScopeClientID);
167 CurrentPath.Scope.Replace("{ErrorDisplay_ScopeID}", CurrentPath.Fwd("DetailsContent", "AddCommentScreen", "ErrorDisplay").Scope.ScopeClientID);
168 }
169
170 private void DetailsContent_DataBind()
171 {
172 string videoID = CurrentPath.Scope.Param<string>("VideoID");
173
174 CurrentPath.Fwd("CommentsContent").Scope.RenderType = ScopeRenderType.Empty;
175 CurrentPath.Fwd("AddCommentScreen").Scope.RenderType = ScopeRenderType.Empty;
7) Method handlers
Sometimes ability to set scope parameters is not enough and we wish to call an actual
method on the controller to execute certain activities. This feature was added to
a new version of the framework. Listing 13 shows how UpdatePagerValues(..)
method is
registered for PagerController
class. In the future I may come up with something
more intelligent, but in the current version, a valid method has to take single
object parameter and return object result. On line 110 we have the
UpdatePagerValues(..)
private method. It's not a problem that there is only one parameter allowed, because
I can always pass a JSON object graph and get separate values from it. On line 25 the
method handler is registered so that it becomes callable from other controllers.
Listing 13: Registering method in PagerController class.
18 public override void SetupModel(ControllerModelBuilder model)
19 {
20 model.SetDataBind(new DataBindHandler(ROOT_DataBind));
21
22 model.HandleAction("NextPage", new ActionHandler(Action_NextPage));
23 model.HandleAction("PrevPage", new ActionHandler(Action_PrevPage));
24
25 model.RegisterMethod("UpdatePagerValues", new MethodHandler(UpdatePagerValues));
26 }
...
110 private object UpdatePagerValues(object jsonData)
111 {
112 int startItemIdx, pageSize, itemTotalCount;
113 if (DaSTUtils.HasValue("StartItemIdx", jsonData)) startItemIdx = (int)DaSTUtils.GetValue("StartItemIdx", jsonData);
114 else startItemIdx = ControlPath.Scope.Param<int>("StartItemIdx", 0);
115 if (DaSTUtils.HasValue("PageSize", jsonData)) pageSize = (int)DaSTUtils.GetValue("PageSize", jsonData);
116 else pageSize = ControlPath.Scope.Param<int>("PageSize", 1);
117 if (DaSTUtils.HasValue("ItemTotalCount", jsonData)) itemTotalCount = (int)DaSTUtils.GetValue("ItemTotalCount", jsonData);
118 else itemTotalCount = ControlPath.Scope.Param<int>("ItemTotalCount", 0);
119
120 startItemIdx = Math.Min(startItemIdx, itemTotalCount - 1);
121 startItemIdx = ((int)(startItemIdx / pageSize)) * pageSize;
122
123 124 ControlPath.Scope.SetParam("StartItemIdx", startItemIdx);
125 ControlPath.Scope.SetParam("PageSize", pageSize);
126 ControlPath.Scope.SetParam("ItemTotalCount", itemTotalCount);
127
128 return null;
129 }
Next, Listing 14 shows how the method is invoked from an action handler of
VideoLibraryController
.
On line 77 we, first, point to the PlaylistPager scope which has a
PagerController
attached and call Method(..)
function (see Fig. 10) to invoke previously registered
method by name. Notice how I pass the generic object as method parameter.
Listing 14: Invoking PagerController method from inside of VideoLibraryController.
73 private void VideoItem_PlaylistUpdated(ActionArgs args)
74 {
75 76 int itemTotalCount = DataLayer.GetPlayItemCount();
77 ControlPath.Fwd("PlaylistPager").Scope.Method(
78 "UpdatePagerValues", new { ItemTotalCount = itemTotalCount });
79
80 ControlPath.Fwd("PlaylistHeader").Scope.Refresh();
81 ControlPath.Fwd("PlaylistPager").Scope.Refresh();
82 ControlPath.Fwd("PlaylistRepeater").Scope.Refresh();
83
84 ControlPath.Fwd("VideoItemRepeater").Scope.Refresh();
85 }
The first question that rises here is why do we use all this method registering
technique and dont just expose a public interface on the controller and call functions
directly? Well, this should never be done in DaST, because, recall, that there is
always a single instance of the controller per data scope which is reused for all
repeated scope instances. So, calling controller methods through registration mechanism we
simply allow the system to maintain the right context on the target controller instance.
8) Direct template manipulation
This is one of my favourite features in the new DaST framework. So far we modified
scope content only by replacing placeholders with real values. If we needed to hide
or show some areas depending on certain condition, we could enclose these areas
into actual scopes and use RenderType
property for hiding and showing these
scopes. But
the problem is that we might need multiple conditional areas and creating separate
scopes for each of them would become messy in the template and would flood the back-end
controller with tons of unwanted trivial code for hiding, showing, and refreshing
these data scopes. And the solution here is to allow direct scope markup manipulation
which beautifully complements already existing placeholder replacements. First manipulation
that I've added is ShowConditionArea(..)
function (see Fig. 10) to show or hide a part of the
scope markup.
For example, ReviewsInfo scope (see Fig. 5) is responsible for the text displayed
in the title of the second tab of the details dialog (see Fig. 2). When there are
comments for the current video, the text should say "XX user review(s)".
If there are no comments, the text will be "0 user reviews(s)". But what
if I wish to display more user friendly message instead, something like "No user reviews
yet"? Now the answer is simple - use the conditional area! Listing 15
shows part of the template with ReviewsInfo scope. To create a conditional area,
markup must be enclosed between <!--showfrom:condition_name-->
and
<!--showstop:condition_name-->
.
This special format is recognized by the system during rendering and processed accordingly.
On Listing 15 we have 2 conditional areas with conditions comm-empty:yes
and
comm-empty:no
meaning that comments are empty and not empty respectively.
Listing 15: Part of DetailsPopupTemplate.htm showing ReviewsInfo scope.
4 <div class="screenContent nested0" scope="DetailsContent">
5 <div id="tabs">
6 <ul>
7 <li><a href="#tabs-1">Video Details</a></li>
8 <li>
9 <a href="#tabs-2"><span>
10 <div style="display: inline;" scope="ReviewsInfo">
11 <!--showfrom:comm-empty:yes-->
12 No user reviews yet
13 <!--showstop:comm-empty:yes-->
14 <!--showfrom:comm-empty:no-->
15 {CommCount} user review(s)
16 <!--showstop:comm-empty:no-->
17 </div></span>
18 </a>
19 </li>
20 </ul>
After template is ready, we just need to call ShowConditionArea(..)
on the
ReviewsInfo
scope to show or hide areas depending on the condition. Look how this is done at
lines 200-201 in the code listing on Listing 9. On line 200 we instruct the rendering
engine to show comm-empty:yes
area only when
commCount <= 0
i.e. when there
are no comments. Now imagine how flexible your UI can be having this small tool.
You can have multiple conditional areas, nest them into each other for conditional
AND, or put them beside each other for conditional OR! Just like placeholder replacements,
this manipulation can be applied to repeated content. Also remember, that DaST applies
template transformations in the same order they were called in the controller class.
I.e. if you first add some placeholder replacements and then apply conditional areas,
the order of transformations will be kept during rendering. Using this amazing tool
we must keep in mind that conditional areas, obviously, cannot have nested data
scopes. In the next version of the framework I plan to add more useful direct template
transformations.
9) DaSTUtils class
I've added DaSTUtils
class containing a set of utility functions simplifying some
common programming tasks. This utility was used in listing on Listing 13 to retrieve
values from generic object passed as a parameter to UpdatePagerValues(..)
function.
DaSTUtils
class diagram is shown on Fig. 16.
Fig. 16: DaSTUtils class.
Brief summary of the functions is below:
GetValue
- retrieves public property with specified name from the object.
HasValue
- checks if the object has a public property with specified name.
ParseJSON
- parses JSON string and returns object graph.
Further Development Plans
Although, current version of the DaST Rendering Engine is still Beta, it's
pretty stable and you can start playing with it and using it in your projects (obvioulsly,
no warranty -- see copyright). I can't promise anything about official documentation
-- most likely we will have to wait for several months. Overall architecture of
the platform is complete and I dont expect any major design changes; however, I'll
be refactoring certain parts of the framework and adding more features to it. Most
significant DaST developments that will take place in the near future are summarized
below.
- Clean exceptions, troubleshooting utils, etc.
First thing needed is clean and uniform exceptions handling with meaningful messages
to help finding a problem. I'll create a new exception type thrown for all developer
faults. We also need some way to view model scope tree information at any time.
This is very useful for troubleshooting and testing. By design, the scope tree data
structure is not exposed to outside and this is not going to change, but I'll add
something like ToXml(..)
method or similar so that the developer will
always be able to see how his changes impact the internal data structures.
- More direct template manipulation features.
In addition to placeholder replacements and conditional areas I plan to add the
ability to repeat parts of the resulting fragment. This means that right inside
the data scope we will be able to create repeaters and mix them with conditional
areas. This feature is extremely important and useful, because we might not want
to create separate data scopes for repeaters and grids. I'm sure the developers
will really appreciate outstanding flexibility allowed by direct template manipulation
in DaST applications.
- Transfer scope DIV attributes to initial scope parameters.
I was planning to do this for the current version, but simply forgot. It happens
:) Like standard Forms allow parametrizing controls by specifying their properties
right inside the ASPX file, DaST framework will allow parametrizing data scopes.
Inside the template you'll specify scope DIV tag with a set of attributes. All atributes
starting from underscore "_" will become scope parameters and will be available
to you through the scope instance. Underscore is needed so that scope params do
not overlap with standard HTML attributes.
- Allow various scope containers.
I have to check everything first, but it seems to me that there is no problem
to allow scope containers in the templates to be not just DIV tags, but all suitable
container tags i.e. those that have innerHtml
property. This is needed,
because, some apps have to have strict W3C compliance meaning that, for example,
DIVs cannot be put inside SPANs which creates certain limitations for placing DIV
scopes in the templates. Allowing data scopes to use various containers solve this
problem completely.
- Moving from ASP.NET Ajax to jQuery Ajax implementation.
This, I think, is the most significant change that will involve major development.
Current partial update implementation in DaST Rendering Engine is totally based
on the standard ASP.NET Ajax Library. The implementation itself is very simple,
partial update engine is quite stable, and I was able to merge DaST partial updates
into the standard AJAX in a very elegant way, which sounds pretty good. The only
problem is that Microsoft's client AJAX Library is huge and 90% of its code becomes
useless if your application uses DaST framework. So, my opinion is that in a good
production version such overhead is unacceptable and must be eliminated. This basically
means that in the future the entire AJAX library will have to be rewritten to suite
the needs of DaST developers. The solution that seems attractive to me is to use
jQuery Ajax implementaion, but I'm not sure that this is the best solution. I might
also end up with clear JavaScript implementation without any 3rd party libraries.
And this is it. I invite everyone to visit DaST dev
site frequently and watch for news and framework updates. We're still setting
up an open-source project environment and I'll post the news about it whenever anything
is ready. I apologize for the delays -- I was really busy during last several months
with new interesting projects on my primary job. But since all architectural work
is complete, from now on I'll be updating the framework very frequenly adding more
features to it. The official release of the DaST Rendering Engine will be in a couple
of months, but the current Beta version is fully functional and stable to be used
in your projects.