If this is your first Mobilengine tutorial, you can get up to speed with the solution artifacts from Part 1 and Part 2 of the mobile form development tut.
What you'll be learning
-
How to use a workflow script (optionally running on both the client and the server side) to update a reference table in the Mobilengine Cloud with user edits in the form
-
How to set the order in which forms appear in the list of forms in the client sidebar
-
How to let users look up an address in a the default mapping application from inside the form
Figure 103. Apple Maps started from inside the form, showing an address from the linked reference table
The tutorials assume that you are logged in as petar.hoyt@gmail.com
. The current user-based filtering won't work on the
sample reference tables otherwise.
In this section, you'll learn the fundamentals of processing form submission data in the Mobilengine Cloud, to allow your users to modify reference data.
You can download all the artifacts created in this tutorial from this link.
The Mobilengine Cloud not only helps you manage and monitor your workflow solutions and related artifacts - if you publish workflow scripts along with your solution artifacts, you can program the Cloud to post-process form submission data. Workflow scripts have a JavaScript-like syntax, and you can use them to update, add, or delete data in reference tables any way you need.
This tutorial is an introduction to workflow scripting; it shows you how you can let your users update a reference table in the Mobilengine Cloud simply by submitting a form.
Rocky Jupiter now wants you to give their truck drivers a mobile form for each step in their daily delivery routine.
The forms themselves are no more than the user interface of the workflow - the heart of any workflow are the scripts that process the form submissions and that keep the workflow ticking along.
The scripts will shift the status of the delivery task that the driver is working on from Assigned through Confirmed, and Loaded all the way to Completed.
When all the details are in place, you'll make the workflow scripts run not only in the Cloud, but on the user's mobile device as well, for speed and the added comfort of offline availability.
This tutorial is for beginner scripters, designed to whet your appetite for mastery. It's a gateway to the really awesome stuff.
The forms in the workflow
Transport deliveries aren't exactly rocket science: get a delivery assigned to you at company HQ, drive to the loading address and load the cargo, then drive to the delivery address and unload the cargo. Start over. The three forms you'll be building in this section correspond to these basic stages, and so will be simple as pie.
The list of deliveries is, naturally, a reference table with every relevant info about each delivery order. The most important details are the driver that is assigned to the task, the status of the task (its stage of completion), and the two associated addresses: the one where the cargo is to be picked up, and the other where the cargo is expected. Have a look at the declaration file, and download the spreadsheet with the input data.
<Reftab Name="assignments" xmlns="http://schemas.mobilengine.com/reftab/v1"> <Columns> <Column Name="usr" Type="Text"></Column> <Column Name="assignment_id" Type="Text" NotNull="true"></Column> <Column Name="client" Type="Text" NotNull="true"></Column> <Column Name="load_type" Type="Text" NotNull="true"></Column> <Column Name="load_address" Type="Text" NotNull="true"></Column> <Column Name="load_date" Type="Date" NotNull="true"></Column> <Column Name="unload_date" Type="Date" NotNull="true"></Column> <Column Name="unload_address" Type="Text" NotNull="true"></Column> <Column Name="comments" Type="Text"></Column> <Column Name="photo_load" Type="Text"></Column> <Column Name="photo_unload" Type="Text"></Column> <Column Name="status" Type="Text" NotNull="true"></Column> </Columns> </Reftab>
Step one: pick a delivery task
The first form the driver is supposed to open has a dropdown
to let them
pick from the list of deliveries that are assigned to them.
Drivers probably pick based on how close they have to drive, so the
dropdown
can display the loading addresses. You'll also let the drivers
look the address up in the defaultŁ mapping application (Apple Maps in this case), and use
the mobile device to navigate to the load-up. For this, you'll use a new type of control
called a linkview
.
A linkview
is much like a text link on a web page: it
displays some text and navigates to a specified web address when the user clicks or taps
it. Its text
attribute takes a string, and its url
attribute takes a URL. For this task-picker form, data-bind the address that the user
selects in the dropdown
into the linkview
below
it.
<form id='confirm' menuName='Pick your next delivery' platforms='ios' xmlns='http://schemas.mobilengine.com/fls/v2'> <dropdown id='tasks' label='Select a a delivery based on loading address: ' choices='{SELECT a.usr, a.assignment_id, a.load_address, a.status FROM assignments a WHERE a.usr==sysp.user AND a.status=="Assigned"}' keyMap='{assignment_id}' textMap='{load_address}'/><linkview label='Get directions to loading address' text='{tasks.selectedText}' url='{SELECT "http://maps.apple.com/?address=" || REPLACE(tasks.selectedText, " ", "%20") address}'/>
</form>
The
query of the dropdown
(lines 7-13) makes sure that only the tasks that
are still available (in the Assigned status) and assigned to the
driver are displayed.
There's a bit of hacking necessary for the linkview
to work properly:
the spaces in the address need to be escaped for the Apple Maps API to be able to process
them. That's what the REPLACE
function does in line 19.
When the driver submits this form, the selected task's status
should be
changed from Assigned to Confirmed.
Step 2: Load the cargo
Exhibit B: A form that the driver opens when they get to the loading address. It lets the
driver check and navigate to the delivery address in a linkview
, and
submit the form to tell Rocky Jupiter HQ that the cargo is on its way. The form also lets
the driver cancel the whole delivery if there's an unexpected hiccup with the cargo.
This form is just as the basic as the first one: there's a textview
for the loading address, a linkview
for the delivery address, and a
checkbox
for cancelling, if necessary.
<form id='load' menuName='Load the cargo' platforms='ios' xmlns='http://schemas.mobilengine.com/fls/v2'> <declarations> <let id='confirmed' shape='record' value='{SELECT a.usr, a.assignment_id, a.load_address, a.unload_address, a.status FROM assignments a WHERE a.usr==sysp.user AND a.status=="Confirmed" LIMIT 1}'/> </declarations> <textview id='task' text='{confirmed.load_address}' label='Load cargo for confirmed delivery: '/> <linkview label='Get directions to the delivery address' text='{confirmed.unload_address}' url='{SELECT "http://maps.apple.com/?address=" || REPLACE(confirmed.unload_address, " ", "%20") address}'/> <checkbox id='cancel' label='Cancel the selected delivery'/> </form>
There
are, in fact, a couple of interesting things about this form. The first is that because
the query that pulls the delivery tasks with a Confirmed
status to
display is very long, it is stored as the value of a let
(lines 6-16).
The second is the LIMIT 1
clause at the end of this query, which makes
sure that the form doesn't break even if there is more than one task with the
Confirmed status.
There shouldn't be more than one active delivery task for any driver at any one time, but at this stage of your scripting journey, you can't guarantee this. Later on you'll make sure that this rule is enforced; this workaround will have to do until then.
When the driver submits this form, the status
of the task
displayed in the load
form should be updated to
Loaded. Get into the truck and let's get delivering!
Step the last: Unload the cargo at the destination
Drivers also need a form to open at the delivery address: not much more than a
signature
control so the driver (or the addressee) can verify that the
cargo has indeed been delivered.
<form id='unload' menuName='Deliver the cargo' platforms='ios' xmlns='http://schemas.mobilengine.com/fls/v2'> <declarations> <let id='loaded' shape='record' value='{SELECT a.usr, a.assignment_id, a.load_address, a.unload_address, a.status FROM assignments a WHERE a.usr==sysp.user AND a.status=="Loaded" LIMIT 1}'/> </declarations> <textview text='{loaded.unload_address}'/> <signature id="signature" hint='Sign here' label="I have delivered the cargo to the address above: "> <validation> <validator message='Please sign the form.' cond='{signature.binaryId IS NOT NULL}'/> </validation> </signature> </form>
Attach
a validator
to the signature
to make it required, just
like you did the first time you coded this control.
This form, when submitted, should tell the database that the status
of
the task at hand is Completed, so it's time to archive it
somewhere.
These forms are pretty, but they don't DO anything, really. You need to make them
update the database: make the status
of the task reflect its current stage of
completeness. Workflow scripts are the magic that make your inanimate form golems come alive
and do your bidding.
The scripts that process the submitted forms
You'd like a
workflow script that will run on the Mobilengine Cloud every time a user submits one of the
forms you've just made. The scripts need to modify the status
field of a
single specific record in the assignments
reference table. Given that their
job is the same, the three scripts that the three forms trigger on the server will
essentially be the same script with minor changes.
There is a tiny bit of house-keeping code that workflow scripts need to start with to make them scripts in the first place. Here's the headers of the three finished scripts; can you spot the boilerplate?
Basically,
the header specifies whether
the script will run on the server
or the client
or both
(more on that
later), names the script (confirmer
, loader
,
unloader
), and specifies the form (or other arriving artifact) trigger that will fire the script on the server. The using directive in the
second line specifies that the scripts all have access to the assignments
reference table.
While you're at it, read the gentle intro to workflow script language basics. It's fun. Go on, we'll wait.
Now that that's out of the way,
let's actually make the scripts do something to the assignments
reference
table. All three scripts will change the status
field of a record in
exactly the same way. The only difference is which record it is, and what the updated value
should be.
The confirmer
script has access to everything submitted to the server in
the confirm
form, accessible via dot-notation. It identifies the record
that needs updating by checking what the user has selected in the tasks
dropdown
, and changes the record's status
field to
Confirmed:
Let's
break this down: The db
keyword
lets you access reference tables in the Cloud using dot-notation:
assignments
in this case. The script then calls reftable.Update()
method on it, to modify a certain row of the reference table. The first argument
identifies the row, and the second one supplies the new field value. The first argument
says, using the form
keyword, that we need the row in assignments
where the assignment_id
field is equal to the selectedKey
property of the tasks
dropdown
.
The loader
script, in turn, has access to everything submitted to the
server in the load
form, and identifies the record to be updated based on
the value in the confirmed
variable. The script then checks whether the
user selected the cancel
checkbox
, and updates the particular record's status
field to either Cancelled, or Loaded
accordingly.
The
script starts off by introducing a variable as a placeholder for the record to be updated, and sets it to whatever
value it finds in the confirmed
let
in the submitted form.
The actual processing is done inside the if
-else
blocks: checking if the checkbox
to cancel the delivery is selected, and
updating the status
field of the record stored in
confirmedTask
accordingly.
Depending on whether the checkbox
is selected or not, the script calls
the reftable.Update()
method to change the record stored in
confirmedTask
, and changes its status
field to
Cancelled or Loaded. Beautiful.
The exact same thing is going on in the unloader
script: identifying the
record to be manipulated based on the variable that stores it in the form, and then
calling the reftable.Update()
method to modify its
status
field.
Save
the scripts with an .rfs
extension in the same folder as their trigger
forms, and upload the whole lot, assignments
declaration file and input
data included, to the Cloud using the mebt, and download the solution to your mobile client. Open
the forms in the right order, and deliver a cargo, assuming Mr. Petar Hoyt's identity.
See your minions in action
Now that you're running server-side scripts triggered by form submissions, you'll
naturally be interested in what is going on in the Cloud. You'll want to see, for example,
whether the workflow script ran successfully or failed because it encountered a problem.
Unlike with forms and reference table declaration files, the mebt
cannot
tell you if your script has bugs - the only way to find out is to have the trigger fire
the script, and look at the log.
The Backoffice site is your ticket to this log. The Dev Console → Log screen records every single event on your personal space on the server.
You have probably logged in to the Backoffice site before, but if you need it, here's a refresher.
If you've clicked through the three forms and submitted them, the Log should look something like the screenshot below.
Click the more toggle to see the details of the script's activity.
If the script failed to run or encountered a problem while running, check the log entry for how you can sort it out.
Take the workflow offline
You're mostly done here; there's just a few tweaks you could make before delivering on the project.
First of all, you could help your future users out by listing the forms they need to open
and interact with in the proper order. All things being equal, forms appear in the sidebar
with their menuName
attribute alphabetized, and it's a bit confusing to
have Deliver the cargo, the last of the three forms, come first in
the list.
Sure, you could modify the menuName
to read 01-Deliver the
cargo, but that seems like a hack.
Luckily, there's an attribute for that: menuOrder
accepts any integer (as
well as the hidden
option, which you'll put to use later) and displays the
forms in ascending order. Go ahead and give confirm.fls.xml
the pole
position it
deserves:
Modify
load.fls.xml
and unload.fls.xml
in a similar fashion,
save the forms, and republish.
Cool. Now add some magic to the scripts to speed up the workflow and take it offline.
You might not have noticed if the Wi-Fi connection on your tablet is high-powered enough,
but the changes that your workflow scripts make to the reference data take some time to show
up. You've submitted Pick your next delivery, but the Load
the cargo form is empty the first time you open it, because
confirmer
hasn't yet changed any status
fields to
Confirmed yet.
This is because the mobile device has to submit the form data to the server, the workflow script has to run and process the form submission data, and the mobile device has to download the modified reference data before the new data can appear on the device.
Trouble is, in the real world, mobile users typically use their flimsy and unreliable mobile data connection instead of a more robust Wi-Fi connection.
Fortunately the Mobilengine platform lets you easily speed up the workflow dramatically, and make sure that the workflow does not stop even when the user has no reception. The solution is to make the workflow scripts run on the mobile device, and change the reference table locally at the same time as the form submission data is sent to the server for processing.
You want the same workflow scripts to first run on the mobile device, and present the changed reference data to the user, and then on the server, to change the reference data in the cloud. Because of this, you won't need to write another script; just modify the ones that you have to be compatible with both the client and the server.
One of the Mobilengine tutorial videos feature a clear overview of this concept.
You may wonder why you need to run the same script twice, once locally, and then again on the server. The data in the cloud always has priority over local data on mobile devices: if you didn't make the same changes locally as globally, all the local changes would be lost the next time your mobile device syncs with the cloud. In addition, server-side scripts are identical to locally-run scripts only in simple workflows - conflict-handling is usually necessary in server-side scripts, but not in local scripts. Another reason to distinguish between the two types of scripts is that client-side scripts also can't send integration messages.
It's much easier than you think: all it takes is the single word
client
inserted right before the server program
declaration.
Overwrite all three scripts and republish the solution. No more waiting on the scripts to run to go on with the workflow.
Stop users breaking the workflow
You've made the lives of your workflow-using drivers a bit easier with those client-side
scripts, now it's your turn to get comfortable. Even though the Rocky Jupiter specifications
for the workflow clearly state that drivers should work on one delivery at a time, your
workflow doesn't check up on users or enforce this rule. Sneaky drivers could come back to
the confirm
form, and select additional delivery tasks before completing
the first one. And then you'd be the one taking the rap for them. Sort this out before it
bites you in the caboose.
One way to go about it is to put everything in the confirm
form inside an
if
so that it's only displayed if there are no tasks associated with the
driver that are in either Confirmed
or Loaded
status.
Otherwise, a polite but firm message should greet the driver remindign them of the
in-progress tasks. The tricky part is the query statement, an EXISTS
condition that returns true
if there are in-progress tasks
confirmed by the current user (lines
3-6).
<form id='confirm'...> <if id='inprogress' cond='{EXISTS (SELECT u.usr, u.status FROM assignments u WHERE u.usr==sysp.user AND u.status IN ("Confirmed", "Loaded"))}'> <textview text='Please complete your in-progress tasks before taking on a new one'/> </if> <if cond='{NOT inprogress.cond}'> ... </if> </form>
You'll be revisiting this solution to exclude in-progress tasks when you bring this workflow to its dashboard-driven apotheosis.
While you're on the topic of displaying messages to users when they wander onto a form at
the inappropriate time, consider the other two forms in the workflow. If there is no task
with the right status
value to display, both load
and
unload
now display a blank and unattractive form, that may also be
confusing to your users. You could display a helpful message if there isn't anything to
see.
The idea is the same as for confirm
: wrap the original controls inside an
if
, and include an exclusive if
whose condition
evaluates to true if the form would be empty
otherwise.
unload
works exactly the same
way.
The exact message is up to you, of course. You can be as friendly or threatening to your users as you need to.
Save the new forms, publish the solution, and open on your device. Confirm a delivery and
then sneak back to the confirm
form to experience the bafflement of your
crafty drivers trying to be sneaky.
Now, re-publish to overwrite the database, and open Load the
cargo first. There should be a helpful reminder from your solution developer
friend.
Why this is only the beginning
You've set up a workflow that can make the user input actually have an effect on the database, which is pretty clever.
However, users can throw a monkey wrench into your workflow by not going through them in the devised order. There's an added inconvenience of actually having to open the forms in the first place: you could make the users only have to open a single form, and then be guided through the workflow as effortlessly as if they were watching TV.
You'll be able to gently railroad your users through a workflow if you go through the next set of our scripting tutorials.