User Interface

Labels
AJAX(112) Apple(1) Application Builder(244) Application Factory(207) ASP.NET(95) ASP.NET 3.5(45) ASP.NET Code Generator(72) ASP.NET Membership(28) Azure(18) 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(11) 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) DotNetNuke(12) EASE(20) Email(6) Features(99) Firebird(1) Form Builder(14) Globalization and Localization(6) Hypermedia(2) Installation(4) 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(15) OAuth(6) OAuth Scopes(1) OAuth2(9) Offline(19) Offline Apps(4) Offline Sync(4) Oracle(10) PKCE(2) PostgreSQL(2) PWA(2) QR codes(2) Rapid Application Development(5) Reading Pane(2) Release Notes(166) Reports(48) REST(28) RESTful(25) RESTful Workshop(15) RFID tags(1) SaaS(7) Security(75) SharePoint(12) SPA(6) SQL Anywhere(3) SQL Server(26) Stored Procedure(4) Teamwork(15) Tips and Tricks(83) Tools for Excel(2) Touch UI(93) Transactions(5) Tutorials(183) Universal Windows Platform(3) User Interface(332) 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
Sunday, October 16, 2022PrintSubscribe
PWA, Device Authorization Grant, Many-To-Many Input

Progressive Web Apps

Code On Time release 8.9.25.0 produces Progressive Web Apps. Support of PWA specification makes an app installable on Android, Chrome OS, Mac OS, and Windows directory from the supported browsers. Deployed apps are ready to be packaged and published to all major app stores. Each app now has its own “beating heart” - the service worker. This script is installed by browsers when the app is loaded. The installed worker will cache the app resources using the “cache busting” technique supported in the server-side framework. A tiny change to a script, stylesheet, or font will cause the cache to refresh. This ensures the correct set of client files both in the online and offline modes.

Applications created with Code On Time will greet users with the prompt to install the app and experience the native mode. Deployed apps are easy to package and publish to app stores.
Applications created with Code On Time will greet users with the prompt to install the app and experience the native mode. Deployed apps are easy to package and publish to app stores.

Learn how to preview and debug an installed application.

OAuth Device Authorization Grant

Device Authorization Grant flow further extends the opportunity to find new ways to integrate applications created with Code On Time in the everyday life of their users. RESTful API Engine turns an application into a powerful backend for custom clients. Built-in Device Authorization flow will future-proof the app by making it possible to integrate the application with the smart devices.

"Cool Gadget" sample demonstrates the Device Authorization Grant flow available in the applications created with Code On Time. Users can safely authorize the 3rd party devices to access the user data and identity information.
"Cool Gadget" sample demonstrates the Device Authorization Grant flow available in the applications created with Code On Time. Users can safely authorize the 3rd party devices to access the user data and identity information.

Many-To-May Data Input And Search

Tagging is the populate user interface feature that allows setting up the many-to-many data relationships between data records in the database with the minimalistic input. This feature has been a part of the framework in the foundation of apps created with Code On Time for many years. The new release finally makes it possible to filter and search the many-to-many fields, which is not as simple as it may seem at the first glance.

Many-to-many data input is the standard features of the apps created with Code On Time. Users can also filter and search data records by entering multiple tags that must be linked to the items in the output.
Many-to-many data input is the standard features of the apps created with Code On Time. Users can also filter and search data records by entering multiple tags that must be linked to the items in the output.

Release Notes

The following features, bug fixes, and enhancements are included in the release:

  • (PWA) Apps now installable.
  • (OAuth2) Device Authorization Grant flow is now supported.
  • (Framework) Many-to-Many support is fully implemented.It is now possible to filter and search the many-to-many fields.
  • (Framework) Requests with the XML content type will ignore the whitespace and DTD links. The latter eliminates the possibility of a "blind" XML External Entity (XXE) Injection.
  • (Touch UI) A click on the checkbox in the grid will execute an update of the row with all field values of the row. Previously only the primary key and the toggled field were included. The new behavior ensures that the business rules on the server will have a consistent set of field values to work with.
  • (Reports) Action ReportAsExcel will produce the output in OPENXML format. The file extension is *.xlsx.
  • (Reports) Action ReportAsWord will produce the output in OPENXML format. The file extension is *.docx.
  • (Framework) YamlDotNet 11.2.1 is included in the library.
  • (Framework) Newtonsoft.JSON 13.0.1 is included in the library.
  • (Touch UI) Position of the context menu on the toolbar is shifted by 4px away from the edge of the screen.
  • (Touch UI) The default display density is derived from the physical screen size.
  • (Touch UI) Input focus will activate the tab containing the focused field even if it was not in the "edit" mode prior to the focus.
  • (Touch UI) Progress indicator has the "accent" color. Previously it was always blue.
  • (Touch UI) Theme selector switches UI to the "busy" mode during the theme preview.
  • (Touch UI) New "Reset" option is available in Settings|Theme menu. The option is available if the user has selected a theme in the past.
  • (Touch UI) Updated icons for "grid" and "cards" view styles.
  • (Touch UI) Top 5/10 options in the advanced search do not raise exceptions. The list of top 5/10 lookups is presented to the user for selection.
  • (Touch UI) Event handler context.app can inspect context.isMenu property to determine if the context options will be displayed in the context menu in response to the interactive request by the user.
  • (Touch UI) Labels with the blank header are marked with data-hide-when="wrap" attribute. The physical presence of the label allows adaptive hiding of the label when the content is wrapped.
  • (Touch UI) Login prompt displays the "Login" next to the icon on the toolbar when the form is displayed in fullscreen mode.
  • (Touch UI) New theme processing compatible with PWA mode.
  • (Framework) Account Manager persists the fixed number of user properties (name,email, access_token,refresh_token,claims). The user avatars are served in JPEG format.
  • (Framework) The new class AppResouceManager performs production of cacheable client resources.
  • (Framework) Method TextUtility.ToMD5Hash(string) will produce a hash of the string parameter.
  • (PWA) Method $app.getScript uses the app manifest information to download the dynamic scripts.
  • (RESTful) Fixed the bug in processing of PATCH requests.
  • (Touch UI) Click on the icon of the ui-disabled item will not trigger the "vclick" on the item.
  • (PWA) Method UserHomePageUrl() determines the 'start_url' in the manifest.
  • (Framework) Method DataCacheItem.SerializeRequest allows custom serialization of the request object. Override the method to use additional request data such as the request domain.
  • (Framework) Details are synced even when the external filter is not defined on the child data view.
  • (RESTful) Enhanced validation of "sort" parameter.
  • (RESTful) ASP.NET session is removed when the embedding engine is resolving the links.
  • (RESTful) Embedding engine calculates resource tags for faster embedding of the object resource. The links of previously fetched resources are used in place of the duplicates.
  • (RESTful) Embedding will timeout at 60 seconds or when the execution exceeds the option server.rest.timeout specified in touch-setting.json configuration file.
  • (RESTful) Robust refreshing of the page with the ".oauth2" cookie set upon receiving the authorization URL request. Simple 302 with Response.Redirect will not set the cookie on the domains other than localhost when running in Microsoft Azure.
  • (RESTful) Virtual on-demand fields specified in the body are ignored.
  • (RESTful) The "Detailed" HTTP error mode is enabled to allow RESTful error reporting in the apps hosted in Microsoft Azure.
  • (Runtime) Content types application/json and application/x-yaml are added to the mime map in web.config. This will ensure property recognition of JSON and Yaml file types on hosting services such as Microsoft Azure.
  • (RESTful) Configuration file web.config removes status codes 400, 401, 403, 404, 412, 4122, and 500 from HTTP error reporting delegated to the host web server. This allows custom JSON responses from the RESTful API of the apps deployed to Microsoft Azure and other hosting platforms.
  • Cloud On Time app for iOS, Mac, and Windows is retired and replaced with the PWA support in the generated apps. Applications are installable from the browser on the supported platforms. Deployed applications are ready to be packaged and published to the app stores.

Coming Soon

Offline Sync

The all-new Offline Sync Add-On is coming next. It will be compatible with the Progressive Web Apps support in the framework. The licensing will change as follows:

  • Perpetual use with 12 months of maintenance updates
  • Sold is multiples of ten users
  • Bound to the domain name and “localhost”
  • New lower pricing

Online apps that do not require the offline/disconnected data will benefit from the enhanced ability to operate when there is no network. If the app is installed on the device from the browser or as the native app, then the pages will be pre-loaded in the local cache for the improved user experience.

Live Project Designer

The next implementation target is the Live Project Designer described in the Roadmap. We are running behind our original schedule but the goal remains the same - make the app development process with Code On Time intuit and simple.

Tuesday, January 25, 2022PrintSubscribe
Embedded SPA3 (Custom UI) with RESTful Hypermedia

This is the third embedded Single Page Application in the RESTful Workshop series. The code is virtually identical to the first app that was created in the Embedded SPA1 with RESTful Hypermedia tutorial. This app has a custom user interface but remains embedded in the host backend application.

Single Page Application (SPA) with custom user interface embedded in the application with the RESTful API Engine created with Code On Time.
Single Page Application (SPA) with custom user interface embedded in the application with the RESTful API Engine created with Code On Time.

Developers may exercise full control over the layout and implementation of specific pages in applications created with Code On Time.

See the live page at https://demo.codeontime.com/pages/spa3.

Bring Your Own UI

Enter the following as the content of the ~/app/pages/spa3.html page. Use your favorite text editor to make changes.

HTML
123456789101112131415161718192021222324252627282930313233<!DOCTYPE html>
<html lang="en">
<head>
    <title>Embedded SPA3 (Custom UI) with RESTful Hypermedia</title>
    <style>
        #authenticated {
            max-width: 640px;
            min-width: 480px;
            margin: 0 auto 0 auto;
            line-height: 34px;
        }

        .toolbar {
            height: auto;
            display: flex;
            border: solid 1px #ddd;
            padding: .25em;
            margin: .5em 0;
        }

        button[data-hypermedia] {
            margin-right: 1em !important;
        }
    </style>
</head>
<body data-ui-framework="none">
    <h1 style="text-align:center">Embedded SPA3 (Custom UI) with RESTful Hypermedia</h1>
    <div id="authenticated">
        <p>This is the list of products:</p>
        <div id="product-list"></div>
    </div>
</body>
</html>

The attribute data-ui-framework=”none” applied to the body element instructs the host application to strip the presentation framework scripts from the page markup served to browsers. The core API scripts are still injected into the page, which makes it possible to take advantage of the security system of the host application. The code in the page can also take advantage of the $app.restful method and use other framework utilities.

Enter the following script block directly before the closing body tag in the page markup.

HTML
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980<script>
    (function () {
        var apiHypermedia,
            products;

        $(document)
            .ready(e => {
                $app.restful()
                    .then(result => {
                        apiHypermedia = result;
                        start();
                    })
                    .catch(restfulException);
            }).on('click', '[data-hypermedia]', handleHypermedia);

        function start() {
            // show the data if the API allows it
            if (apiHypermedia && apiHypermedia.products)
                refreshProductList(apiHypermedia.products._links.first);
            else
                document.querySelector('#product-list').textContent = 'Unauthorized to see the products';
        }

        /* product list rendering and paging */

        function refreshProductList(hypermedia) {
            $app.restful({ "url": hypermedia })
                .then(result => {
                    products = result;
                    renderProductData();
                })
                .catch(restfulException);
        }

        function renderProductData() {
            var sb = [
                '<table border="1" cellpadding="5" cellspacing="0">',
                '<tr><th>Product</th><th>Category</th><th>Supplier</th><th>Unit Price</th><th>Units In Stock</th></tr>'];
            for (var i = 0; i < products.collection.length; i++) {
                var p = products.collection[i];
                sb.push('<tr>');
                sb.push('<td>', $app.htmlEncode(p.productName), '</td>');
                sb.push('<td>', $app.htmlEncode(p.categoryName), '</td>');
                sb.push('<td>', $app.htmlEncode(p.supplierCompanyName), '</td>');
                sb.push('<td>', $app.htmlEncode(p.unitPrice), '</td>');
                sb.push('<td>', $app.htmlEncode(p.unitsInStock), '</td>');
                sb.push('</tr>')
            }
            sb.push('</table>');
            sb.push('<div class="toolbar">')
            var hypermedia = products._links;
            sb.push('<button data-hypermedia="self"', hypermedia.self ? '' : ' disabled="disabled"', '>Refresh</button>');
            sb.push('<button data-hypermedia="first"', hypermedia.first ? '' : ' disabled="disabled"', '>First</button>');
            sb.push('<button data-hypermedia="prev"', hypermedia.prev ? '' : ' disabled="disabled"', '>Prev</button>');
            sb.push('<button data-hypermedia="next"', hypermedia.next ? '' : ' disabled="disabled"', '>Next</button>');
            sb.push('<button data-hypermedia="last"', hypermedia.last ? '' : ' disabled="disabled"', '>Last</button>');
            var hypermediaArgs = $app.urlArgs(products._links.self);
            sb.push('Page: ', parseInt(hypermediaArgs.page) + 1, ' of ', Math.ceil(products.count / hypermediaArgs.limit),
                ' (', + products.count + ' items)');
            sb.push('</div>');
            document.querySelector('#product-list').innerHTML = sb.join('');
            $app.touch.scrollable('refresh'); // let the Touch UI know that the contents of the page have changed
        }

        function handleHypermedia(e) {
            var btn = e.target.closest('[data-hypermedia]')
            if (btn && products) {
                var hypermedia = btn.getAttribute('data-hypermedia');
                refreshProductList(products._links[hypermedia]);
            }
        }

        /* miscellaneous utilities */

        function restfulException(ex) {
            if (ex && ex.errors)
                $app.alert(ex.errors[0].reason + ': ' + ex.errors[0].message)
        }
    })();
</script>

The difference between this implementation and SPA1 is in the event handling only. Touch UI is not available - there are no pagereadycomplete.app or vclick events. SPA3 begins execution in the ready handler triggered on the document and responds to the click event triggered on the buttons in the pager toolbar.

JavaScript
123456789$(document)
    .ready(e => {
        $app.restful()
            .then(result => {
                apiHypermedia = result;
                start();
            })
            .catch(restfulException);
    }).on('click', '[data-hypermedia]', handleHypermedia);

If Touch UI is enabled, then the SPA must wait for the pagereadycomplete.app event to begin execution. This will ensure that the app is gracefully blending in. Here is how the app starts in the SPA1 tutorial.

JavaScript
123456789$(document)
    .one('pagereadycomplete.app', e => {
        $app.restful()
            .then(result => {
                apiHypermedia = result;
                start();
            })
            .catch(restfulException);
    }).on('vclick', '[data-hypermedia]', handleHypermedia);

Everything else is exactly the same.

Final Source Code

This single page app is composed of a single physical file. The complete source of the spa3.html file is shown next.

HTML
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113<!DOCTYPE html>
<html lang="en">
<head>
    <title>Embedded SPA3 (Custom UI) with RESTful Hypermedia</title>
    <style>
        #authenticated {
            max-width: 640px;
            min-width: 480px;
            margin: 0 auto 0 auto;
            line-height: 34px;
        }

        .toolbar {
            height: auto;
            display: flex;
            border: solid 1px #ddd;
            padding: .25em;
            margin: .5em 0;
        }

        button[data-hypermedia] {
            margin-right: 1em !important;
        }
    </style>
</head>
<body data-ui-framework="none">
    <h1 style="text-align:center">Embedded SPA3 (Custom UI) with RESTful Hypermedia</h1>
    <div id="authenticated">
        <p>This is the list of products:</p>
        <div id="product-list"></div>
    </div>
    <script>
        (function () {
            var apiHypermedia,
                products;

            $(document)
                .ready(e => {
                    $app.restful()
                        .then(result => {
                            apiHypermedia = result;
                            start();
                        })
                        .catch(restfulException);
                }).on('click', '[data-hypermedia]', handleHypermedia);

            function start() {
                // show the data if the API allows it
                if (apiHypermedia && apiHypermedia.products)
                    refreshProductList(apiHypermedia.products._links.first);
                else
                    document.querySelector('#product-list').textContent = 'Unauthorized to see the products';
            }

            /* product list rendering and paging */

            function refreshProductList(hypermedia) {
                $app.restful({ "url": hypermedia })
                    .then(result => {
                        products = result;
                        renderProductData();
                    })
                    .catch(restfulException);
            }

            function renderProductData() {
                var sb = [
                    '<table border="1" cellpadding="5" cellspacing="0">',
                    '<tr><th>Product</th><th>Category</th><th>Supplier</th><th>Unit Price</th><th>Units In Stock</th></tr>'];
                for (var i = 0; i < products.collection.length; i++) {
                    var p = products.collection[i];
                    sb.push('<tr>');
                    sb.push('<td>', $app.htmlEncode(p.productName), '</td>');
                    sb.push('<td>', $app.htmlEncode(p.categoryName), '</td>');
                    sb.push('<td>', $app.htmlEncode(p.supplierCompanyName), '</td>');
                    sb.push('<td>', $app.htmlEncode(p.unitPrice), '</td>');
                    sb.push('<td>', $app.htmlEncode(p.unitsInStock), '</td>');
                    sb.push('</tr>')
                }
                sb.push('</table>');
                sb.push('<div class="toolbar">')
                var hypermedia = products._links;
                sb.push('<button data-hypermedia="self"', hypermedia.self ? '' : ' disabled="disabled"', '>Refresh</button>');
                sb.push('<button data-hypermedia="first"', hypermedia.first ? '' : ' disabled="disabled"', '>First</button>');
                sb.push('<button data-hypermedia="prev"', hypermedia.prev ? '' : ' disabled="disabled"', '>Prev</button>');
                sb.push('<button data-hypermedia="next"', hypermedia.next ? '' : ' disabled="disabled"', '>Next</button>');
                sb.push('<button data-hypermedia="last"', hypermedia.last ? '' : ' disabled="disabled"', '>Last</button>');
                var hypermediaArgs = $app.urlArgs(products._links.self);
                sb.push('Page: ', parseInt(hypermediaArgs.page) + 1, ' of ', Math.ceil(products.count / hypermediaArgs.limit),
                    ' (', + products.count + ' items)');
                sb.push('</div>');
                document.querySelector('#product-list').innerHTML = sb.join('');
                $app.touch.scrollable('refresh'); // let the Touch UI know that the contents of the page have changed
            }

            function handleHypermedia(e) {
                var btn = e.target.closest('[data-hypermedia]')
                if (btn && products) {
                    var hypermedia = btn.getAttribute('data-hypermedia');
                    refreshProductList(products._links[hypermedia]);
                }
            }

            /* miscellaneous utilities */

            function restfulException(ex) {
                if (ex && ex.errors)
                    $app.alert(ex.errors[0].reason + ': ' + ex.errors[0].message)
            }
        })();
    </script>
</body>
</html>

Next

Continue to the Standalone SPA4 with RESTful Hypermedia and OAuth 2.0 segment. You will learn how to create a single page app with a custom user interface that can be hosted on any domain. The app will use OAuth 2.0 Authorization Code Flow with PKCE to authenticate users.

Sunday, October 3, 2021PrintSubscribe
"Actions" Lookup Style

 Touch UI was designed to be responsive and functional on a device with any screen size. Forms are presented in modal popups on large screens or in the fullscreen mode on smaller devices. Actions are automatically rendered in the system toolbar bar, in the form button bar, or as a floating button. There is the universal “more” menu providing users with access to all actions available in the current context. 

Developers can download HTML templates of live forms and tinker with them by moving fields around and injecting custom actions in the desired position. Touch UI provides a simple method of injecting custom actions in the desired position without a custom form template. The screenshot below demonstrates two sets of actions mixed in the flow of the data fields in the form on the iPad-size screen.


Here is another example of the same form in the Pixel phone preview mode in the app created with Code On Time.


Begin by adding two action groups with the “Custom” scope to the Employees data controller. Place actions with the Custom command name and corresponding Argument and Header Text into each group. Make sure to specify the When Last Command Name property for the actions. Our example has this property set to Any to ensure that the actions are visible in any mode regardless of the last executed action.


Action group IDs are set to ag100 and ag101 by the Project Designer. Right-click an action group in the Project Explorer and choose Rename if you prefer the custom identifiers.

Now create two fields in the same data controller with the matching fields names. Set their item Style to Actions. The framework will treat these fields as virtual and therefore they will not have any impact on the standard data manipulation actions or custom actions.


Drag the fields into the desired position in the data field flow of the editForm1 view. 


The framework will render the actions as specified. These actions will be visible only if all conditions specified in their “When …” properties are met. If all actions are invisible, then an empty space will be in their place instead. Use the Visible When property of the data field to hide the empty space when such conditions do occur.

We had attempted previously to come up with a way to mix the data fields and actions without having to build a custom template. Our last attempt succeeded when we were solving the problem of making it obvious to users that the Get Verification Code button in the 2-Factor Authentication form must be pressed in order to get the code via chosen delivery method.


The data field with the Actions style in the form above is also tagged as merge-with-previous to eliminate the line separating the “Actions” lookup from the previous data field.

If you were to move the actions from the Custom group ag100 to the Form group ag2, then the form would look like this. Custom actions Meet, Promote, and Happy Birthday are rendered in the form button bar. 


This is how the corresponding configuration of the data controller may look in the Project Explorer.


Arguably keeping the ag100 data field in the form flow right next to the Last Name, First Name, Hire Date, and Birth Date is a better user experience than placing the actions into the form button bar.