Blog: Posts from May, 2020

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
Posts from May, 2020
Tuesday, May 12, 2020PrintSubscribe
Barcodes, QR Codes, and RFID tags
Code On Time release 8.9.5.0 introduces the framework-level support for barcode input and unique User Interface Automation technology with optional Kiosk UI. Developers can handle the values incoming from the barcode and QR code scanners and RFID readers.

UI Automation makes it possible to send commands to the user interface of apps based on Touch UI to perform complex simulations of user actions. UI Automation rules are triggered by scanned barcodes or changes in the state of the user interface. The rules are written in JavaScript and operate both in offline and online mode.

Kiosk UI is the feature of UI Automation. Developers can describe the display flow of data elements to present the alternative views of data in Touch UI. The following screenshot demonstrates the complex master-detail presentation of the Order Form that can be easily operated even by the inexperienced end users.


The comprehensive video tutorial is available on our YouTube channel. The source code developed in the tutorial can be found at https://codeontime.com/blog/2020/05/hello-barcodes-sample-code.

Part 1: Live demo. (33 minutes)

The live demonstration of barcode, QR code, and RFID processing in a demo app running in Firefox browser.  Watch this part and skip the rest if you are short on time.

Part 2:  Building Order Entry Form (1 hour 45 minutes)

Learn how to build the Order Entry Form shown in the live demo. You will learn to assign default values, calculated extended price and order total in the manner compatible with online and offline modes. We demonstrate the application working in the web browser and in “offline” mode in Cloud On Time app. Watch this segment to learn how to create apps with Code On Time (there are no barcodes - just the new “approved” method to app development)

Part 3:  Barcode Input and UI Automation (3 hours 30 minutes)

This segment starts with an explanation of barcode processing in Touch UI. Learn how to enable barcode/QR code/RFID input in the app and how to perform the low-level processing of scanned values.

The bulk of the video explains the brand new UI Automation. You will learn how to build “if this then that” rules to bring Artificial Intelligence into your app. The rules handle barcodes, QR codes, and RFID tags.

You can make your own rules to perform complex user interface activities in response to keyboard shortcuts or custom actions. UI Automation makes possible “post production” app enhancements without making changes to the core app with simple JavaScript declarations written in Prolog style. Barcode processing is just one of the capabilities of UI Automation.

The segment ends with the demonstration of barcode, QR-code, and RFID tag processing in “offline” mode.

Part 4: Kiosk UI (2 hours)

Learn to create an alternative presentation for the Order Entry Form that will work great for the inexperienced users and the users on-the-go. Kiosk provides a “remote control” for the Order Entry Form.

You will learn about Display Flow technology and Kiosk User Interface (optional feature of UI Automation). See the brief introduction of Kiosk UI from part 1 at https://www.youtube.com/watch?v=35Vh_aOuQQc&t=1474.

The segment ends with the demonstration of Kiosk-style order entry in “offline mode.

The following features and bug fixes are also included:

  • (PostgreSQL) Model Builder displays entities in alphabetical order. 
  • (PostgreSQL) Model Builder now "sees" the database views in the schema.
  • (Framework) Method dataView.sync() correctly assigned the key to the selectedKeyList.
  • (Touch UI) Method $app.touch.focus does not crash when the container is null.
  • (Touch UI) Fixed CSS rules for the icons in the modal form titles.
  • (Touch UI) Apps based on Touch UI do not perform processing of "Chart" view type.
  • (Touch UI) Projects based on Touch UI  do not have "ChartHandler" references in the web.config.
  • (Touch UI) Panels display a solid top border in native mode.
  • (ASPX) Membership manager is correctly initialized in the apps based on ASPX page model
  • (AppGen) The app generator will not report DirectoyNotFound exception while copying the Addons to the ~/bin folder of the app prior to compilation.
  • (Touch UI) Built-in tooltip does removes "script" tags from the rich html tooltips.
  • (Touch UI) Context items with "hidden:true" property are no visible in the Context Menu.
  • (Offline Sync) Partial refresh of changes client-side objects with the server data will also update the $app.syncMap. The views on the page will get refreshed when the sync is over.
  • (ODP) If the request to read data does not specify the view of the data controller then the ID of the first view is assumed to be requested.
  • (ODP) Display properties of the DataView field are correctly initialized. Previously the display properties such as Page Size were ignored.
  • (Touch UI) Method extension.headerText() is called to refresh the current command row when the view is refreshed.
  • (Touch UI) State of the checkbox is toggled in the grid view style if the field is not read-only and edit action is available.
  • (Touch UI) A correct header data is displayed in the form of automatically selected "new" record after insert even at all times. Prior to the fix the data text of the previously selected record was displayed.
  • (Data Aquarium) Calculate command errors are display as notification.
  • (Data Aquarium) Values with "p" format are converted into a "percent value" between 0 and 1.
  • (Data Aquarium) Data and time parsing is performed in the data fields "with tryParseFuzzy..." method before an attempt is made to parse the date string with Date.parse() method.
  • (Touch UI) Chrome OS and Android will use "Google Sans" fonts.
  • (Touch UI) Calendar popup has a pronounced border and displays a semitransparent background when modal.
  • (Touch UI) Chrome OS is detected and the UI will look like Android.
  • (Framework) Lookup view hides fields other than those that are specified in DataTextField and Copy map when Kiosk UI is active.
  • (Touch UI) Forms are always modal when displayed on top of Kiosk UI.
  • (Touch UI) Method $app.touch.toggleTooltip changes the tooltip of the element between "Collapse' and 'Expand'.
  • (Touch UI) The scrollable view displayed when the "See All" button is pressed is now behaving just like its originator.
  • (Touch UI) All forms are displays as modal when kiosk is active.
  • (Data Aquarium) Sync is not performed on reverse return from the form view to the the grid if there is no selection.
  • (Touch UI) The command row is assigned to the data view if it is blank even thought the primary key has not changed.
  • (Touch UI) Shortcuts can be triggered in touch devices.
  • (Touch UI) Sensitivity of touch movement is now set at 4 pixels. Previously it was set to 0.
  • (Touch UI)
  • (Touch UI) Event "themechanged.app" is triggered on the document when the app theme has changed.
  • (Touch UI) Method ResetUI will update the sticky header.
  • (Framework) Aliased and non-aliased field filter are detected in dataView.filterOf() method.
  • (Framework) Filtered by value primary keys are correctly presented in filter details in Kiosk mode.
  • (Framework)  Method dataview.sync()  will apply the primary key filter for better performance in Kiosk mode. Only one selected row will be displayed after sync
  • (Framework) Method dataView.sync() will clear the primary key fields from the filter unless sync is requested in UI automation mode.
  • (Framework) Data types without matching .NET counterpart (such as geography in MS SQL) are returned as binary strings starting with "0x" prefix.  Previously the framework has crashed.
  • (Model Builder) Data preview works correctly with the SQL data types that are not mapped directly to .NET. such as "geography" in Microsoft SQL Server.
  • (Touch UI) Method $app.touch.showAccountManager() does not fail if membership is not enabled in the app.
  • (Touch UI) Search button in modal lookup forms is moved to the left side of the modal form title. The button is hidden when the search form is opened.
  • (Framework) Refresh token cannot be used to authenticate API requests.
  • (Touch UI) Activation of inline editor in response to user typing is performed with a delay to capture the input.
  • (Touch UI) Tooltip with the error message is displayed as as the raw user input is incorrect even if the user is trying to exit the field.  This will help understanding the error messaging when entering incorrect numbers and dates.
  • (Touch UI) Activation of inline editor in response to user typing is performed with a delay to prevent conflicts with rapid input detection generated by barcodes scanners and RFID readers. It also improves the qualify of keyboard input capture prior to activation of inline editor.
  • (Touch UI) Popup calendar does not appear at random location when the user is rapidly changing focus to another field.
  • (Touch UI) Removed slight shifting of text in the drop down input in inline editing mode.
  • (Framework) External JavaScript business rules can execute before and after with optional async promises.
  • (Framework) Method $app.execute automatically figures the controller, view, data processor and other configuration properties if "from" or "in" property is specified in the options.
  • (Framework) Method $app.ifThisThenThat registers the UI automation.
  • (Touch UI)  Focus frame takes the height of the row for checkbox fields.
  • (Touch UI) Direct click on the checkbox will toggle its value in inline editing mode. The same will happen if the user will press Space key  int the cell with the checkbox.
  • (Touch UI) Inline editor advances to the next field in the row when user submits value on a mobile device. Inline editor displayed in overlay mode will continue to advance to the same field in the row.
  • (Touch UI) Delete key will clear the value of the active cell  if the field is not read-only and not required.
  • (Touch UI) Auto-focus in the form in edit mode is perfomed is following the event 'pagereadycomplete.app'. Previously focus was set prior to the event.
  • (Touch UI) Direct click on the checkbox in grid will toggle its state if the user is allowed to edit the record the field is not read-only.
  • (Touch UI) Method $app.touch.isInTransition() performs an improved verification of the transitional state of the GUI.
  • (Touch UI) Method $app.touch.touched() will return true if the element specified in the argument has been clicked directly or through the layer of other elements. By default the boundaries are inflated by 2 pixels. The optional second argument allows specifying a more precise inflation.
  • (Touch UI) Method $app.touch.resetUI() will reset the user interface after UI automation to reflect the current state of the app. Various user interface elements are not updated during automation to improve performance. The makes possible a perfect refresh of the UI upon completion of automation.
  • (Touch UI) Method $app.touch.notify will accept an array as argument. Elements of array are join into a comma-separated string .
  • (Touch UI) Event 'ifttt.app' is triggered when the app has started to enable the initial "if this then that" automation.
  • (Touch UI) Lookup items are not created if the barcode input is in progress.
  • (Touch UI) Backspace key will activate editor and clear the field value in inline editing mode.
  • (Touch UI) Inline editor always activates with the maximum length to allow editing the field value without scrolling.
  • (Touch UI) Ctrl+Enter will post he field value and stay in the input field in inline editing mode.
  • (Universal Input) Shortcuts Ctrl+Z (undo) and Ctrl+Shift+Z will undo/redo the field value in each individual input.  Typical web forms will gradually undo input values across all form fields.
  • (Touch UI) Popup calendar is  not displayed when the focus of the date input is rapidly changed.
  • (Touch UI) Improved  formatting of inputs and placeholders in inline-editing mode.
  • (Touch UI) Removed static rules used to position drop arrows in lookup/autocomplete/dropdownlist controls. This is now done programmatically for perfect positioning.
  • (Touch UI) Header  of the active column is not displayed in "bold" font anymore. The column status is emphasized through the color only.
  • (Touch UI) Lookup does not request "formatted" data anymore. Formatting has caused text values to be copied to the borrowed fields. Formatting was used when duplicates were returned in the lookup and additional values are revealed to help the user to make the correct choice.
  • (Touch UI) Method $app.touch.inlineEditing() will toggle the inline editing mode of dataview specified in the argument.
  • (Framework) Method $app.execute will execute the request after a brief delay if ODP instance will be utilized. This improves overall responsiveness o the GUI.
  • (Touch UI) Method $app.input.execute() now accepts simple "map" object argument with the values that must be assigned to the mapped field names.
  • (Classic UI) A non-blank Editor property will not break the app in Touch UI mode.
  • (Touch UI) Processing of date keyboard shortcuts is performed in the timeout instead of instant response to keydown event. This improves consistency across browsers.
  • (Touch UI) The primary dataview on the "offline" page was using the data processor instance to delete records, which has caused the change not to be committed unless the delete was performed in the child form.  Now the data processor is created only if the app is running in a true "offline" mode.
  • (Touch UI) The dedicated grid/list of items displayed for a lookup is always provided with a standard icon in the page header if a custom icon is not defined.
  • (Touch UI) Changed alignment of collapse/expand icons in the form category headers.
  • (Touch UI) The height of modal title has been increased from 32 pixels to 40 pixels.
  • (Touch UI) Ctrl+Enter does not populate the date. It simply refocused the field.
  • (Touch UI) Pressing space in the blank "date" field will enter the current date.
  • (Touch UI) Ctrl+Down|Up|Left|Right will change the current value of the date field.
  • (Universal Input) Input with focus is re-focused if its value has changed. This ensures correct display of value.
  • (Touch UI) Universal input $app.input.execute accepts field references in the values parameter specified as "field" property.
  • (Touch UI) Date/time input supports the following keyboard commands:
    • Ctrl+Enter - enter the current data/time in the empty field.
    • Ctrl+Left, Ctrl+Right - change the date by one day.
    • Ctrl+Up, Ctrl+Down - change the date by one week if the calendar is visible on by one day if the calendar is hidden.
    • Escape - close the visible calendar
    • Alt+Down, Ctrl+Space - show the calendar popup.
  • (Touch UI) Message "Loading..." is displayed in normal font (previously italic).
  • (Touch UI)  Refactored lookup autocomplete to delay execution of server-side lookup while the user is typing. Also delayed replaced of value entered by the user to prevent lost keys.
  • (Touch UI) If the current selected item has a NULL value in the "Data-Text" field then "N/A" is displayed in the summary on the sidebar.
  • (Touch UI) Numeric input correctly places cursor at the end of the value on iOS devices.
  • (Touch UI)  Popups will not restore focus after closure on touch devices.
  • (Touch UI) Lookup will not focus the input on mobile devices when the "Select" button is tapped by the user.
  • (Touch UI) Fixed alignment of "chevron/arrow button " in lookups to ensure accurate position.
  • (Native Apps) The current UI culture is used to filter out the resources required  in the native front-end. Previously the internal culture was used as a filter instead.
  • (Framework)  Lookups  based on data controllers populated with business rules ("code" and  SQL) are working correctly.
  • (Touch UI) Dates are correctly cleared when the date vale is erased by the end user in the form.
  • (Touch UI) Option ui.embedding controls if the app adjusts presenting when hosted in iframe of another website. The default value is 'auto', which will cause the app to disable inifinite scrolling and hide the navigation menu. If ui.embedding:false then the full user interface is displayed.
  • (Touch UI)  Option {"host":{"enabled":false}} placed in ~/touch-settings.json will disable automatic user interface adjustment when the app is running in iframe.
  • (Touch UI) Built-in membership manager correctly closes the user form after Delete command 
  • (Touch UI) The framework triggers menuinit.app event on the document at the start of the page construction. If  the "nodes" property of the event is assigned then the menu nodes will be uses as the main menu of the app. Offline Sync uses this event to provide a trimmed menu when the device does not have an online connection.
  • (Touch UI) The framework handles menuchanged.app event. It uses the "nodes" property of the event as the new main menu.  Offline Sync triggers this event in response to the changes in online/offline state of the device to expand/trim the menu nodes of the app visible to the user.
  • (OfflineSync) Triggers menuchanged.app event to let the app know about changes in the menu nodes.
  • (AppGen) If the generic Windows app is selected as the preview target for the app then the app generator makes sure that [Documents]\Code OnTime folder existis in order to persist the uma-launch.json action exchange file.
  • (Touch UI)  System "Help" item is not displayed in the context menu of hosted apps.
Tuesday, May 12, 2020PrintSubscribe
"Hello Barcodes!" Sample Code
The tutorial "Barcodes, QR codes, and RFID tags" results in the production of the following code:

~/touch-settings.json

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
{
  "barcode": {
    "enabled": true
  },
  "ui": {
    "automation": {
      "enabled": true,
      "kiosk": {
        "enabled": true,
        "gapColor": true,
        "maxWidth": 2000,
        "copyright": false,
        "fullscreen":  false
      }
    }
  }
}

~/js/MyApp.js

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
/*eslint eqeqeq: ["error", "smart"]*/

(function () {

    var Barcodes = {
        LoyaltyCard: /^lc/i,
        AccessCard: /^000/
    };

    // business rules

    $app.rules.Orders = {
        Calculate: function (dataView, args) {
            var freight = dataView.data().Freight;
            if (freight == null)
                freight = 0;

            $app.execute({ from: 'OrderDetails' }).then(function (result) {
                var total = 0;
                result.OrderDetails.forEach(function (detail) {
                    total += detail.ExtendedPrice;
                });
                if (total > 0)
                    total += freight;
                $app.input.execute({ OrderTotal: total });
            });
            return false;
        },
        after: {
            New: function (dataView, args) {
                var order = dataView.data();
                $app.input.execute({ OrderDate: new Date(), OrderNumber: order.OrderID });
            }
        }
    };

    $app.rules.OrderDetails = {
        after: {
            New: function (dataView, args) {
                $app.input.execute({ Discount: 0, Quantity: 1 });
            }
        },
        Calculate: function (dataView, args) {
            var details = dataView.data();

            var unitPrice = details.UnitPrice;
            if (unitPrice == null)
                unitPrice = 0;

            var quantity = details.Quantity;
            if (quantity == null)
                quantity = 0;

            var discount = details.Discount;
            if (discount == null)
                discount = 0;

            var extendedPrice = unitPrice * quantity * (1 - discount);
            $app.input.execute({ ExtendedPrice: extendedPrice })

            return false;
        }
    }

    // Barcode Input Simulator

    $(document).on('context.app', function (e) {
        e.context.push({
            hidden: true,
            text: 'Scan Product 1',
            shortcut: 'Ctrl+Shift+1',
            callback: () => $app.input.barcode('842776107183')
        });
        e.context.push({
            hidden: true,
            text: 'Toggle Kiosk',
            shortcut: 'Ctrl+Shift+K',
            callback: () => $app.touch.kiosk(!$app.touch.kiosk())
        });
    });

    // low-level barcode processing

    //$(document).on('barcode.app', function (e) {
    //    $app.touch.notify('I am handling this: ' + e.text);
    //    setTimeout(function () {
    //        $app.input.barcode(true);
    //    }, 7000)
    //    return false;
    //});

    //
    // Special Workflow Control Barcodes
    //

    $app.ifThisThenThat((app, context) =>
        app.if(context.barcode('ORDER_LINE_SETQTY').input('numpad.number', v => v > 0))
            .then(() => app.action('edit', 'details'))
            .then(() => app.val({
                'qua': context.input('numpad.number')
            }))
            .then(() => app.action('save'))
            .then(() => app.notify(false))
            .then(() => app.input('numpad', null))
    );

    $app.ifThisThenThat((app, context) =>
        app.if(context.barcode('ORDER_LINE_SETQTY'))
            .then(() => app.notify('Please use the numeric pad to enter the quantity.'))
    );

    $app.ifThisThenThat((app, context) =>
        app.if(context.barcode('ORDER_LINE_INC'))
            .then(() => app.action('edit', 'details'))
            .then(() => app.inc('qua'))
            .then(() => app.action('save'))
            .then(() => app.notify(false))
    );

    $app.ifThisThenThat((app, context) =>
        app.if(context.barcode('ORDER_LINE_DEC'))
            .then(() => app.action('edit', 'details'))
            .then(() => app.dec('qua'))
            .then(() => app.action('save'))
            .then(() => app.notify(false))
    );

    $app.ifThisThenThat((app, context) =>
        app.if(context.barcode('ORDER_LINE_5OFF'))
            .then(() => app.action('edit', 'details'))
            .then(() => app.val('disc', .05))
            .then(() => app.action('save'))
            .then(() => app.notify(false))
    );

    //
    // Hands-Free Order Entry
    //

    $app.ifThisThenThat((app, context) =>
        app.if(context.page('orders').list().barcode(Barcodes.LoyaltyCard).field('ord+num').action('new order'))
            .then(() => app.action('new order'))
            .then(() => app.val({
                'freight': 10.00,
                'via company': 'Federal Shipping',
                'loyalty': context.barcode()
            }))
            .then(() => app.notify('Please scan the order items.'))
    );

    $app.ifThisThenThat((app, context) =>
        app.if(context.page('orders').editing().barcode(Barcodes.LoyaltyCard).field('loyalty').val('loyalty', null))
            .then(() => app.val({
                'loyalty': context.barcode()
            }))
    );

    $app.ifThisThenThat((app, context) =>
        app.if(context.page('orders').editing().barcode(Barcodes.LoyaltyCard).field('loyalty'))
            .then(() => app.confirm('Assign a different loyalty card?'))
            .then(() => app.val({
                'loyalty': context.barcode()
            }))
    );

    $app.ifThisThenThat((app, context) =>
        app.if(context.page('orders').list().barcode(Barcodes.AccessCard).field('ord+num').action('new order'))
            .then(() => app.action('new order'))
            .then(() => app.val({
                'freight': 10.00,
                'via company': 'Federal Shipping',
                'access': context.barcode()
            }))
            .then(() => app.notify('Please scan the order items.'))
    );

    $app.ifThisThenThat((app, context) =>
        app.if(context.page('orders').editing().barcode(Barcodes.AccessCard).field('access'))
            .then(() => app.val({
                'access': context.barcode()
            }))
    );

    //
    // Product Barcode is scanned in Order Form
    //

    function tomorrow() {
        var d = new Date();
        d.setDate(d.getDate() + 1);
        return d;
    }

    $app.ifThisThenThat((app, context) =>
        app.if(context.page('orders').list().barcode().field('ord+num').action('new order'))
            .then(() => app.action('new order'))
            .then(() => app.val({
                'freight': 10.00,
                'via company': 'Federal Shipping',
                'required': tomorrow()
            }))
            .then(() => app.action('new order details', 'order details'))
            .then(() =>
                app.val({
                    'barcode': context.barcode(),
                    'qua': 0
                })
                    .fail(() =>
                        app.discard('Product not found.'))
            )
            .then(() => app.inc('qua', context.barcode()))
            .then(() => app.action('save'))
            .then(() => app.notify(false))
    );

    $app.ifThisThenThat((app, context) =>
        app.if(context.page('orders').editing().barcode().field('ord+num').row('barcode', context.barcode(), 'details'))
            .then(() => app.row('barcode', context.barcode(), 'details'))
            .then(() => app.action('edit', 'details'))
            .then(() => app.inc('qua', context.barcode()))
            .then(() => app.action('save'))
            .then(() => app.notify(false))
    );

    $app.ifThisThenThat((app, context) =>
        app.if(context.page('orders').editing().barcode().field('ord+num'))
            .then(() => app.action('new', 'details'))
            .then(() =>
                app.val({
                    'barcode': context.barcode(),
                    'qua': 0
                })
                    .fail(() =>
                        app.discard('Product not found.'))
            )
            .then(() => app.inc('qua', context.barcode()))
            .then(() => app.action('save'))
            .then(() => app.notify(false))
    );

    //
    // Using $app.execute to read data
    //

    $app.ifThisThenThat(
        (app, context) =>
            app.if(context.not().page('employee').barcode(Barcodes.AccessCard))
                .then(() =>
                    $app.execute({
                        controller: 'Employees',
                        filter: { AccessCard: context.barcode().unwrap() },
                        as: 'emp'
                    })
                        .then((result) =>
                            result.emp.length > 0 ?
                                app.notify(result.emp[0].FirstName + ' ' + result.emp[0].LastName +
                                    ', please scan the access card in the order form.') :
                                app.notify('Unknown access card: ' + context.barcode())
                        )
                )
    );

    //
    // Product Inventory
    //

    $app.ifThisThenThat((app, context) =>
        app.if(context.page('products').list().barcode().field('barcode').row('barcode', context.barcode()))
            .then(() => app.row('barcode', context.barcode()))
    );

    $app.ifThisThenThat((app, context) =>
        app.if(context.page('products').list().barcode().field('barcode'))
            .then(() => app.notify('Please select a product to assign a barcode.'))
    );

    $app.ifThisThenThat(
        (app, context) =>
            app.if(context.page('products').editing().barcode().field('barcode'))
                .then(() => app.val('barcode', context.barcode()))
    );

    $app.ifThisThenThat(
        (app, context) =>
            app.if(context.page('products').reading().field('barcode').barcode())
                .then(() =>
                    app.confirm('Assign the barcode ' + context.barcode() + ' to the product "' +
                        context.val('product') + '"?'))
                .then(() =>
                    app.action('edit'))
                .then(() =>
                    app.val('barcode', context.barcode()))
                .then(() =>
                    app.action('save'))
    );

    //
    // Customer Loyalty Cards
    //

    $app.ifThisThenThat(
        (app, context) =>
            app.if(context.page('customer').list().barcode(Barcodes.LoyaltyCard).field('loyalty').row('loyalty', context.barcode()))
                .then(() => app.rowFilter('loyalty', context.barcode()))
    );

    $app.ifThisThenThat(
        (app, context) =>
            app.if(context.page('customer').list().barcode(Barcodes.LoyaltyCard).field('loyalty'))
                .then(() => app.filter('loyalty', context.barcode()))
                .then(() => app.notify('Loyalty card not found: ' + context.barcode()))
    );

    $app.ifThisThenThat(
        (app, context) =>
            app.if(context.page('customer').editing().barcode(Barcodes.LoyaltyCard).field('loyalty'))
                .then(() => app.val('loyalty', context.barcode()))
    );

    $app.ifThisThenThat(
        (app, context) =>
            app.if(context.page('customer').reading().field('loyalty').barcode(Barcodes.LoyaltyCard))
                .then(() => app.confirm('Assign the loyalty card ' + context.barcode() + ' to the customer "' +
                    context.val('company') + '"?'))
                .then(() => app.action('edit'))
                .then(() => app.val('loyalty', context.barcode()))
                .then(() => app.action('save'))
    );

    //
    // Employee Access Cards
    //

    $app.ifThisThenThat(
        (app, context) =>
            app.if(context.page('employee').list().barcode(Barcodes.AccessCard).field('access').row('access', context.barcode()))
                .then(() => app.rowFilter('access', context.barcode()))
    );

    $app.ifThisThenThat(
        (app, context) =>
            app.if(context.page('employee').list().barcode(Barcodes.AccessCard).field('access'))
                .then(() => app.filter('access', context.barcode()))
                .then(() => app.notify('Access card not found: ' + context.barcode()))
    );

    $app.ifThisThenThat(
        (app, context) =>
            app.if(context.page('employee').editing().barcode(Barcodes.AccessCard).field('access'))
                .then(() => app.val('access', context.barcode()))
    );

    $app.ifThisThenThat(
        (app, context) =>
            app.if(context.page('employee').reading().field('access').barcode(Barcodes.AccessCard))
                .then(() => app.confirm('Assign the access card ' + context.barcode() + ' to the employee "' +
                    context.val('Last') + '"?'))
                .then(() => app.action('edit'))
                .then(() => app.val('access', context.barcode()))
                .then(() => app.action('save'))
    );

    var ordersDisplay2 = [
        { flow: 'row' },
        { flow: 'row' },
        {
            flow: 'column',
            label: 'Lines',
            in: 'details',
            count: 50,
            height: 'fill',
            alter: {
                _md: {
                    tab: 'Lines'
                }
            },
            '>': {},
            '#': {},
            'Product Name': {},
            'price': {
                alter: {
                    _lg: {
                        hidden: true
                    }
                }
            },
            'qua': { label: 'Qty' },
            'Disc': {
                label: 'Disc',
                alter: {
                    _lg: {
                        hidden: true
                    }
                }
            },
            'ext+price': { label: 'Ext. Price' },
            'barcode': { flow: 'row', label: false, width: '30%', wrap: false },
            'price2': {
                clone: 'price',
                flow: 'column',
                label: false,
                hidden: true,
                alter: {
                    _lg: {
                        hidden: null
                    }
                }
            },
            'supplier': {
                flow: 'column', label: false, wrap: true,
                alter: {
                    _lg: {
                        hidden: true
                    }
                }
            },
            'category': { flow: 'column', label: false, align: 'right' }
        },
        {
            alter: {
                _md: {
                    hidden: true
                }
            }
        },
        {
            alter: {
                _md: {
                    tab: 'Ship',
                    height: 'fill'
                }
            },
            'order date': { label: 'Ordered' },
            'required': { label: 'Required' },
            'ship via': {},
            'ship+phone': { label: 'Phone' },
            'salesPerson': {
                flow: 'column',
                expression: order => order.val('first') + ' ' + order.val('last'),
                when: order => order.val('last') != null,
                label: 'Employee',
                accentValue: true,
                alter: {
                    _md: {
                        flow: null
                    }
                }
            },
            'freight': {},
            'total': {
                flow: 'row',
                largeValue: true,
                colorValue: 'green',
                alter: {
                    _md: {
                        hidden: true
                    }
                }
            }
        },
        {
            flow: 'column',
            alter: {
                _md: {
                    hidden: true
                }
            }
        },
        {
            flow: 'column',
            label: 'Customer',
            width: '4x',
            height: 'fill',
            alter: {
                _md: {
                    flow: null,
                    tab: 'Customer',
                    height: 'fill'
                }
            },
            'ship name': {},
            'company': { label: false, medium: true, accent: true },
            'cust+ph': { label: 'Phone' },
            'loyalty': { label: 'Loyalty Card' },
            'ship addr': { label: false, dense: true },
            'city, region, zip': {
                expression: order =>
                    order.val('city') ?
                        order.val('city') + ', ' + (order.val('region') || '') + ' ' + order.val('postal') :
                        null,
                label: false,
                dense: true
            },
            'country': { label: false, dense: true }
        },
        {
            'total': {
                flow: 'row',
                largeValue: true,
                colorValue: 'green',
                hidden: true,
                alter: {
                    _md: {
                        hidden: null
                    }
                }
            }
        },
        {
            alter: {
                _md: {
                    hidden: true
                }
            }
        },
        {
            'numpad': {
                label: 'Search products or enter quantity',
                alter: {
                    _md: {
                        hidden: true,
                        compact: true
                    }
                }
            }
        },
        { flow: 'column' },
        {
            flow: 'column',
            width: '4x',
            height: 'fill',
            alter: {
                _sm: {
                    width: '2x'
                }
            },
            'save': {
                alter: {
                    _sm: {
                        text: false
                    }
                }
            },
            'cancel': {
                alter: {
                    _sm: {
                        text: false
                    }
                }
            }
        },
        {
            in: 'details',
            append: true,
            'Set quantity': {
                dark: true,
                barcode: 'ORDER_LINE_SETQTY',
                when: line => line.val('prod')
            },
            'delete': {
                darkest: true,
                text: false,
                wide: true
            }
        },
        {
            in: 'details',
            append: true,
            'Archive': {
                text: false,
                icon: 'material-icon-archive',
                when: line => line.val('prod'),
                barcode: 'ORDER_LINE_ARCHIVE'
            },
            'Increase Quantity': {
                text: false,
                dark: true,
                icon: 'material-icon-add',
                when: line => line.val('prod'),
                barcode: 'ORDER_LINE_INC'
            },
            'Decrease Quantity': {
                text: false,
                dark: true,
                icon: 'material-icon-remove',
                when: line => line.val('prod'),
                barcode: 'ORDER_LINE_DEC'
            },
            'Numeric Pad': {
                icon: 'material-icon-dialpad',
                text: false,
                darkest: true,
                perform: {
                    show: 'numpad',
                    hide: 'numpad'
                },
                hidden: true,
                alter: {
                    _md: {
                        hidden: null
                    }
                }
            }
        },
        {
            append: true,
            'Pay with credit card': {
                darkest: true,
                perform: {
                    pick: 'ord+date'
                }
            },
            'No sale': {
                dark: true,
                perform: {
                    pick: 'cust+comp'
                }
            }
        },
        {
            in: 'details',
            append: true,
            '5% discount': {
                wide: true,
                barcode: 'ORDER_LINE_5OFF',
                when: detail => detail.val('prod'),
                alter: {
                    _md: {
                        wide: false
                    }
                }
            }
        },
        { flow: 'column' },
        { flow: 'row' }
    ];

    $app.ifThisThenThat(
        (app, context) =>
            app.if(context.page('Orders').field('order number').editing().not().display(ordersDisplay2))
                .then(() => app.display(ordersDisplay2))
    );

    var ordersDisplay1 = 'Please scan a loyalty card or a product barcode to start your order.';

    $app.ifThisThenThat(
        (app, context) =>
            app.if(context.page('Orders').list().field('order number').not().display(ordersDisplay1))
                .then(() => app.display(ordersDisplay1))
    );




})();
Friday, April 17, 2020PrintSubscribe
Announcing Barcode/QR/RFID support, UI Automation, Kiosk UI, and Display Flow
Code On Time reaches another milestone in the journey to the low code application development nirvana. The unparalleled integration of barcode, QR code, and RFID tag processing with an optional Kiosk UI is now in the hands of developers!


Following our commitment to the mobile business apps, we have embarked on the implementation of the Barcode Scanning feature listed on our roadmap. Ability to interact with the real world objects through graphical codes and radio frequency tags is essential to the mobile end user experience

Our use case called for the end user to scan a sequence of barcodes, representing product UPCs, the QR code of a customer loyalty card, and reading the RFID tag of the salesperson’s access card. The data is sent to the app in a random sequence that results in a new order being created, line items inserted, quantities increased, and the customer and the salesperson associated with the order.

Sounds simple? Our own specification has required assigning the special markers to the certain fields in the Order and Details configuration for the use case to work. Soon we have realized that it is impossible to  come up with the coherent markers that are translated into execution of complex actions in response to the incoming barcodes and tags.

Through trial and error we have developed an amazing technology to make our “Hands-Free Order Entry” use case a reality. The pattern of order entry is universal and will apply to many business processes beyond the typical point-of-sale system: order delivery, stock inventory check, membership verification, self-service price check, security patrol, check-in and check-out, timecards, and much more.

Barcode Queue

We have extended the Touch UI framework to have a queue of barcodes. The generic term “barcode” also includes QR codes, RFID tags, or any other sequences of characters. The framework raises the “barcode.app” event for each “barcode” in the queue. Developers can handle the barcode immediately or in an asynchronous fashion when needed. The queue can accept new entries while the barcode at the front of the queue is being processed.
The wireless and wired barcode scanners, QR-code scanners, and RFID readers behave as human interface devices. They send characters representing scanned data to the keyboard input. Touch UI framework automatically detects the rapid submission of characters from virtual keyboard devices and places individual codes in the barcode queue. Touch UI can distinguish between the user typing rapidly and the barcode scanner sending the keys. That makes possible the barcode input detection in the online apps in the web browsers. Scanners working in the inventory mode are also supported. 

Applications executing on the devices with cameras can also capture barcodes and QR codes when running in the native mode in Cloud On Time app or its white-label derivative on iOS, Mac OS, Android, Chrome OS, and Windows platforms. End users of the native app can activate the camera scanning mode and capture one or more barcodes. The captured barcodes are placed in the queue for processing.

UI Automation

Let’s say the end user of your app is scanning a UPC barcode on a box. Your app must respond to this simple action by starting a new order, creating a line item, finding the product for that barcode, and saving the line item. If the product is not found, then the order must be canceled.

The user of a custom Order Entry app can press the “New Order” button to create a new order. Next the user will press the “New Detail” button to enter a new line item. The product lookup field will help finding the product by the name or UPC. The business rules of the app will assign the default quantity and calculate the extended price. Finally, the user will click the “Save” button to add the line item to the order. The order total will be updated to reflect the new line item.

We have decided to take advantage of the highly structured metadata-driven Touch UI to allow creating automation scripts executed in response to the certain conditions (e.g. barcode scan on Orders page) and simulating the user actions by automatically pressing the buttons and entering values. The new $app.ifThisThenThat API makes it possible.



This script will respond to the barcode scanned on the Orders page and cause your Touch UI application to create an order with a line item.



From the perspective of the end user, the scanning of the barcode has magically created an order with the new line items. The form appears filled with the data.

In fact, the automation script has caused the app to execute the corresponding commands on behalf of the user. The business rules have been faithfully executed on each step. The forms have remained invisible during automation. Transition effects and other user interface enhancements were disabled. 

UI Automation is built into Touch UI. Developers can design the core user experience based on Touch UI and later on enhance the app with the ability to respond to the scanned barcodes or other conditions. The barcodes can be simulated with the keyboard shortcuts or special custom actions and allow automating complex user interface sequences by manipulating the user interface of the app.

An automation script consists of multiple “if this then that” rules written in JavaScript and evaluated by the framework when the page loads up or when a barcode appears in the queue. The first rule that matches the current state of the app will be executed.

Kiosk UI

The responsive nature of Touch UI makes it possible for your app to display effortlessly on the screen of any size. The user interface provides a consistent set of features and controls that are easy to learn and match those found in the modern mobile operating systems.

If you place our Order Entry app in front of the customers in the grocery store, then you may inadvertently end up creating delays on the self-checkout lines. Some customers may be intimidated by the popup windows and multiple user interface options.

You need to reduce the app capabilities to the level of a sketch. Here you have the line items, there you have the customer information. Important buttons are clustered together. The order total must be easy to spot. A virtual terminal must be present to allow keying in the barcodes that cannot be read by the scanners due to poor quality.

Welcome to the Kiosk UI, the UI Automation feature that makes it possible to create an alternative presentation for your application suitable for mobile and occasional users with minimal training.



Kiosk-style presentation can be designed as a part of your application or introduced later on by the app administrator without any involvement on your part. The usability of your application can be extended in ways that you cannot even imagine when delivering apps built with Code On Time to your clients! They can take care of it themselves!

Consider another example of Kiosk UI displaying the customer information and responding to the loyalty card QR code.




The kiosk is displayed right on top of your app hiding the complexity and making emphasis on the core functionality that is important to certain end users. 

Below is the actual screen that the kiosk is hiding from the end user. The kiosk is serving as a remote control for your application. End user is pushing buttons and making choices that are reflected in the form hidden behind the kiosk display. 




Barcode input is always “on” when the kiosk display is active. 

Kiosk presentation is activated by “if this then that” rules. The definition of the kiosk is an array of JavaScript objects describing the flow of information on the kiosk display. A particular display can be activated in the response to the state of the application.



Try matching the display definition above with the fullscreen Kiosk UI display shown below:



Kiosk UI is activated through UI Automation scripts. The kiosk presentation can be selectively disabled for certain user roles, while retaining the automation. The advanced user may have access to all features of your app. The end users on the go can be provided with the Kiosk presentation for a streamlined experience.

Display Flow

The technology in the foundation of Kiosk UI is called “display flow”. 

This is a sample of the point-of-sale layout with the button cluster on the left created through movement of the corresponding JavaScript objects to the top of the display flow list with the minor changes to the properties.


The display flow is fully responsive. The point-of-sale system collapses into a tabbed user interface with the smaller buttons on a phone-size screen. Screen size breakpoints and device orientation can be used to configure the properties of the display flow.


Optional user interface elements can be hidden and brought up on-demand. The virtual terminal is turned off in the next screenshot.


The height of the display objects can be set to fill the screen. If that is the case then the display will cover the entire screen of the device and the “fillers” will become scrollable. This requires no CSS or HTML knowledge and works like magic. Multiple scrollable areas are supported.

The display flow is themed and accented as any other component of Touch UI.



Here is another example of the same display with the invisible gaps creating a simple modern user interface floating in the space.



It takes only a few minutes to rearrange the content.



Better yet, you can design multiple kiosk displays for the same app when needed.



JavaScript encoding of the display flow makes it suitable for online and offline applications.

We are very excited about this new addition to the Touch UI. The display flow will replace the current HTML based layouts in the forms/grid/list/cards and will help us to unify and simplify the various kinds of presentation in the framework. The display flow will allow drag & drop modification with the Visual Designer in the upcoming Code On Time v9. Imagine any Touch UI form with a display flow in the scrollable area and you will get the idea. Please note that we will retain the current look and fill and will still support HTML view templates.

It took many sleepless nights to work out a simple and expressive language for low code user interface design that is accessible and easy to manage with the visual development tools.

Availability

This new exciting technology is available in the release 8.9.5.0. 

Get Code On Time and build amazing apps that work online and offline with barcodes, QR codes, and RFID tags. Give your apps an infinite life span and unexpected use cases with UI Automation and Kiosk User Interface.