Building A Mobile Application With O4W - Part 1

By Bryan Shumsky, Revelation Software

Introduction

In the past (see specifically Spectrum Magazine issues July/August2010 and September/October 2010), I discussed Revelation Software's O4W web development toolkit.  For those of you who've forgotten, O4W provides both a "wizard" interface for generating reports, forms, menus, and graphical dashboards, and a full basic+ API to build custom routines.  It's designed to make Web 2.0 development easy for multivalue programmers, without requiring developers to learn all the ins and outs of HTML, javascript, and AJAX.

O4W uses an open-source library called jQuery as its underlying scaffolding.  jQuery provides a platform independent way to create rich web user interfaces, and has been adopted by over 50% of the world's top 10,000 web sites, with a vibrant and large community of users and developers.  It's been incorporated into Microsoft development environments, and is even used by Wikipedia (source: Wikipedia - so it must be true!)

Over the past year or two, the jQuery developers have released a comparable library for mobile device development, called jQueryMobile.  O4W has been extended to allow you to develop mobile applications using jQuery Mobile behind the scenes.  Over the next two issues, we'll explore how you can make a mobile web app with O4W.

This Means YOU

Before we begin, let's get a preconception some of you might have out of the way - O4W is not just for OpenInsight developers.  Through the use of OpenInsight's extendable Basic Filing System, files that are in D3, mvBase, UniVerse, UniData, QM, or SQL databases can be "hooked up" to OpenInsight, and appear (to all extents) as native OpenInsight tables. That means that O4W's wizard tools can operate on them, and that O4W custom routines written in basic+ can access and update them directly.

Sample Application

Look, it's no secret - I like pizza.  Anyone who knows me - indeed, many people who don't know me, but who just see me - will come to this conclusion.  So anything I can do to make pizza more readily available is a worthwhile endeavor.

As our sample application, we're going to make a mobile application for a pizza delivery service. We'll use "stepwise enhancements" to take an initial, simple application, and dress it up until it has a bunch of sausage and pepperoni - oops, sorry, I mean bells and whistles.

And then when it's all done, we'll order in pizza, ok?

Mobile Design Considerations

I carry around a Galaxy Note smartphone, one of the "phablets" (phone-tablets) that many commentators in the tech world originally dismissed as being too big and unruly for people to want.  But - surprise! - the Note has taken off, because people realized that a bigger screen makes it easier to do things.  Yes, in this instance, substance won out over style.

But even though my phone has a bigger screen, I still don't want to try and edit a full-size spreadsheet on it, for example - and neither do I want to try and view (or use) a web page that only looks good on a 27" monitor.  Yes, the growing size of many smartphones, and the increased use of tablets, means that mobile websites can take advantage of more screen real estate than ever before, but the major design restriction is still going to be the limited number of display inches (in comparison to a desktop or laptop screen).

So when we design our mobile web page, we must remember two things:

a) Don't try to squeeze on more content than can reasonably be seen on the smallest device you plan to support - No, telling the user to "zoom out" or make his or her font smaller is not the right answer.  Redesign your layout so that it fits naturally on the screen(s) that your users are going to use; and

b) The communication channel between the device and the server will not necessarily be as fast as wired internet or wifi - no matter how many "G's" the phone companies tell you your phone is running (3G, 3.5G, 4G - gee wiz!), it's still not going to match wired Gigabit ethernet.  So don't make your mobile page try to push more bytes than it has to!

Fortunately, jQuery Mobile (and thus O4W Mobile) take these into consideration when generating the web page.  jQuery Mobile will control the layout to make things eye- and finger-friendly on different sized screens, and will also attempt to minimize round trips between the device and the server by "cacheing" results wherever possible.

Building the Form

jQuery Mobile will automatically convert the HTML and javascript that's generated by O4W and make it "mobile friendly", but in order for jQuery to do this correctly, we have to identify where each section of our HTML goes on the mobile screen.  jQueryMobile does this by having us assign "roles" to each section.  The required, standard roles on a mobile screen look like this:

We use the same structure, and the same basic+ APIs, to generate a form in O4W Mobile as we do in "regular" O4W – we just also add information to the API call to identify the role that each element plays in the overall picture.
Let's go ahead and start building our first iteration of the mobile pizza application.  In O4W, each routine that may be called via a URL has to have a name that starts with "O4W_" (this is just a security precaution - don't want someone inadvertently invoking DELETE-FILE via the browser...), and by convention we usually put an "M_" somewhere for the mobile version of the application, so let's call this routine O4W_MMM_PIZZA (mmm...pizza...):

SUBROUTINE O4W_MMM_PIZZA(CTLENTID,EVENT, REQUEST)
$INSERT O4WCOMMON
$INSERT O4WEQUATES
BEGIN CASE
                CASE EVENT _EQC "CREATE"
                                O4WSetMobile("1");* tell O4W we're generating mobile output
                                O4WForm();* initialize the output form
END CASE
RETURN 0

Every O4W routine has this general format - a subroutine call with three parameters, and including two standard inserts.  When the URL with this request is first received, the routine gets called with the "CREATE" event, and at this point the subroutine can use O4W calls to build the required browser output.
For our simple example, we'll build two mobile pages in this routine - a menu display, and a location page - along with a navigation bar to transfer between the two.  Our code will make extensive use of the O4WSectionStart and O4WSectionEnd API calls to group the output into sections (those become "divs" in HTML, for you HTML junkies out there), and we then apply modifiers to the sections to indicate what role they play in the overall page layout:

                * buildfirst page of mobile application
                O4WSectionStart("menuPage",pageOptions) ;* define the overall page section
                O4WSectionStart("menuHdr",hdrOptions) ;* define a section for our header
                O4WHeader("mmmPizzaMenu", 3) ;* show a header of size 3
                * build the navigation bar
                Gosub makeNav
                O4WSectionEnd("menuHdr");* done with the header section
                O4WBreak() ;* skip a line
                O4WBreak() ;* skip anotherline
                * start a new section forour main content
                O4WSectionStart("menuContent",O4WMarkedOptions('1'):contentOptions)
                Gosub buildMenu
                O4WSectionEnd("menuContent")
                O4WSectionEnd("menuPage")

The internal subroutine to build the navigation bar is similar - it defines the navigation section, and then uses standard O4W APIcalls to build links between the pages:

MakeNav:
                                *create menu bar
                                o4wsectionstart("pizzaNavbar",O4WMobileOptions("navbar"))
                                o4wliststart(0);* start an unordered list
                                o4wlistitem();* our first item in the list is a link to the menu page
                                O4WLink("Menu",O4W_LINKTYPE_LOCAL$, "menuPage")
                                o4wlistitem();* the second item in the list is a link to the location page
                                O4WLink("Location",O4W_LINKTYPE_LOCAL$, "locPage")
                                o4wlistend();* the list is done
                                o4wsectionend("pizzaNavbar");* and so is this section
Return

Note that we explicitly called the O4WMobileOptions call to identify this section as the "navbar" role, but in the overall page creation code we used globally defined variables that we'd set up previously as a convenience.  The pageOptions and hdrOptions modifiers, as well as some other handy "role definitions", can thus be reused:

pageOptions =o4wmobileoptions("page","b"):O4WDataStyle("","data-add-back-btn", "true")
hdrOptions = o4wmobileoptions("header")
contentOptions = o4wmobileoptions("content")
dialogOptions = o4wmobileoptions("dialog","b")

And indeed we do reuse these in the definition of the second page:

* build second page
                                O4WSectionStart("locPage",pageOptions)
                                O4WSectionStart("locHdr",hdrOptions)
                                O4WHeader("mmmPizzaLocations", 3)
                                *use same menu bar
                                GosubmakeNav
                                O4WSectionEnd("locHdr")
                                O4WBreak()
                                O4WBreak()
                                O4WSectionStart("locContent",O4WMarkedOptions('1'):contentOptions)
                                *use standard O4W API to build this output
                                O4WTableStart("locTable",o4wmobiletableoptions("1", "", "33"))
                                O4WSetCell(1,1, '', o4wtablecelloptions("","","1")) ;* mark asa 'header'
                                O4WText("Location:")
                                O4WSetCell(1,2)
                                address= "99 Kinderkamack Rd":@VM:"FirstFloor":@VM:"Westwood, NJ 07675"
                                O4WText(address)
                                O4WTableEnd("locTable")
                                O4WSectionEnd("locContent")
                                O4WSectionEnd("locPage")

Here we use an O4WTable to lay out the columns of data in nice, even rows - we'll also do the same thing for our main menu page.
When this is all put together, along with the buildMenu internal subroutine, we generate the following pages of output on the mobile device:

Here's the full code that was used to make these pages:

Subroutine O4W_MMM_PIZZA(CTLENTID, EVENT, REQUEST)

$Insert O4WCOMMON

$Insert O4WEQUATES

pageOptions =o4wmobileoptions("page","b"):O4WDataStyle("","data-add-back-btn", "true")

hdrOptions = o4wmobileoptions("header")

contentOptions = o4wmobileoptions("content")

dialogOptions = o4wmobileoptions("dialog","b")

Begin Case

                Case EVENT _EQC"CREATE"

                                O4WSetMobile(1)

                                O4WFORM()

                                * buildfirst page of mobile application

                                O4WSectionStart("menuPage",pageOptions)

                                O4WSectionStart("menuHdr",hdrOptions)

                                O4WHeader("mmmPizzaMenu", 3)

                                * buildthe navigation bar

                                GosubmakeNav

                                O4WSectionEnd("menuHdr")

                                O4WBreak()

                                O4WBreak()

                                O4WSectionStart("menuContent",O4WMarkedOptions('1'):contentOptions)

                                GosubbuildMenu

                                O4WSectionEnd("menuContent")

                                O4WSectionEnd("menuPage")                 

                                *build second page

                                O4WSectionStart("locPage",pageOptions)

                                O4WSectionStart("locHdr",hdrOptions)

                                O4WHeader("mmmPizzaLocations", 3)

                                * use samemenu bar

                                GosubmakeNav

                                O4WSectionEnd("locHdr")

                                O4WBreak()

                                O4WBreak()

                                O4WSectionStart("locContent",O4WMarkedOptions('1'):contentOptions)

                                * usestandard O4W API to build this output

                                O4WTableStart("locTable",o4wmobiletableoptions("1", "", "33"))

                                O4WSetCell(1,1, '', o4wtablecelloptions("","","1")) ;* mark asa 'header'

                                O4WText("Location:")

                                O4WSetCell(1,2)

                                address ="99 Kinderkamack Rd":@VM:"First Floor":@VM:"Westwood,NJ 07675"

                                O4WText(address)

                                O4WTableEnd("locTable")

                                O4WSectionEnd("locContent")

                                O4WSectionEnd("locPage")

End Case

Return 0

makeNav:

                                * createmenu bar

                                o4wsectionstart("pizzaNavbar",O4WMobileOptions("navbar"))

                                o4wliststart(0)

                                o4wlistitem()

                                O4WLink("Menu",O4W_LINKTYPE_LOCAL$, "menuPage")

                                o4wlistitem()

                                O4WLink("Location",O4W_LINKTYPE_LOCAL$, "locPage")

                                o4wlistend()

                                o4wsectionend("pizzaNavbar")

Return

buildMenu:

                O4WSectionStart("menuGroups",o4wmarkedoptions("0"))

                O4WSectionStart("appDetails",o4wmarkedoptions("0"))

                o4wheader("Appetizers",3)

                O4WText("BreadSticks")

                o4wbreak()

                O4WText("GarlicBread")

                o4wbreak()

                o4wtablestart("wingTable",o4wmobiletableoptions("1"))

                O4WText("HotWings")

                o4wsetcell()

                o4wtext("6Wings":@VM:"12 Wings")

                o4wtableend("wingTable")

                o4wbreak()

                o4wbreak()

                o4wsectionEnd("appDetails")

                O4WSectionStart("entreeDetails",o4wmarkedoptions("0"))

                o4wheader("Entrees",3)

                o4wtablestart("pizzaTable",o4wmobiletableoptions("1"))

                O4WText("Pizza")

                o4wsetcell()

                o4wtext("Personal":@VM:"Medium":@VM:"Large")

                o4wsetcell(2)

                o4wtext("Toppings")

                o4wsetcell()

                o4wtext("Pepperoni":@VM:"Sausage":@VM:"Ham":@VM:"Meatball":@VM:"Chicken")             o4wsetcell(3)

                o4wtext("Sauce")

                o4wsetcell()

                o4wtext("RedSauce":@VM:"White Sauce (Garlic)")

                o4wtableend("pizzaTable")

                o4wbreak()

                o4wbreak()

                o4wsectionEnd("entreeDetails")

                O4WSectionStart("drinkDetails",o4wmarkedoptions("0"))

                o4wheader("Drinks",3)

                o4wtablestart("drinkTable",o4wmobiletableoptions("1"))

                o4wText("Soda")

                o4wsetcell()

                o4wtext("Coke":@VM:"DietCoke":@VM:"Sprite")

                o4wsetcell(2)

                o4wtext("Juice")

                o4wsetcell()

                o4wtext("Apple":@VM:"Orange":@VM:"Cranberry")

                o4wtableend("drinkTable")

                o4wbreak()

                o4wbreak()

                o4wsectionEnd("drinkDetails")

                O4WSectionStart("dessertDetails",o4wmarkedoptions("0"))

                o4wheader("Desserts",3)

                O4WText("Cheesecake")

                o4wbreak()

                o4wbreak()

                o4wtablestart("iceTable",o4wmobiletableoptions("1"))

                O4WText("ItalianIce")

                o4wsetcell()

                o4wtext("Cherry":@VM:"Lemon")

                o4wtableend("iceTable")

                o4wbreak()

                o4wsectionEnd("dessertDetails")

                O4WSectionEnd("menuGroups")

return

The actual rendered output will vary depending on which mobile device, and which browser, is used; jQuery Mobile will convert the various calls to their appropriate "native" equivalents.

The Next Step

To actually view this program on your smartphone or tablet point your mobile browser to:

http://m.revelation.com/pizza

Now that we've generated our first, rudimentary mobile output, it's time to dress it up with some more "pizzazz" (see how I worked in pizza one more time?) ...but for that, you'll have to wait for next month's part two!

Revelation Conference

The previous Revelation Software Users' Conference was held at the Omni Orlando Resort at Championsgate in Orlando, Florida on April 18-21, 2016.

Click Here

On The Road

Throughout the year Revelation Software attends various multivalue User Conferences as well as our Regional User Group meetings.

Click Here

International Offices

Revelation Software has various International Offices and sales regions located on multiple continents.

Click Here

Featured Clients

Revelation Software's clients are involved in every industry around the world. Here is a sampling of some of our clients and their businesses

Click Here