Blog

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
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.

Wednesday, December 25, 2019PrintSubscribe
December 2019 Hot Fix 4
Release 8.9.4.0 introduces the following enhancements:

  • Generated apps are now compatible with Cloud On Time app v8.9.4.0 on OSX, iOS, and Windows platforms.
  • (App Gen) Project Wizard will locate addons in the user-defined location and in default [Documents]\Code OnTime\Addons folder.
  • (Touch UI) The width of the expanded sidebar has been increased by 44 pixels to match that of native Microsoft and Google apps.
  • (Touch UI) Optimized prevention of auto correct|capitalization|completion in text inputs by the web browsers.
  • (Touch UI) Methods $app.alert and $app.confirm will correctly call the corresponding callback or resolve the promise in response to any user action (click on OK, Cancel, [Close] icon, Back button, History shortcut). Re-entrance of the confirmation window is not possible.
  • (Touch UI) Fixed incorrect programming of double tap detection on touch devices.
  • (Touch UI) Implementation of $app.alert and $app.confirm is using promises.
  • (Touch UI) Multi-file upload works with ODP and selects the first record created in response to upload.