Click here to Skip to main content
15,881,424 members
Articles / Web Development / HTML

Yes We Can! Open-source DaST competes with MVC and WebForms

Rate me:
Please Sign up or sign in to vote.
4.92/5 (7 votes)
28 Sep 2011CPOL25 min read 23.3K   281   19   3
DaST is a brand new concept with open-source framework. The article is to highlight the most important differences of the latest RC version, and to demonstrate one more time DaST elegance and simplicity over MVC and WebForms by example of creating a fully dynamic tree view web application.

Abstract

I while ago I proposed a new DaST web dev pattern and its open-source ASP.NET implementation, ASP.NET DaST Rendering Engine, as a solution having huge open-source dev potential and outperforming all standard server-page frameworks such as ASP.NET Forms or MVC. This is my 3rd article on DaST concept timed to the issue of RC version of DaST library. Considering experience with my previous articles, this time I’ll not say that DaST is smarter or better than any frameworks, especially MVC, in order to not hurt feelings of some developers :) I’ll just explain how things work, state only established facts, and I let you make your own conclusions which framework is better for your web development work ;))

I know I promised DaST library production release 4 months ago, but unfortunately I had to go abroad for a while, which delayed framework development quite a bit. The version that has just been issued is Beta4 RC. This is a fully featured, tested, and stable version of the framework, which you can download and start using in your projects!

Initially, I was not going to write another article before production release, but since I've made some minor syntax changes and one major architectural change to how the scope tree is constructed, and there is no official documentation available yet, I decided to explain my recent changes in more details, as some of you might want to participate in DaST open-source project development. The version that has just been issued is Beta RC and this article is to highlight the most important differences of Beta4 compared to previous DaST framework versions. This article will consist of 4 parts. First, I’ll quickly present a new TreeView DEMO application. Next, I’ll describe all syntax and architectural changes made since the previous version. Then we will delve into TreeView application implementation to see all latest framework changes in action. And finally, I'll shortly discuss my plans for next library releases.

All source code for this article can be downloaded from the SourceForge. Here are some links:

Before You Read Anything

For the current article I’m assuming that you're already well familiar with DaST pattern and you at least read my previous article with the detailed tutorial for creating VideoLibrary DEMO application using DaST. If not, then below I list some resources that you should go through before reading any further:

If there are any questions after reading all this, dont hesitate to ask them on the public DaST discussion forum on SourceForge. For convinience, I opened this forum for anonimous access.

Also, throughout this article, I’ll use VideoLibrary DEMO as a sample for explanations in some cases, so let’s agree that I’ll reference my previous article on VideoLibrary application as “Article 2”.

Table of Contents

TreeView DEMO

This demo application is a prototype of a dynamic tree view displaying hierarchically structured data with async on-demand loading of data branches. Hierarchical data I’ve chosen for this application is a set of randomly nested folders and documents (like old view of MSDN help). Another great example could be a discussion forum thread with tree-like replies structure. Main purposes of TreeView DEMO application are 1) to demonstrate simplicity and elegance of DaST pattern one more time, and 2) to show the importance of the latest design change consisting in on-demand construction of the scope controller models. I deployed this application on my DaST blog, so you can run this demo LIVE right from the site. Typical UI of the application is shown on Fig 1:

Image 1
Fig. 1: TreeView DEMO application UI

So, the application simply displays a tree of folders and documents. Both folders and documents can be nested in parent folders to reach the unlimited hierarchical nesting structure. User is allowed to modify a tree by adding new or deleting existing folders and documents, except that root folder cannot be deleted. If container folder is deleted, all its child folders and documents are deleted too. Initially every folder is displayed in “collapsed” state. When folder is clicked, its contents are loaded by async postback action, and folder switches to “open” state. This UI prototype is very primitive, but it could be used as a good starting point for implementing more complex web applications requiring on-demand loading feature for hierarchical data.

Nesting structure of folders and documents is defined by the XML. This physical XML file has some initial folder/document structure in it and is loaded for each session into memory staying there until current session ends. All changes made to the tree are stored in this in-session XML object, but not in the physical file meaning that all your changes are discarded whenever ASP.NET session renews. Fig 2 shows a part of this XML file which defines tree structure from Fig 1:

Image 2
Fig. 2: XML data for TreeView application

Before we turn to implementation details of TreeView DEMO application, I'll go through the differences between Beta4 RC and the last Beta2 version of the framework.

Beta4 RC Changes Overview

To understand everything I’ll be talking about in this section, you should read my Article 2 that uses another VideoLibrary application as demo for Beta2 framework features. I’d like to make this section really short, so instead of posting code snippets here, I’ll reference them in my previous article e.g. “Listing X in Article 2”. Also, VideoLibrary demo has been updated to run on the newest version of DaST framework and its source code can be obtained from SourceForge in case you wish to do deeper comparison with previous version of VideoLibrary from Article 2. So, let’s start from simple syntax changes.

Minor Syntax Changes

Below I'll just list several minor syntax changes that took place:

  • GetTemplate() instead of SetTemplate(SetTemplateArgs template)

    Now instead of assigning template content in SetTemplate(..) function (Listing 7 of Article 2), we just return template as string from GetTemplate() function. Previous construct was used, because I thought we might need some other settings modified in SetTemplateArgs parameter, but seems like we don’t need this feature anymore.

  • InitializeModel(..) instead of SetupModel(..)

    There is no hidden meaning here – I just like the second name better. So, implementing controllers with new DaST library version, you simply use InitializeModel(..) instead of SetupModel(..) that was used before on Listing 7 of Article 2.

  • Action handlers are passed generic object parameter.

    Refer on Listing 7 of Article 2 again. Instead of having action parameters of type ActionArgs, we will now have parameters of generic object type. If action is raised on the client side by DaST.Scopes.Action(..), then this object will be a string (whatever is passed in that client call); otherwise, if action is raised from parent controller or invoked from child controller (new functionality, read further sections for details), then action argument could be any object passed from parent or child controller. This construct is simpler and more intuitive.

  • More scope containers: DIV, SPAN, and TD

    As promised, I extended the list of valid scope containers. Now instead of DIV, you can also use SPAN and TD. The main criteria for the valid container is that this DOM element must have an innerHTML property for the Ajax routine to be able to perform async refresh on it.

  • Scope definition in template changed: “dast:scope” instead of “scope

    Refer to Listing 6 of Article 2. We used “scope” attributes on DIV containers to tell the system that this is a scope. I think it’s a reasonable change to have attribute namespace and use “dast:scope” attributes instead.

Interim Scope Parameters

I decided to separate scope parameters into permanent parameters and interim parameters that persist only during single rendering process and are not serialized. This is a very reasonable change, because often we just want to pass values between controllers within one postback and these values do not necessarily need to be serialized. Remember that the more scope params are serialized, the more data is transferred between async postbacks resulting in increased traffic.

ControlPath.Scope.Params is a collection for permanent parameters, the ones that you want to persist between async postbacks. Objects that you add to this collection must be serializable to JSON (parameters are stored in JSON format). Interim params are stored in ControlPath.Scope.InterimParams collection. Since values in this collection are not serialized, they can be just any generic objects.

Major Change: Model Tree Built On Demand

One of the reasons for this article was actually this major change which I’d like to emphasize, because it changes how we thought about rendering process before.

Recall description of the rendering process in Article 2. In that version of DaST, the model scope tree was build before the rendering process started. I.e. before any data binding handlers in any controllers were called, the system had to construct the complete model scope tree by invoking SetTemplate() and

SetupModel()
functions for each controller one by one. And this actually works fine, but only when we know the structure of model tree before the rendering process. But what if I want to add some different child controller depending on the conditions revealed only at rendering stage? Think about our TreeView application … When folder is opened, its child items can be ether folders or documents. So, if folder has N child items in it, the child scope has to be repeated N times to display these N items and each item could be ether folder or document. Wnen model tree is built from the beginning, after that we can only repeat scopes, but structure of the tree does not ever change. In our case this causes the problem, because two repeated child folders can have absolutely different child branches coming from them which means different model trees. So, basically, before repeating each folder node, we need to "adjust" its model subtree to be the one needed for this folder!

So it became clear that building the entire model tree at once is not the right way to do it. The model tree should be built on per-controller basis every time the rendering process hits the controller root scope. Look at Fig 5 of VideoLibrary model tree in Article 2. What’s the right way to build the model tree here? The rendering process starts from NULL scope as before, but model tree must be empty at this time. Then NULL scope is encountered, SetTemplate()/SetupModel() are called, and model tree gets built ONLY for NULL scope e.g. “AddButton” scope is not known to the model tree at that time, because rendering traversal did not come to the controller at “VideoItem” scope yet. Then traversal continues to all child scopes, renders them, but whenever the system encounters some child controller attached to the current scope e.g. “PlaylistPager”, the model tree for this controller is built first, before invoking any data binding handlers i.e.

SetTemplate()/SetupModel
      ()
are called, and then data binding handlers are called. And so on – the rendering process continues recursively until the entire tree is rendered. Next, recall that if “VideoItemRepeater” repeats “VideoItem” N times, then data binding handlers are called N times as well. Now this means that
SetTemplate()/SetupModel
      ()
are also called N times, on each rendering iteration before binding handlers! And this is great, because now you can assign your child controllers directly before each controller rendering iteration.

Another important thing is that you’re now allowed to use ControlPath.Scope.Params inside the SetTemplate() and SetupModel() (in a new design these are called GetTemplate() and InitializeModel(..)) functions. This means that you can get all your serialized parameters and, depending on them, set different controller template or add different child controllers for current controller, right before this controller is rendered. Note that you can use only permanent parameters here; interim params cannot be used within model initialization functions. I’ll explain this below.

When page is loaded for the first time, the rendering process starts from the root of the tree i.e. the entire tree is built. On the async postback there is absolutely no need to rebuild the whole tree – we only need to build the branch to the action scope. This means that before async action is processed, all

SetTemplate()/SetupModel
      () 
functions are called for the controllers on the action branch to restore the model tree to the action scope. If further, during action processing, you invoke action on the controller outside of the action branch, then model tree is rebuilt for this controller on demand. Since model tree is restored, add parameters impacting its structure, should be restored as well, because structure of the model tree lying on action path should be exactly the same as after previous rendering, otherwise, system will simply fail to find the target controller for current action. And this is the reason for not allowing usage of interim params inside model setup functions – we want the params to be restorable on the postback.

This explanation might sound a little bit complex, but it’s actually very simple and intuitive once you get used to it. Look further at the TreeView application implementation where this new on-demand model construction design becomes a key to achieving the desired application functionality.

Methods Replaced By Actions

Recall scope methods mechanism used in previous VideoLibrary described in Article 2. This mechanism is now completely gone (I never liked it, to be honest :) Now, instead of calling methods on a controller, you will simply invoke scope actions! So, to pass action to the parent controller you’ll do ControlPath.Scope.RaiseAction("PlaylistUpdated", null) as you did before. But to tell the parent controller to execute some logic, you’ll just register some scope action on it and invoke it manually e.g.

ControlPath.Fwd("PlaylistPager").Scope.InvokeAction("UpdatePagerValues",
      new { ItemTotalCount = itemTotalCount })
to tell the pager controller to recalculate its values. Take a look at the source code of the new version of VideoLibrary application – it demonstrates this mechanism very well.

You may say that actions do not completely replace methods, because they don’t return values. Well, yes, but taking into account how clear and uniform your code becomes having actions used everywhere, this limitation is reasonable and totally acceptable. If you really need to return value from some action, you could set some ReturnValue on the complex parameter passed to the action handler.

And we’re done with changes! Now let’s turn to the implementation details of TreeView DEMO.

TreeView DEMO Implementation

This simple application demonstrates how the new on-demand model building design works in the application with varying model tree, which was not easily implementable with previous versions of DaST. And, as usual we start from planning a model scope tree itself.

TreeView Model Tree

To achieve same UI as on Fig 1, I came up with the following model scope tree structure displayed on Fig 3 below:

Image 3
Fig. 3: Model scope tree for TreeView application

Recall from Article 2 that red nodes mean nodes with controllers attached to them. There are 4 controllers used in this application: RootController, NodeSelector, FolderNode, and DocumentNode. RootController is attached to “NULL” root scope. NodeSelector is attached to “Selector” scope. And “Node” scope can have either FolderNode or DocumentNode attached to it. Since “Node” scope can have different controllers attached, the model tree coming out of the “Node” scope can also be different: if

FolderNode
is attached, model tree goes on option (1), if DocumentNode is attached, model tree goes on option (2).

On Fig 3 I also specified actual templates, to which scopes in model tree belong. These are depicted as green-colored areas with template file name on the top. There are 4 templates corresponding to each of the controllers: RootTemplate.htm used by RootController, FolderTemplate.htm used by FolderNode, DocumentTemplate.htm used by DocumentNode (this template is depicted empty on the figure, because does not have any scopes, only HTML), and <div dast:scope="Node"></div> used by NodeSelector (template defined as HTML text, because this controller returns a hardcoded string instead of loading template from a file – refer to controller source code for more details).

Finally, the idea how this is going to work is simple. NodeSelector controller retrieves the corresponding XML element using XPath expression passed to it. If element is a folder (see XML on Fig 2), then NodeSelector assigns a FolderNode controller to child “Node” scope. FolderTemplate.htm corresponding to FolderNode has 4 other scopes in it. “AddFolder” scope has markup to input name of the new folder and submit it and this scope is displayed only when user clicks one of “Add Folder” link buttons on the tree view. Similar thing with “AddDocument” scope – this one is needed to add document node to tree view. Most interesting scope here is “ChildItemRepeater” – it takes care of displaying child items of the folder on a tree view. It has a “Selector” child scope with NodeSelector attached to it, meaning that “ChildItemRepeater” renders NodeSelector N times for each child item in the folder. Then NodeSelector is passed new XPath for every iteration, retrieves another XML element, and so on – the process repeats recursively until all XML elements are exhausted. By default, each folder is output in collapsed state i.e. none of its child items are rendered, and the user has to click the folder to open it. In case if XML element is a document, everything is much easier – DocumentNode controller is assigned to “Node” and just renders some HTML to show document on a tree view.

I’ll not go through any HTML template sources in this article – they are really small and simple and you can just download the application and take a look. Now, we are more interested in the back-end part of TreeView i.e. 4 scope controllers that I mentioned above. So let’s take a closer look at their implementations with details and explanations.

Controller Implementations

Below I’m going to give complete code listings of all 4 controllers. One reason for that is because I want you to think how you would implement the same TreeView application in Forms or MVC and compare this to the DaST implementation. Note that all TreeView implementation including async refresh logic is on the back-end and there is 0 (zero) javascript in the templates – all programming you need is inside your controller classes!

RootController controller

Let’s start from the RootController whose code is shown below on Listing 1.

Listing 1: RootController source code
C#
32:	public class RootController : ScopeController
33:	{
34:	  public override string ProvideTemplate()
35:	  {
36:	    return Utils.LoadTemplate("RootTemplate.htm");
37:	  }
38:	
39:	  public override void InitializeModel(ControllerModelBuilder model)
40:	  {
41:	    model.Select("Selector").SetController(new NodeSelector());
42:	
43:	    model.SetDataBind(new DataBindHandler(delegate()
44:	    {
45:	      ControlPath.Fwd("Selector").Scope.Params.Set("Path", "./*[1]");
46:	    }));
47:	  }
48:	}

Everything is quite trivial here:

  • Line 36 – specify the controller template.
  • Line 41 – attach NodeSelector controller to the “Selector” scope (the one that goes right after “NULL” root scope on Fig 3)
  • Lines 43-46 – controller root data binding. Note that I use inline delegate for binding handler instead of full method references as I did before in Article 2. This form of syntax is just my personal preference for this particular application, because it simpler and saves some space.
  • Line 45 – inside root binding handler, we simply pass a “Path” parameter to the NodeSelector controller that we just added to the “Selector” scope. This path contains a valid XPath expression that selects the target XML element that NodeSelector will process. In our case we pass the XPath of the first child of the <treeview> document element i.e. the root <Folder name="Web Development"> element from Fig 1.

After rendering procedure passes through the RootController, it encounters NodeSelector (that we just added to “Selector” scope) whose source code goes next.

NodeSelector controller

Source code of NodeSelector is below on Listing 2:

Listing 2: NodeSelector source code
C#
32:	public class NodeSelector : ScopeController
33:	{
34:	  public override string ProvideTemplate()
35:	  {
36:	    return "<div dast:scope=\"Node\"></div>";
37:	  }
38:	
39:	  public override void InitializeModel(ControllerModelBuilder model)
40:	  {
41:	    string path = ControlPath.Scope.Params.Get<string>("Path");
42:	    var xelem = DataLayer.GetHierarchyElement(path);
43:	
44:	    if (xelem.Name == "Folder")
45:	    {
46:	      model.Select("Node").SetController(new FolderNode());
47:	      model.Select("Node").HandleAction("NodeDeleted", new ActionHandler(delegate(object arg) 
48:	      {
49:	        ControlPath.Scope.RaiseAction("NeedRepeaterRefresh", null);
50:	      }));
51:	    }
52:	    else if (xelem.Name == "Document")
53:	    {
54:	      model.Select("Node").SetController(new DocumentNode());
55:	      model.Select("Node").HandleAction("NodeDeleted", new ActionHandler(delegate(object arg)
56:	      {
57:	        ControlPath.Scope.RaiseAction("NeedRepeaterRefresh", null);
58:	      }));
59:	    }
60:	    else throw new NotSupportedException("Not supported type of node in TreeView definition");
61:	
62:	    ControlPath.Fwd("Node").Scope.Params.Set("Path", path);
63:	  }
64:	}

The whole purpose of this controller is to attach the right child controller to the “Node” scope to display ether folder or document. So, rendering procedure encounters NodeSelector and calls its ProvideTemplate() and InitializeModel() one after another to rebuild the model tree of the current controller for the current rendering iteration.

  • Line 36 – we return the controller template which basically consists of a single “Node” child scope.
  • Lines 41-42 – we retrieve the XPath expression (that we passed to the current controller on line 45 of RootController) and get the actual XML element using this expression.
  • Lines 44-51 – we test our element name if this is a folder and if this is, then we attach FolderNode controller to “Node” scope, and handle “NodeDeleted” action on this controller. Action handler simply passes “NeedRepeaterRefresh” to the parent controller using RaiseAction(..) call.
  • Lines 52-59 – same as before, but for DocumentNode controller.
  • Line 62 – we pass XPath further to the “Node” scope i.e. either FolderNode or DocumentNode controller that we have just attached to it.

Note that we did not do any data binding here, because we simply do not need it. Now, let’s assume that we have a folder element i.e. FolderNode has been added to the “Node” scope. In this case, the next controller hit by rendering process is FolderNode – the one that has the most complex implementation.

FolderNode controller

Source code for this controller is below on Listing 3:

Listing 3: FolderNode source code
C#
034:	public class FolderNode : ScopeController
035:	{
036:	  public override string ProvideTemplate()
037:	  {
038:	    return Utils.LoadTemplate("FolderTemplate.htm");
039:	  }
040:	
041:	  public override void InitializeModel(ControllerModelBuilder model)
042:	  {
043:	    ControlPath.Scope.Params.Init("IsOpen", false);
044:	
045:	    string path = ControlPath.Scope.Params.Get<string>("Path");
046:	    var folderElem = DataLayer.GetHierarchyElement(path);
047:	    
048:	    
049:	    model.Select("ChildItemRepeater", "Selector").SetController(new NodeSelector());
050:	
051:	
052:	    model.Select("ChildItemRepeater", "Selector").HandleAction("NeedRepeaterRefresh", new ActionHandler(delegate(object arg)
053:	    {
054:	      ControlPath.Fwd("ChildItemRepeater").Scope.Refresh();
055:	    }));
056:	
057:	    model.HandleAction("ToggleOpen", new ActionHandler(delegate(object arg)
058:	    {
059:	      bool bOpen = !ControlPath.Scope.Params.Get<bool>("IsOpen");
060:	      ControlPath.Scope.Params.Set("IsOpen", bOpen);
061:	      ControlPath.Scope.Refresh();
062:	    }));
063:	
064:	    model.HandleAction("ShowAddFolder", new ActionHandler(delegate(object args)
065:	    {
066:	      ControlPath.Fwd("AddFolder").Scope.Refresh();
067:	      ControlPath.Fwd("AddDocument").Scope.Refresh();
068:	      ControlPath.Fwd("AddDocument").Scope.RenderType = ScopeRenderType.Empty;
069:	    }));
070:	
071:	    model.HandleAction("ShowAddDocument", new ActionHandler(delegate(object args)
072:	    {
073:	      ControlPath.Fwd("AddFolder").Scope.Refresh();
074:	      ControlPath.Fwd("AddDocument").Scope.Refresh();
075:	      ControlPath.Fwd("AddFolder").Scope.RenderType = ScopeRenderType.Empty;
076:	    }));
077:	
078:	    model.HandleAction("SubmitFolder", new ActionHandler(delegate(object arg)
079:	    {
080:	      ControlPath.Fwd("AddFolder").Scope.Refresh();
081:	      ControlPath.Fwd("AddFolder").Scope.RenderType = ScopeRenderType.Empty;
082:	           
083:	      var folderName = Regex.Replace((string)arg, "[\\<\\>\r\n]", "", RegexOptions.Singleline);
084:	      if(!string.IsNullOrEmpty(folderName.Trim())) DataLayer.AddFolder(path, folderName.Trim());
085:	      
086:	      ControlPath.Fwd("ChildItemRepeater").Scope.Refresh();
087:	
088:	      if (!ControlPath.Scope.Params.Get<bool>("IsOpen"))
089:	      {
090:	        ControlPath.Scope.Params.Set("IsOpen", true);
091:	        ControlPath.Scope.Refresh();
092:	      }
093:	    }));
094:	
095:	    model.HandleAction("SubmitDocument", new ActionHandler(delegate(object arg)
096:	    {
097:	      ControlPath.Fwd("AddDocument").Scope.Refresh();
098:	      ControlPath.Fwd("AddDocument").Scope.RenderType = ScopeRenderType.Empty;
099:	      ControlPath.Scope.Params.Set("IsOpen", true);
100:	
101:	      var documentName = Regex.Replace((string)arg, "[\\<\\>\r\n]", "", RegexOptions.Singleline);
102:	      if (!string.IsNullOrEmpty(documentName.Trim())) DataLayer.AddDocument(path, documentName.Trim());
103:	
104:	      ControlPath.Fwd("ChildItemRepeater").Scope.Refresh();
105:	
106:	      if (!ControlPath.Scope.Params.Get<bool>("IsOpen"))
107:	      {
108:	        ControlPath.Scope.Params.Set("IsOpen", true);
109:	        ControlPath.Scope.Refresh();
110:	      }
111:	    }));
112:	
113:	    model.HandleAction("CancelFolder", new ActionHandler(delegate(object arg)
114:	    {
115:	      ControlPath.Fwd("AddFolder").Scope.Refresh();
116:	      ControlPath.Fwd("AddFolder").Scope.RenderType = ScopeRenderType.Empty;
117:	    }));
118:	
119:	    model.HandleAction("CancelDocument", new ActionHandler(delegate(object arg)
120:	    {
121:	      ControlPath.Fwd("AddDocument").Scope.Refresh();
122:	      ControlPath.Fwd("AddDocument").Scope.RenderType = ScopeRenderType.Empty;
123:	    }));
124:	
125:	    model.HandleAction("DeleteFolder", new ActionHandler(delegate(object arg)
126:	    {
127:	      DataLayer.DeleteHierarchyElement(path);
128:	      ControlPath.Scope.RaiseAction("NodeDeleted", null);
129:	    }));
130:	
131:	
132:	
133:	    model.SetDataBind(new DataBindHandler(delegate()
134:	    {
135:	      CurrentPath.Scope.Binder.Replace("{FolderName}", folderElem.Attribute("name").Value);
136:	      CurrentPath.Scope.Binder.Replace("{XPath}", path);
137:	      CurrentPath.Scope.Binder.ApplyAreaConditional("not-root-node", folderElem.Parent.Name != "TreeView");
138:	
139:	      bool bOpen = ControlPath.Scope.Params.Get<bool>("IsOpen");
140:	      CurrentPath.Scope.Binder.ApplyAreaConditional("state-open", bOpen);
141:	      CurrentPath.Scope.Binder.ApplyAreaConditional("state-collapsed", !bOpen);
142:	
143:	
144:	      ControlPath.Fwd("AddFolder").Scope.RenderType = ScopeRenderType.Empty;
145:	      ControlPath.Fwd("AddDocument").Scope.RenderType = ScopeRenderType.Empty;
146:	    }));
147:	
148:	    model.Select("ChildItemRepeater").SetDataBind(new DataBindHandler(delegate()
149:	    {
150:	      bool bOpen = ControlPath.Scope.Params.Get<bool>("IsOpen");
151:	      CurrentPath.Scope.Binder.RestartRepeater();
152:	      if (bOpen)
153:	      {
154:	        for (int i = 0; i < folderElem.Elements().Count(); i++)
155:	        {
156:	          CurrentPath.Scope.Binder.Repeat();
157:	          string childPath = string.Format("{0}/*[{1}]", path, (i + 1).ToString());
158:	          CurrentPath.Fwd(i, "Selector").Scope.Params.Set("Path", childPath);
159:	        }
160:	      }
161:	    }));
162:	  }
163:	}

This controller will actually draw the folder on the tree view. In addition to this, we have to check folder state and if it is open, then output the child items of the current folder which can be folders or documents. Again, for all action and data binding handlers I use inline delegates, so the entire controller implementation comes within InitializeModel(..) function.

  • Line 38 – return content of FolderTemplate.htm (scope skeleton of this template is on Fig 1)
  • Line 43 – use Init(..) to set “IsOpen” param if it is not set yet
  • Lines 45-46 – retrieve XPath expression (previously set on line 62 of NodeSelector) and use it to get the folder element. Note that folderElem variable is accessible from inline delegate handlers defined further in
    InitializeModel(..)
    method.
  • Line 49 – attach NodeSelector controller to the “Selector” scope coming out of the “ChildItemRepeater”.
  • Lines 52-55 – handle “NeedRepeaterRefresh” action raised by NodeSelector controller that we have just added. This action just tells the FolderNode controller to refresh the “ChildItemRepeater” scope (line 54) i.e. redraw folder child items.
  • Lines 57-62 – handle controller’s “ToggleOpen” action that occurs when user clicks on the folder node in the tree. On this action we simply get folder state flag, take logical NOT of it to toggle, and save the updated value. Then we refresh from controller root to redraw the entire folder with all its contents.
  • Lines 64-69 – handle controller’s “ShowAddFolder” action that occurs when user clicks “Add Folder” link beside folder name. “AddFolder” scope (refer Fig 1) is initially invisible and we can display it simply by calling
    Refresh(..)
    on line 66. Recall from Article 2that Refresh(..) automatically sets scope RenderType to
    Normal
    
    . In case if “AddDocument” scope is currently visible, we need to hide it so we call Refresh(..) and set its RenderType to Empty.
  • Lines 71-76 – same as lines 64-69, but for “ShowAddDocument” action.
  • Lines 78-93 – handle controller’s “SubmitFolder” action that occurs after user input the new folder name and click “Submit” link button. When folder is submitted, we need to cleanup new folder input box (lines 80-81), get rid of bad characters in name (line 83), and populate in-session XML with a new element (line 84) using data layer. Next, we want to actually show our newly added folder, so we refresh “ChildItemRepeater” scope (line 86) whose last child item will now be the new folder that we just added. If folder is in collapsed state, then we need to open it automatically when item is added – that’s what we have lines 88-92 for.
  • Lines 95-111 – same as lines 73-93, but for “SubmitDocument” action.
  • Lines 113-117 – handle controller’s “CancelFolder” action that occurs when user clicks “Cancel” link button on the new folder input box. Here we simply hide the new folder input.
  • Lines 119-123 – same as lines 113-117, but for “CancelDocument” action.
  • Lines 125-129 – handle controller’s “DeleteFolder” action that occurs when “Delete” link button is clicked beside the folder name in the tree view. On this action, we need to physically remove this folder element from in-session XML (line 127) and let the parent folder know that something was deleted and it needs to redraw its “ChildItemRepeater” scope (line 128).
  • Lines 133-146 – we databind the controller’s root scope, providing placeholder values (lines 135-136) and applying some area conditionals. “not-root-node” conditional is needed to hide the “Delete” link button for root tree view node. “state-open” and “state-collapsed” are needed to display correct folder image for different states. On lines 144-145 we initially set both “AddFolder” and “AddDocument” scopes to be invisible. We use Empty instead of None, because we still need scope container element to be output.
  • Lines 148-161 – finally, we databind the “ChildItemRepeater” scope. We retrieve folder open flag and if open, then scope is repeated N times for each child element in the current folder element. Repeating scopes, I construct the correct XPath expression on each iteration and pass it to the “Selector” child scope of “ChildItemRepeater”.

And we’re done with FolderNode! Of course, to understand everything completely, you’ll have to run and debug this application which I suggest you do. The last controller is DocumentNode which can be added by NodeSelector in case of XML element is a document.

DocumentNode controller

Source code of DocumentNode is below on Listing 4:

Listing 4: DocumentNode source code
C#
32:	public class DocumentNode : ScopeController
33:	{
34:	  public override string ProvideTemplate()
35:	  {
36:	    return Utils.LoadTemplate("DocumentTemplate.htm");
37:	  }
38:	
39:	  public override void InitializeModel(ControllerModelBuilder model)
40:	  {
41:	    string path = ControlPath.Scope.Params.Get<string>("Path");
42:	    var documentElem = DataLayer.GetHierarchyElement(path);
43:	
44:	    model.HandleAction("DeleteDocument", new ActionHandler(delegate(object arg)
45:	    {
46:	      DataLayer.DeleteHierarchyElement(path);
47:	      ControlPath.Scope.RaiseAction("NodeDeleted", null);
48:	    }));
49:	
50:	    model.SetDataBind(new DataBindHandler(delegate()
51:	    {
52:	      CurrentPath.Scope.Binder.Replace("{DocumentName}", documentElem.Attribute("name").Value);
53:	      CurrentPath.Scope.Binder.Replace("{XPath}", path);
54:	    }));
55:	  }
56:	}

This one is much simpler than FolderNode, because DocumentNode cannot contain any items meaning that no actions or child scopes are needed. I’ll not go into any details here, because source code is quite obvious.

And this is it! We’re completely done with our implementation of TreeView DEMO application. Try to compare implementation above with what you would have in Forms – a complete mess with user controls inside nested UpdatePanels plus terrible design in a code-behind class. And we always have to remember that on async postback DaST executes rendering logic for impacted area ONLY, but Forms executes the entire page handler from the beginning to the end which, besides killing hope for more or less clear back-end code design, affects overall application performance.

MVC will do much better than Forms, but you’ll have to add a significant piece of client script to get async updates to provide on-demand tree view branch loading. You’ll have to create a separate partial view to serve branch loading ajax calls and update the right spot on the tree view using javascript. Plus don’t forget about Add New Folder/Document capabilities, deletion, and so on – the script might go a bit complex in the end. And for sure it will not be simpler than just calling Scope.Refresh(..) in DaST implementation :)

Conclusion

I think that my next step on DaST framework development will be to create some quick start dev reference and simple framework documentation. This will have to be done before official DaST library release anyway and will take me some time (unless someone will help me with this). Also, currently I’m busy with one commercial project on DaST – internal administration console for one big e-commerce business. So, the most likely date of DaST library release is around the end of November.

Also, this is the new open-source project and it needs your help! We need the open-source developers and if you feel you can contribute to the project, please join the team! The new DaST pattern is developing rapidly and so is the framework. The design work is pretty much done, at least for now, but there are still tons of improvements to the code and certain modules that can be made. For example, params are serialized using Base64 over JSON which is ok for now, but if we add simple compression, the serialized value could be reduced by 5 times. Plus I’d really like VS integrated plugin to visualize the scope tree in the VS designer. And so on – there is lots of dev work for this new library. Plus, after ASP.NET version is rolled out, we will be starting open-source PHP DaST project, so PHP developers are welcome to join DaST team too!

I’d really appreciate if you give this framework a try in your project and let the team know about your experience, problems you’re having or framework bugs. Also, we’re always open for suggestions and well-grounded criticism. You can always get instant support or just share your opinion about the pattern on the DaST Public Forum which is open for anonymous access.

And this is all for this article! I made it a bit more technical this time, but I hope it was interesting anyway. DaST project home on the SourceForge is here. To get more information on DaST open-source project, come to the project info site. The summary of all latest DaST downloads is very useful and you can always find the most updated versions there. Finally, to be well informed of the latest DaST related events, please visit www.rgubarenko.net for all framework news and updates or sign up for the project mailing lists on SourceForge.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Canada Canada
Software Architect with over 15 years in IT field. Started with deep math and C++ Computer Vision software. Currently in .NET and PHP web development. Creator of DaST pattern, open-source frameworks, and plugins. Interested in cutting Edge IT, open-source, Web 2.0, .NET, MVC, C++, Java, jQuery, Mobile tech, and extreme sports.

Comments and Discussions

 
QuestionGood one Pin
kiran dangar4-Oct-11 3:39
kiran dangar4-Oct-11 3:39 
GeneralMy vote of 5 Pin
Anurag Gandhi2-Oct-11 22:38
professionalAnurag Gandhi2-Oct-11 22:38 
QuestionNice Article! +5 Pin
RAND 45586629-Sep-11 0:04
RAND 45586629-Sep-11 0:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.