User Interface

Labels
AJAX(112) App Studio(9) Apple(1) Application Builder(245) Application Factory(207) ASP.NET(95) ASP.NET 3.5(45) ASP.NET Code Generator(72) ASP.NET Membership(28) Azure(18) Barcode(2) Barcodes(3) BLOB(18) Business Rules(1) Business Rules/Logic(140) BYOD(13) Caching(2) Calendar(5) Charts(29) Cloud(14) Cloud On Time(2) Cloud On Time for Windows 7(2) Code Generator(54) Collaboration(11) command line(1) Conflict Detection(1) Content Management System(12) COT Tools for Excel(26) CRUD(1) Custom Actions(1) Data Aquarium Framework(122) Data Sheet(9) Data Sources(22) Database Lookups(50) Deployment(22) Designer(178) Device(1) DotNetNuke(12) EASE(20) Email(6) Features(101) Firebird(1) Form Builder(14) Globalization and Localization(6) How To(1) Hypermedia(2) Inline Editing(1) Installation(5) JavaScript(20) Kiosk(1) Low Code(3) Mac(1) Many-To-Many(4) Maps(6) Master/Detail(36) Microservices(4) Mobile(63) Mode Builder(3) Model Builder(3) MySQL(10) Native Apps(5) News(18) OAuth(9) OAuth Scopes(1) OAuth2(13) Offline(20) Offline Apps(4) Offline Sync(5) Oracle(11) PKCE(2) Postgre SQL(1) PostgreSQL(2) PWA(2) QR codes(2) Rapid Application Development(5) Reading Pane(2) Release Notes(184) Reports(48) REST(29) RESTful(29) RESTful Workshop(15) RFID tags(1) SaaS(7) Security(81) SharePoint(12) SPA(6) SQL Anywhere(3) SQL Server(26) SSO(1) Stored Procedure(4) Teamwork(15) Tips and Tricks(87) Tools for Excel(3) Touch UI(93) Transactions(5) Tutorials(183) Universal Windows Platform(3) User Interface(338) Video Tutorial(37) Web 2.0(100) Web App Generator(101) Web Application Generator(607) Web Form Builder(40) Web.Config(9) Workflow(28)
Archive
Blog
User Interface
Thursday, April 2, 2009PrintSubscribe
Hiding View Selector

All data views in Data Aquarium Framework web applications are automatically provided with a view selector, a drop down control displayed in the right top corner of action bar. This user interface element allows you to have a code-free presentation of different views of data

image

Here is the source code of this page:

<%@ Page Title="" Language="C#" MasterPageFile="~/MasterPage.master" 
    AutoEventWireup="true" CodeFile="Products.aspx.cs" 
    Inherits="Products" %>

<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="Header1Placeholder" 
        runat="Server">
    Hidden View Selector
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="Header2Placeholder" 
        runat="Server">
    Northwind
</asp:Content>
<asp:Content ID="Content4" ContentPlaceHolderID="BodyPlaceholder" 
        runat="Server">
    <div id="ProductsContainer" runat="server"/>
    <aquarium:DataViewExtender ID="ProductsExtender" 
        runat="server" TargetControlID="ProductsContainer"
        Controller="Products" />
</asp:Content>

In certain situations you may want to hide the view selector.

Modify Content1 and add CSS class NoHeader to ProductsContainer as shown in this example.

<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="Server">
    <style type="text/css">
        .NoHeader .ViewSelector
        {
            display: none;
        }
    </style>
</asp:Content>
........
<asp:Content ID="Content4" ContentPlaceHolderID="BodyPlaceholder" 
        runat="Server">
    <div id="ProductsContainer" runat="server" class="NoHeader"/>
    <aquarium:DataViewExtender ID="ProductsExtender" 
        runat="server" TargetControlID="ProductsContainer"
        Controller="Products" />
</asp:Content>

Refresh the page and the view selector will disappear.

image

Data Aquarium Framework completely relies  on CSS to control the visual appearance of all user interface elements. This allows easy an powerful customization of user interface without a myriad of complicated properties that control visual presentation.

Sunday, March 1, 2009PrintSubscribe
Business Rules: ControllerAction Attribute

We continue discussion of business rules started with posts about RowBuilder attribute. We will create a method that will handle processing of employee territories that were check-marked by user in editForm1.

image

Field Territories  is a placeholder field of String type. Business rule method PrepareExistingEmployeeRow() retrieves a list of territories that an employee is responsible for.

Now it is time to implement saving of new checked territories and deletion of unchecked ones.

Handling of Custom Fields

We will need two methods in our business rule class to process Territories field value.

First method will hide that fact of changes to Territories field value from Data Aquarium Framework.

C#:

[ControllerAction("Employees", "editForm1", "Update", ActionPhase.Before)]
protected void BeforeEmployeeUpdate(int employeeId, 
    FieldValue territories, FieldValue lastName)
{
    territories.Modified = false;
    lastName.Modified = true;
}

VB:

<ControllerAction("Employees", "editForm1", "Update", ActionPhase.Before)> _
Protected Sub BeforeEmployeeUpdate(ByVal employeeId As Integer, _
    ByVal territories As FieldValue, ByVal lastName As FieldValue)
    territories.Modified = False
    lastName.Modified = True
End Sub

The name of the method implies that we want this method to execute just before an employee update. But method name means nothing to the framework. Data Aquarium Framework relies on attribute ControllerAction to decide if it has to invoke a business rule method.

Our ControllerAction attribute specifies that method BeforeEmployeeUpdate() shall execute when command Update is issued in form editForm1 of data controller Employees. There is also an execution phase information. We want this method to be invoked before the framework tries to perform an update.

There are three parameters: employeeId, territories, and lastName.

Data Aquarium Framework assumes that parameter names are matching the values of data fields specified for editForm1 in the data controller descriptor ~/Controllers/Employees.xml.

The native data field value information is held in FieldValue class instances. If you specify a parameter of type FieldValue then you gain access to its properties OldValue, NewValue, Value, and Modified.

If you don't care about the nature of changes of the field then you can simply specify an exact type that is matched to the data field definition in data controller descriptor.

We know that employeeId has not changed and that is why we specify System.Int32 as the type of the field. We do want to perform certain manipulations with the state of changes to territories and lastName fields. That requires FieldValue type.

The order of the fields and name capitalization does not have to match the definitions in data controller descriptor. Specify only the fields that you need.

The first line of the method resets Modified property to hide any field changes. Here is how Territories field was defined in data controller descriptor:

        <command id="command1" type="Text">
            <text>
                <![CDATA[
select
    "Employees"."EmployeeID" "EmployeeID"
    ,"Employees"."LastName" "LastName"
    ,"Employees"."FirstName" "FirstName"
. . . . . . . . . . . . . . . . . ,"ReportsTo"."LastName" "ReportsToLastName" ,"Employees"."PhotoPath" "PhotoPath", ,null "Territories" <<< Territories Field Definition from "dbo"."Employees" "Employees" left join "dbo"."Employees" "ReportsTo" on "Employees"."ReportsTo" = "ReportsTo"."EmployeeID"
]]> </text> </command>

We are returning null as a field value. The field is not based on any actual database table field. The framework does not know that and will try to update null with a new value. Resetting Modified property to false will prevent any update attempts that would happen by default otherwise.

We do want the framework to perform an SQL update for any other fields that might have changed. If the only field that have changed is Territories then no update will be performed. That is why we always "pretend" that field lastName has been changed. An alternative is to write more code and execute an update on our own. You will have to call PreventDefault() method of your business rules class to prevent any default update logic from execution if you do your own updates.

Post-Action Business Logic

Here is how we will update the territories that an employee is responsible for.

C#:

[ControllerAction("Employees", "editForm1", "Update", ActionPhase.After)]
protected void AfterEmployeeUpdate(FieldValue territories, int employeeId)
{
    string[] newTerritories = 
        Convert.ToString(territories.NewValue).Split(',');
    string[] oldTerritories = 
        Convert.ToString(territories.OldValue).Split(',');
    // remove "unchecked" territories
    foreach (string territoryId in oldTerritories)
        if (!(String.IsNullOrEmpty(territoryId)) && 
            (Array.IndexOf(newTerritories, territoryId) == -1))
        {
            EmployeeTerritories et = 
                EmployeeTerritories.SelectSingle(employeeId, territoryId);
            et.Delete();
        }
    // add new "checked" territories
    foreach (string territoryId in newTerritories)
        if (!(String.IsNullOrEmpty(territoryId)) && 
            (Array.IndexOf(oldTerritories, territoryId) == -1))
        {
            EmployeeTerritories et = new EmployeeTerritories();
            et.EmployeeID = employeeId;
            et.TerritoryID = territoryId;
            et.Insert();
        }
}

VB:

<ControllerAction("Employees", "editForm1", "Update", ActionPhase.After)> _
Protected Sub AfterEmployeeUpdate(ByVal territories As FieldValue, _
    ByVal employeeId As Integer)
    Dim newTerritories As String() = _
        Convert.ToString(territories.NewValue).Split(",")
    Dim oldTerritories As String() = _
        Convert.ToString(territories.OldValue).Split(",")
    ' remove "unchecked" territores
    For Each territoryId In oldTerritories
        If Not String.IsNullOrEmpty(territoryId) AndAlso _
            Array.IndexOf(newTerritories, territories) = -1 Then
            Dim et As EmployeeTerritories = _
                EmployeeTerritories.SelectSingle(employeeId, territoryId)
            et.Delete()
        End If
    Next
    ' add new "checked" territories
    For Each territoryId In newTerritories
        If Not String.IsNullOrEmpty(territoryId) AndAlso _
            Array.IndexOf(oldTerritories, territories) = -1 Then
            Dim et As EmployeeTerritories = New EmployeeTerritories()
            et.EmployeeID = employeeId
            et.TerritoryID = territoryId
            et.Insert()
        End If
    Next
End Sub

Parameter territories is defined as a variable of type FieldValue since we want to know the previous value of the field. We split new and old value of the field by comma.

Next we scan old territories and if any territory is not in the list of new ones then we delete the territory with the help of EmployeeTerritories class that was automatically generated for us by ASP.NET code generator Code OnTime Generator.

Finally we scan new territories and for any "new" territories that were not in the list of old ones we are executing an employee territory insertion. Again we do that we the help of automatically generated business object EmployeeTerritories.

Conclusion

A simple and easy to understand server programming model for business rules is a crown jewel of Data Aquarium Framework.  Business rules are built on top custom action handlers and provide a truly simple and easy to use programming mode to enhance your AJAX ASP.NET application with server code.

In our next post we will discuss programmatic data filtering via business rules.

Monday, February 23, 2009PrintSubscribe
Starting DataViewExtender in "New" Mode

The latest update to Data Aquarium Framework introduces two new properties, StartCommandName and StartCommandArgument. You can use these properties to start a DataViewExtender instance in the desired mode by forcing it to execute a command just before the view is rendered in a web browser.

Quick Sample

Create a new page ~/NewProduct.aspx in a project generated with our ASP.NET Code Generator.

<%@ Page Title="" Language="C#" MasterPageFile="~/MasterPage.master" 
    AutoEventWireup="true" CodeFile="NewProduct.aspx.cs" Inherits="NewProduct" %>

<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="Header1Placeholder" runat="Server">
    New Product
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="Header2Placeholder" runat="Server">
    Northwind
</asp:Content>
<asp:Content ID="Content4" ContentPlaceHolderID="BodyPlaceholder" runat="Server">
    <div id="Products" runat="server" />
    <aquarium:DataViewExtender ID="ProductsExtender" runat="server" 
        TargetControlID="Products" 
        Controller="Products" View="createForm1" 
        StartCommandName="New" StartCommandArgument="createForm1" />
</asp:Content>

If you were to omit two last attributes in DataViewExtender markup then a read-only view of the first record of your data controller command is displayed. The framework treats all views the same way - there is no magic in "createForm1" name. The client engine will simply select the first record and present it in a specified view.

A client-side command "New" must be executed to force a display of a new record in createForm1. New properties will do the trick.

image

Client-Side State Machine

Another great feature available to developers is the client-side action state machine.

Run sample described above and then create a new record. As soon as a record is inserted you will be redirected to the "grid" view of all products. The same happens if you press Cancel button.

Suppose you want to use Cancel button to simply reset the form to the original "new" state. Also you want to rename button OK to Save and have it display a newly inserted record in "preview" mode that will lead to some fictitious step 2. A new button Save and New will create a new record and switch to the "new" mode right after a new record is created.

Open ~/Controllers/Products.aspx and change action group with scope "Form" as shown below.

<actionGroup scope="Form">
    <!-- modified actions -->
    <action whenLastCommandName="Select" whenLastCommandArgument="editForm1" 
            commandName="Edit" />
    <action whenLastCommandName="Select" whenLastCommandArgument="editForm1" 
            commandName="Delete" confirmation="Delete?" />
    <action whenLastCommandName="Select" whenLastCommandArgument="editForm1" 
            commandName="Cancel" headerText="Close" />
    <!-- no changes -->
    <action whenLastCommandName="Edit" commandName="Update" headerText="OK" />
    <action whenLastCommandName="Edit" commandName="Delete" confirmation="Delete?" />
    <action whenLastCommandName="Edit" commandName="Cancel" />
    <!-- modified actions -->
    <action whenLastCommandName="New" commandName="Insert" commandArgument="ShowProduct" 
            headerText="Save" />
    <action whenLastCommandName="New" commandName="Insert" commandArgument="SaveAndNew" 
            headerText="Save and New"/>
    <action whenLastCommandName="New" commandName="New" commandArgument="createForm1" 
            headerText="Cancel" confirmation="Do you want to reset this form?"/>
    <action whenLastCommandName="Insert" whenLastCommandArgument="ShowProduct" 
            commandName="Select" commandArgument="createForm1"/>
    <action whenLastCommandName="Insert" whenLastCommandArgument="SaveAndNew" 
            commandName="New" commandArgument="createForm1"/>
    <action whenLastCommandName="Select" whenLastCommandArgument="createForm1" 
            commandName="Navigate" commandArgument="~/Default.aspx?ProductID={ProductID}" 
            headerText="Continue to Step 2"/>
    <action whenLastCommandName="Select" whenLastCommandArgument="createForm1" 
            commandName="Cancel"/>
</actionGroup>

You probably noticed that new attribute whenLastCommandArgument is used to filter actions and complements attribute whenLastCommandName.

Action Insert creates a new record and in a case of success will try to select an action that is matched to the last command and argument. Pairs command/argument with values Insert/ShowProduct and Insert/SaveAndNew will trigger automatic execution of commands Select/createForm1 and New/createForm1.

Server-side actions Insert, Update, Delete, and any custom actions are forcing the state machine to execute if command has been successfully completed and server code did not request redirect or client script execution.

Here is how the screen looks when you refresh ~/NewProduct.aspx.

image

Enter details of a new product and click Save button. Product review form is presented.

image

If you click on Continue to Step 2 then you will be redirected to a URL that looks similar to the one below:

http://localhost:50689/NewFeatures/Default.aspx?ProductID=140

Hiding View Selector

The "orange" view selector at the top may spoil your view workflow ideas. Modify the content Content1 as shown below:

<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="Server">
<style type="text/css">
    table.ViewSelector {
        display:none;
    }
</style>

The view selector will be hidden from users.

image