Business Rules/Logic

Labels
AJAX(112) App Studio(7) 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(177) 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(183) 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(2) 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
Business Rules/Logic
Monday, October 8, 2012PrintSubscribe
Extending a Web App with jQuery CRUD

The Product Browser example explains how to create a user control that takes advantage of the application server built in a web app created with Code On Time.  The scope of this example is to demonstrate reading the app data with REST requests with a custom JavaScript. The example also uses jQuery library integrated in the generated web apps.

Most custom user interface extensions will need to update, insert, and delete data.  Create, Read, Update, and Delete operations are also known as CRUD.  A complete example of a standalone Product Catalog Manager is implementing a Cross-Domain CRUD.

If a custom user control is created in a web app, then there is no need for cross-domain AJAX requests. Standard JSON requests can be executed with explicitly defined HTTP methods POST, GET, PUT, and DELETE to indicate the purpose of an AJAX web request.

Enabling Access to Products, Suppliers, and Categories

Three data controllers are required to implement the Product Catalog Manager. The data controllers Products, Categories, and Suppliers must explicitly allow REST requests. The application server will deny any web requests if data controllers are not configured accordingly.

Select Products, Suppliers, and Categories data controllers in Project Designer and enter the following in its Representational State Transfer (REST) Configuration property for each controller:

Property Value
Representation State Transfer (REST) Configuration

Uri: .
Users: *

Creating a User Control

Create a custom user control following instructions from the Product Browser example and replace the markup of a the user control with the example from the Source Code section below.

Browse to the page of a running web app that contains the user control. You will see the following:

A CRUD-enabled Product Catalog Manager implemented as a User Control in a web app created with Code On Time

Note that if you are accessing the page without singing in, then you will see a browser prompt for user name and password. The static link to the list of suppliers will force the application server to issue an identity challenge.

<script type="text/javascript" 
    src="../appservices/Suppliers?_sortExpression=CompanyName&_instance=SupplierData">
</script>

If you do not wish to have a static link to suppliers then dynamically populate SupplierID list following the pattern shown in the implementation of loadCategories method.

loadCategories: function () {
    $.ajax({
        url: this.basePath() + '/Categories?_sortExpression=CategoryName',
        dataType: 'json',
        cache: false,
        success: function (data) {
            $.each(data.Categories, function (index, category) {
                $('<option>').text(category.CategoryName)
                .attr('value', category.CategoryID).appendTo($('#CategoryID'));
            })
        }
    });
}

Try updating, inserting, and deleting data.

A new product is created by issuing an PUT request to the application server of a web app.

A new product has been created by issuing an PUT request to the application server of a web app created with Code On Time

An existing product is deleted by issuing a DELETE request to the application server of a web app.

An existing product is being deleted by issuing a DELETE request to the application server of a web app created with Code On Time

Source Code

The ajax requests are using a relative path to access the application server. Method ProductManager.basePath() return the path.

Notice the use of “json” in the “dataType” parameter of ajax requests. Also the nature of the CRUD operations is explicitly expressed by the “type” of the requests.

<%@ Control AutoEventWireup="true" %>
<!-- this tag is needed to enable jQuery IntelliSense only -->
<script src="../Scripts/_System.js" type="text/javascript"></script>
<!-- the user interface of the control -->
<script type="text/javascript" 
    src="../appservices/Suppliers?_sortExpression=CompanyName&_instance=SupplierData">
</script>
<script type="text/javascript">
var ProductManager = {
    // Returns the url of the application server of a demo web app.
    basePath: function () { return '../appservices'; },
    // Loads the list of categories requested from the app server via ajax request.
    loadCategories: function () {
        $.ajax({
            url: this.basePath() + '/Categories?_sortExpression=CategoryName',
            dataType: 'json',
            cache: false,
            success: function (data) {
                $.each(data.Categories, function (index, category) {
                    $('<option>').text(category.CategoryName)
                    .attr('value', category.CategoryID).appendTo($('#CategoryID'));
                })
            }
        });
    },
    // Populate suppliers from the list retrieved by the second <script> element.
    loadSuppliers: function () {
        $.each(MyCompany.SupplierData.Suppliers, function (index, supplier) {
            $('<option>').text(supplier.CompanyName)
                .attr('value', supplier.SupplierID).appendTo($('#SupplierID'));
        })
    },
    // Shows a list of products in <select> element
    showProductList: function () {
        var query = '?_sortExpression=ProductName&_q=' + encodeURIComponent($('#QuickFind').val());
        $.ajax({
            url: this.basePath() + '/Products' + query,
            cache: false,
            dataType: 'json',
            success: function (data) {
                var selectedValue = $('#ProductList').val();
                $('#ProductList option').remove();
                $.each(data.Products, function (index, product) {
                    $('<option>')
                        .text(product.ProductName + ' / ' + product.CategoryCategoryName)
                        .attr('value', product.ProductID).appendTo($('#ProductList'));
                });
                $('#ProductList').val(selectedValue);
                $('#ProductListPanel').show();
            }
        });
    },
    // Shows a form with product details,
    showProductDetails: function (productId) {
        if (productId == null) return;
        $('#ProductSearchPanel').hide();
        $('#DeleteButton').show();
        $.ajax({
            url: this.basePath() + '/Products/editForm1/' + productId,
            cache: false,
            dataType: 'json',
            success: function (product) {
                $('#ProductDetailsPanel').show();
                $('#ProductID').attr('value', product.ProductID);
                $('#ProductName').attr('value', product.ProductName).focus();
                $('#SupplierID').attr('value', product.SupplierID);
                $('#CategoryID').attr('value', product.CategoryID);
                $('#QuantityPerUnit').attr('value', product.QuantityPerUnit);
                $('#UnitPrice').attr('value', product.UnitPrice);
            }
        });
    },
    // Returns back to the search mode.
    backToSearch: function () {
        $('#ProductDetailsPanel').hide();
        $('#ProductSearchPanel').show();
        $('#ProductList').focus();
    },
    // Refreshes the search result.
    refreshSearch: function () {
        this.backToSearch();
        this.showProductList();
    },
    // Formats a URI of a product
    createProductUrl: function (requestType) {
        if (requestType == 'POST')
            return this.basePath() + '/Products/createForm1';
        return this.basePath() + '/Products/editForm1/' + encodeURIComponent($('#ProductID').val());
    },
    // Creates an object with the properties retrievd from the input fields 
    // of the "ProductDetails" form.
    collectFieldValues: function () {
        return {
            ProductID: $('#ProductID').val(),
            ProductName: $('#ProductName').val(),
            SupplierID: $('#SupplierID').val(),
            CategoryID: $('#CategoryID').val(),
            QuantityPerUnit: $('#QuantityPerUnit').val(),
            UnitPrice: $('#UnitPrice').val()
        };
    },
    // Deletes a products.
    deleteProduct: function () {
        if (!confirm('Delete?')) return;
        $.ajax({
            url: this.createProductUrl('DELETE'),
            dataType: 'json',
            type: 'DELETE',
            data: ProductManager.collectFieldValues(),
            success: function (result) {
                if (result.errors)
                    alert(result.errors[0].message);
                else
                    ProductManager.refreshSearch();
            }
        });
    },
    // Shows the product form with blank values.
    newProduct: function () {
        $('#ProductSearchPanel').hide();
        $('#DeleteButton').hide();
        $('#ProductDetailsPanel').show();
        $('#ProductID').attr('value', null);
        $('#ProductName').attr('value', 'New Product').focus().select();
        $('#SupplierID').attr('value', null);
        $('#CategoryID').attr('value', null);
        $('#QuantityPerUnit').attr('value', null);
        $('#UnitPrice').attr('value', null);
    },
    // Saves a product. If there is no value in the #ProductID hidden field then
    // a new product is created by "POST" request. Otherwise an existing product
    // is updated with "PUT" request.
    saveProduct: function () {
        if (!confirm('Save?')) return
        var requestType = $('#ProductID').val() != '' ? 'PUT' : 'POST';
        $.ajax({
            url: this.createProductUrl(requestType),
            dataType: 'json',
            type: requestType,
            data: ProductManager.collectFieldValues(),
            success: function (result) {
                if (result.errors)
                    alert(result.errors[0].message);
                else {
                    if (requestType == 'POST')
                        alert('ID of the new product is ' + result.ProductID);
                    ProductManager.refreshSearch();
                }
            }
        });
    },
};
$(document).ready(function () {
    // pre-populate the drop down lists of categories and suppliers
    ProductManager.loadCategories();
    ProductManager.loadSuppliers();
    // attach event handlers to buttons
    $('#ProductListPanel,#ProductDetailsPanel').hide();
    $('#FindButton').click(function (e) {
        e.preventDefault();
        ProductManager.showProductList();
    });
    $('#ShowDetailsButton').click(function (e) {
        e.preventDefault();
        ProductManager.showProductDetails($('#ProductList').val());
    });
    $('#BackToSearchButton').click(function (e) {
        e.preventDefault();
        ProductManager.backToSearch();
    });
    $('#NewButton').click(function (e) {
        e.preventDefault();
        ProductManager.newProduct();
    });
    $('#SaveButton').click(function (e) {
        e.preventDefault();
        ProductManager.saveProduct();
    });
    $('#DeleteButton').click(function (e) {
        e.preventDefault();
        ProductManager.deleteProduct();
    });
    $('#QuickFind').focus();
});
</script>
<style type="text/css">
    body, button, input, select
    {
        font-family: Tahoma;
        font-size: 8.5pt;
    }
   
    #QuickFind
    {
        width: 280px;
    }
    
    #ProductList
    {
        width: 350px;
        height: 208px;
        margin-bottom: 4px;
    }
    
    #FindButton
    {
        width: 60px;
    }
    
    #ProductListPanel
    {
        margin-top: 4px;
    }
    
    .Field
    {
        padding-bottom: 4px;
    }
    
    #ProductDetailsPanel
    {
        margin-top: 4px;
        padding: 8px;
        border: solid 1px silver;
        display: inline-block;
    }
    
    .Field label
    {
        display: block;
        color: green;
    }
    
    .Field input
    {
        width: 300px;
    }
</style>
<div>
    <div id="ProductSearchPanel">
        <input id="QuickFind" type="text" />
        <button id="FindButton">
            Find</button>
        <div id="ProductListPanel">
            <select id="ProductList" size="5">
            </select>
            <div>
                <button id="ShowDetailsButton">
                    Show Details</button>
                <button id="NewButton">
                    New Product</button>
            </div>
        </div>
    </div>
    <div id="ProductDetailsPanel">
        <input id="ProductID" type="hidden" />
        <div class="Field">
            <label for="ProductName">
                Product Name:</label>
            <input id="ProductName" />
        </div>
        <div class="Field">
            <label for="SupplierID">
                Supplier Company Name:</label>
            <select id="SupplierID">
            </select>
        </div>
        <div class="Field">
            <label for="CategoryID">
                Category Name:</label>
            <select id="CategoryID">
            </select>
        </div>
        <div class="Field">
            <label for="QuantityPerUnit">
                Quantity Per Unit:</label>
            <input id="QuantityPerUnit" />
        </div>
        <div class="Field">
            <label for="UnitPrice">
                Unit Price:</label>
            <input id="UnitPrice" />
        </div>
        <div>
            <button id="BackToSearchButton">
                Back To Search</button>
            <button id="SaveButton">
                Save</button>
            <button id="DeleteButton">
                Delete</button>
        </div>
    </div>
</div>
Saturday, September 29, 2012PrintSubscribe
“Select” Action

The application framework initiates a Select action when a data view needs to load data from the server.

Let’s create an example using a Northwind sample web app to see when the Select action is fired in the server code.

Start the Project Designer. In the Project Explorer, switch to the Controllers tab and right-click on Orders / Business Rules node. Press New Business Rule.

New Business Rule context menu option in the Project Explorer.

Give this rule the following properties:

Property Value
Type SQL
Command Name Select
Phase Before
Script
set @Result_ShowAlert = 'Before'

Press OK to save the business rule. Right-click Orders / Business Rules and press New Business Rule again.

New Business Rule context menu option in the Project Explorer.

Assign these properties:

Property Value
Type SQL
Command Name Select
Phase Execute
Script
set @Result_ShowAlert = @CustomerCompanyName

Press OK to save. Create one more business rule with this configuration:

Property Value
Type SQL
Command Name Select
Phase After
Script
set @Result_ShowAlert = 'After'

Press OK to save. The Business Rules node should look like the picture below.

image

Assigning a value to @Result_ShowAlert creates a JavaScript expression that is added to the server response. As a page of data is rendered, the “alert” expressions will be consequently appended to the ClientScript property of the Result.

The client library will evaluate the entire ClientScript property content using eval function of JavaScript language. This will result in the sequence of alerts displayed to the user.

On the toolbar, press Browse.

Navigate to the Orders page. The first popup for Before phase will appear.

Alert showing 'Before' appears before the select command occurs.

Press OK, and alert for Execute phase will appear, showing the Customer Company Name of the first record.

Alert displaying the Customer Company Name of the data being selected.

Keep pressing OK, as each alert will display the Customer Company Name of each record displayed in the grid. After alerts for each selected data row have been displayed, the After phase popup will appear.

Alert displaying 'After' when the select command has been completed.

Click on a Customer Company Name link. The three alerts will appear in succession as well.

Alert displaying 'Before' when user navigates to the form view of an order.

When you press Edit in the form, no alerts will be shown – this is because Edit command only renders the form on the client without making a round trip to the server. The data has already been retrieved from the server when select command was fired.

No alert is displayed when the form enters edit mode - Edit command does not travel to the server.

Saturday, September 29, 2012PrintSubscribe
Passing Values to Custom Report Action

Many action require additional information that must be requested from the user. The Implementing a Custom Action tutorial in Getting Started series shows an example of such action.

The built-in Report action allows implementing custom report output created on the server. Developers can use external software or write code to produce the output.

Let’s consider requesting custom parameters and processing them when producing the output. The example assumes that there is an external URL that can accept ReportHeader. The action handler will redirect user to that URL.

The action handler will obtain several parameters from the following sources

  1. Confirmation data controller
  2. Selected row of the data view
  3. URL in the address bar of the browser

Confirmation Controller Configuration

Make sure that Reporting is enabled for your Northwind sample project.

Start the Project Designer. In the Project Explorer, switch to the Controllers tab and press New Controller icon on the toolbar.

New Controller context menu option in the Project Explorer.

Assign a name to the controller.

Property Value
Name ReportProperties

Press OK to save the controller. Right-click on ReportProperties / Fields node, and press New Field.

New Field context menu option for Fields node of ReportProperties confirmation controller.

Give this field the following settings.

Property Value
Name ReportHeader
Type String
Length 40
Allow null values True
Label Report Header

Press OK to save the field.

Adding Custom Report Action

Right-click on Orders / Actions / ag3 – New action group node, and press New Action.

New Action context menu option on an action group for Orders controller.

Give this action the following settings.

Property Value
Command Name Report
Command Argument _blank
Confirmation _controller=ReportProperties
_title=Configure properties for the report.
_width=500

Press OK to save the action.

Handling the Action

Create a controller business rule to handle the report action.

Right-click on Orders / Business Rules node.

New Business Rule context menu option in the Project Explorer.

Assign the following properties.

Property Value
Type C# / Visual Basic
Command Name Report
Command Argument _blank
Phase Execute

Press OK to save. On the toolbar, press Generate to create the business rule file.

When finished, right-click on Orders / Business Rules / Report, _blank (Code / Execute) - r100 controller node, and press Edit Rule in Visual Studio.

Edit Rule in Visual Studio context menu option for a code business rule.

Visual Studio will load the project and display the source code file of the business rule. Replace the code with the following:

C#:

using System;
using MyCompany.Data;

namespace MyCompany.Rules
{
    public partial class OrdersBusinessRules : MyCompany.Data.BusinessRules
    {
        
        /// <summary>
        /// This method will execute in any view for an action
        /// with a command name that matches "Report" and argument that matches "_blank".
        /// </summary>
        [Rule("r100")]
        public void r100Implementation(
                    int? orderID, 
                    string customerID, 
                    string customerCompanyName, 
                    int? employeeID, 
                    string employeeLastName, 
                    DateTime? orderDate, 
                    DateTime? requiredDate, 
                    DateTime? shippedDate, 
                    int? shipVia, 
                    string shipViaCompanyName, 
                    decimal? freight, 
                    string shipName, 
                    string shipAddress, 
                    string shipCity, 
                    string shipRegion, 
                    string shipPostalCode, 
                    string shipCountry,
                    // custom arguments
                    string parameters_ReportHeader, // comes from confirmation
                    string xyz                      // comes from URL
                    )
        {
            // This is the placeholder for method implementation.
            Result.NavigateUrl =
                String.Format("~/Pages/OrdersReport.aspx?ReportHeader={0}&OrderID={1}&xyz={2}",
                    parameters_ReportHeader, orderID, xyz);

        }
    }
}

Visual Basic:

Imports MyCompany.Data
Imports System

Namespace MyCompany.Rules

    Partial Public Class OrdersBusinessRules
        Inherits MyCompany.Data.BusinessRules

        ''' <summary>
        ''' This method will execute in any view for an action
        ''' with a command name that matches "Report" and argument that matches "_blank".
        ''' </summary>
        <Rule("r100")>
        Public Sub r100Implementation( _
                    ByVal orderID As Nullable(Of Integer),
                    ByVal customerID As String,
                    ByVal customerCompanyName As String,
                    ByVal employeeID As Nullable(Of Integer),
                    ByVal employeeLastName As String,
                    ByVal orderDate As Nullable(Of DateTime),
                    ByVal requiredDate As Nullable(Of DateTime),
                    ByVal shippedDate As Nullable(Of DateTime),
                    ByVal shipVia As Nullable(Of Integer),
                    ByVal shipViaCompanyName As String,
                    ByVal freight As Nullable(Of Decimal),
                    ByVal shipName As String,
                    ByVal shipAddress As String,
                    ByVal shipCity As String,
                    ByVal shipRegion As String,
                    ByVal shipPostalCode As String,
                    ByVal shipCountry As String,
                    ByVal parameters_ReportHeader As String,
                    ByVal xyz As String
                    )
            'This is the placeholder for method implementation.            
            Result.NavigateUrl =
            String.Format("~/Pages/OrdersReport.aspx?ReportHeader={0}&orderId={1}&xyz={2}",
                          parameters_ReportHeader, orderID, xyz)

        End Sub
    End Class
End Namespace

Save the file.

Testing the Report Action

Switch back to the Project Designer. On the toolbar, press Browse.

Navigate to the Orders page. Append the URL parameter “?xyz=Value” to the current page.

Entering an 'xyz' URL parameter into the URL bar of the browser.

Press Enter to navigate to the URL. On the list of orders, highlight an order and activate the Report action on the action bar.

A row selected in the list of Orders. The Report action on the action bar is highlighted.

The confirmation controller will open. Enter a Report Header and press OK.

Confirmation controller with Report Header populated with the value 'Header1'.

The next page will display a server error – no page was created to handle the request. However, if you look at the URL, you will see that the values of fields specified in the business rule have been passed.

Values from the confirmation controller, data view, and previous URL parameters have been passed.

The values available for passing include already present URL parameters (xyz), a field value from the selected row (orderId), and the value specified by the user in a confirmation controller (reportHeader).

If no row was selected, the value from the first row in the grid will be passed.

If you do have a real application behind the URL, then one can expect the correct output to be produced.