...
The
...
mobile
...
service
...
solution
...
manages
...
the
...
synchronization
...
of
...
the
...
offline
...
data.
...
Requirements:
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
By
...
default
...
Servoy
...
will
...
load
...
the
...
first
...
200
...
pks
...
when
...
a
...
form
...
is
...
used.
...
This
...
is
...
not
...
needed
...
in
...
the
...
service
...
solution
...
and
...
can
...
be
...
prevented
...
by
...
calling
...
databaseManager.setCreateEmptyFormFoundsets()
...
in
...
the
...
solution
...
onopen
...
global
...
method:
...
Code Block |
---|
function onSolutionOpen() {
// prevent prefilling of form foundsets with default query
databaseManager.setCreateEmptyFormFoundsets()
}
{code}
h3. Constructing a offline data package for the mobile client
The |
The ws_read(version,name)
...
on
...
the
...
offline_data
...
form
...
has
...
to
...
return
...
an
...
OfflineDataDescription
...
(=JSON)
...
object
...
filled
...
with
...
foundset
...
data
...
the
...
developer
...
wants
...
the
...
mobile
...
client
...
to
...
retrieve.
...
An
...
OfflineDataDescription
...
instance
...
is
...
created
...
with:
...
Code Block |
---|
var retval = plugins.mobileservice.createOfflineDataDescription('data_');
{code}
|
Note:
...
the
...
argument
...
is
...
optional
...
and
...
is
...
used
...
to
...
call
...
rest
...
endpoints
...
for
...
row
...
data
...
at
...
forms
...
starting
...
with
...
this
...
prefix
...
Then
...
we
...
need
...
to
...
provide
...
a
...
list
...
of
...
relation
...
names
...
which
...
should
...
be
...
used
...
in
...
traversal
...
to
...
find
...
all
...
data,
...
like:
...
Code Block |
---|
var traverse = new Array();
traverse.push('accountmanager_to_companies');
traverse.push('companies_to_contacts');
{code}
|
Lastly
...
we
...
have
...
instruct
...
the
...
OfflineDataDescription
...
to
...
collect
...
the
...
data,
...
starting
...
with
...
root
...
foundset
...
(containing
...
records)
...
and
...
return.
...
Code Block |
---|
retval.addFoundSet(fs_contact, traverse);
return retval;
{code}
|
Basically
...
addFoundSet
...
in
...
the
...
service
...
solution
...
exposes
...
an
...
unrelated
...
foundset
...
to
...
the
...
mobile
...
client,
...
which
...
can
...
be
...
used
...
in
...
an
...
unrelated
...
way
...
like
...
in
...
a
...
(first)
...
form
...
or
...
databaseManager.getFoundset(...)
...
For
...
each
...
record
...
in
...
the
...
provided
...
(unrelated)
...
foundset
...
the
...
specified
...
relations
...
are
...
traversed
...
and
...
all
...
data
...
taken.
...
...
...
...
In
...
order
...
to
...
provide
...
a
...
mobile
...
client
...
with
...
user
...
specific
...
data
...
the
...
ws_authenticate(useruid,password)
...
method
...
should
...
be
...
added:
...
Code Block |
---|
function ws_authenticate(useruid,password)
{
//TODO find user and check password against pwhash column
if (password == 'demo')//static demo check
{
var retval = new Object();
retval.username = useruid;
return retval;
}
return false;
}
{code}
|
In
...
the
...
ws_read
...
method
...
we
...
can
...
utilize
...
the
...
authenticate
...
username
...
variable
...
via
...
Code Block |
---|
var authenticate_info = questionParams.ws_authenticate[0];
globals.username = authenticate_info.username;
{code}
|
Here
...
the
...
authenticate
...
username
...
is
...
put
...
into
...
a
...
global
...
variable
...
which
...
in
...
turn
...
can
...
be
...
used
...
like:
...
Code Block |
---|
//prepare personal data
var fs_contact = globals.contact_data$username;//global related foundset using username global var, containing the account manager contact
{code}
|
Full
...
ws_read
...
method
...
for
...
personalized
...
data.
...
Code Block |
---|
function ws_read(version,name)
{
var questionParams = arguments[arguments.length-1];
//create return value
var retval = plugins.mobileservice.createOfflineDataDescription('data_');
//setting the key for user_select relation
var authenticate_info = questionParams.ws_authenticate[0];
globals.username = authenticate_info.username;
//prepare personal data
var fs_contact = globals.contact_data$username;//global related foundset using username global var, containing the account manager contact
/**
* @type {Array<String>}
*/
var traverse = new Array();
traverse.push('accountmanager_to_companies');
traverse.push('companies_to_contacts');
retval.addFoundSet(fs_contact, traverse);
return retval;
}
{code}
h3. |
...
...
...
...
Row/record
...
data
...
is
...
retrieved
...
in
...
separate
...
calls
...
for
...
each
...
entity,
...
for
...
example
...
for
...
"orders"
...
row
...
data
...
results
...
in
...
a
...
call
...
to
...
"orders"
...
form
...
is
...
made
...
on
...
ws_read
...
method.
...
Note:
...
If
...
a
...
prefix
...
is
...
provided
...
in
...
the
...
offlinedata
...
the
...
call
...
will
...
end
...
up
...
at
...
prefix+entityname,
...
example
...
for
...
prefix
...
"data_"
...
the
...
call
...
happens
...
on
...
form
...
"data_orders"
...
ws_read
...
is
...
with
...
a
...
list
...
of
...
pks
...
it
...
wants
...
as
...
row
...
data
...
for,
...
example
...
code:
...
Code Block |
---|
function ws_read(version,method)
{
var questionParams = arguments[arguments.length-1];
if (method == 'list')
{
/**
* @type {String}
*/
var ids = questionParams.ids[0];
if (ids != null && ids != '')
{
var idsa = ids.split(',', -1);
if (idsa.length > 0)
{
var json = plugins.mobileservice.getRowDescriptions(foundset.getDataSource(), idsa)
return json;
}
}
}
throw 404;
}
{code}
|
TIP:
...
since
...
ws_read
...
for
...
entities
...
is
...
likely
...
the
...
same,
...
it
...
might
...
be
...
beneficial
...
to
...
create
...
a
...
base
...
form
...
containing
...
this
...
logic
...
and
...
extend
...
from
...
this
...
form
...
...
...
...
...
...
...
...
...
ws_update
...
is
...
called
...
for
...
changes
...
made
...
by
...
mobile
...
client,
...
example
...
code:
...
Code Block |
---|
function ws_update(data,version,pk)
{
if (foundset.find())
{
foundset.contact_id = pk;
var count = foundset.search();
if (count > 0)
{
var rec = foundset.getRecord(1);
databaseManager.copyMatchingFields(data,rec,true);
databaseManager.saveData(rec);
}
}
}
{code}
|
ws_create
...
is
...
called
...
for
...
new
...
records
...
on
...
the
...
mobile
...
client,
...
example
...
code:
...
Code Block |
---|
function ws_create(data,version,pk)
{
var rec = foundset.getRecord(foundset.newRecord());
databaseManager.copyMatchingFields(data,rec,true);
databaseManager.saveData(rec);
}
{code}
|
Note:
...
the
...
retrieved
...
PK
...
(and
...
derived
...
FK's)
...
is
...
always
...
UUID's
...
if
...
the
...
underlaying
...
datamodel
...
is
...
not
...
UUID
...
based,
...
keep
...
and
...
apply
...
a
...
mapping
...
!
...
ws_delete
...
is
...
called
...
for
...
deleted
...
record
...
in
...
the
...
mobile
...
client,
...
example
...
code:
...
Code Block |
---|
function ws_delete(version,pk)
{
if (foundset.find())
{
var table = databaseManager.getTable(controller.getDataSource());
var pkname = table.getRowIdentifierColumnNames()[0]
foundset[pkname] = pk;
var count = foundset.search();
if (count > 0)
{
var rec = foundset.getRecord(1);
foundset.deleteRecord(rec);
}
}
}
{code}
h5. Transaction based syncing
By default the mobile client will do a rest call per changed row, these are separated calls so every call will be on a fresh new client on the serverside.
Because of this a transaction can't be used for all the changes of 1 mobile client and then fail a sync when something goes wrong. If the sync should be in 1 call, so that a transaction can be used over all the calls to the above |
By default the mobile client will do a rest call per changed row, these are separated calls so every call will be on a fresh new client on the serverside.
Because of this a transaction can't be used for all the changes of 1 mobile client and then fail a sync when something goes wrong. If the sync should be in 1 call, so that a transaction can be used over all the calls to the above ws_update/ws_create/ws_delete
...
methods,
...
a
...
ws_update
...
method
...
must
...
be
...
created
...
on
...
the
...
offline_data
...
form.
...
This
...
ws_update
...
method
...
then
...
gets
...
a
...
full
...
data
...
package
...
of
...
all
...
the
...
changes
...
that
...
a
...
mobile
...
client
...
has,
...
this
...
will
...
then
...
be
...
dispatched
...
over
...
all
...
the
...
methods
...
that
...
are
...
described
...
above.
...
An
...
example
...
of
...
a
...
ws_update
...
method
...
on
...
the
...
offline_data
...
form:
...
Code Block |
---|
function ws_update(data,version,authenticateResult)
{
try {
databaseManager.startTransaction();
plugins.mobileservice.performSync(data,version,authenticateResult);
databaseManager.commitTransaction();
} catch (e) {
databaseManager.rollbackTransaction();
// log the error and return false to that the mobile client will know the sync did fail.
application.output(e,LOGGINGLEVEL.ERROR);
return false;
}
}
{code}
|
This
...
code
...
above
...
starts
...
a
...
transaction
...
then
...
calls
...
the
...
performSync
...
method
...
of
...
the
...
mobile
...
service
...
plugin.
...
This
...
plugin
...
will
...
dispatch
...
all
...
the
...
changes
...
to
...
the
...
various
...
ws_update/create/delete
...
methods
...
of
...
the
...
entity
...
forms.
...
If
...
something
...
goes
...
wrong
...
then
...
an
...
exception
...
will
...
be
...
thrown
...
and
...
the
...
transaction
...
will
...
rollback.
...
...
...
If
...
using
...
get/setUserProperty()
...
in
...
the
...
Service
...
Solution,
...
the
...
properties
...
are
...
stored
...
in
...
the
...
Mobile
...
Client
...
that
...
connected
...
to
...
the
...
Service
...
Solution.
...
As
...
such,
...
a
...
user
...
property
...
can
...
be
...
used
...
to
...
store
...
for
...
example
...
an
...
authentication
...
token
...
to
...
be
...
used
...
between
...
multiple
...
calls
...
to
...
the
...
Service
...
Solution
...
from
...
the
...
Mobile
...
Client.
...
When
...
performing
...
custom
...
Ajax
...
calls
...
to
...
the
...
Service
...
Solution
...
from
...
the
...
Mobile
...
Client,
...
for
...
example
...
using
...
jQuery.ajax,
...
extra
...
steps
...
need
...
to
...
be
...
taken
...
to
...
make
...
sure
...
the
...
user
...
properties
...
are
...
available
...
in
...
the
...
Service
...
Solution
...
and
...
properly
...
persisted.
...
The
...
user
...
properties
...
are
...
to
...
be
...
send
...
with
...
the
...
Ajax
...
request
...
as
...
a
...
special
...
Request
...
Header,
...
identified
...
by
...
the
...
name
...
'servoy.userproperties'.
...
The
...
current
...
values
...
for
...
the
...
user
...
properties
...
of
...
the
...
Service
...
Solution
...
are
...
stored
...
in
...
the
...
Mobile
...
Client
...
in
...
the
...
localstorage,
...
from
...
which
...
they
...
can
...
be
...
retrieved
...
using
...
the
...
code
...
sessionStorage.getItem("servoy.userproperties").
...
In
...
the
...
Response
...
of
...
the
...
custom
...
Ajax
...
call,
...
the
...
possible
...
altered
...
value
...
can
...
be
...
stored
...
back
...
into
...
the
...
localstorage
...
using
...
this
...
code:
...
sessionStorage.setItem("servoy.userproperties",
...
value)
...
...
Below
...
an
...
example
...
of
...
how
...
to
...
make
...
a
...
custom
...
Ajax
...
request
...
using
...
JQuery.ajax
...
Code Block |
---|
var url = ....
$.ajax({
url: url,
beforeSend: function (request) {
...
request.setRequestHeader("servoy.userproperties:", sessionStorage.getItem("servoy.userproperties"));
...
}
}).done(function(data, textStatus, jqXHR) {
sessionStorage.setItem("servoy.userproperties", jqXHR.getResponseHeader("servoy.userproperties")
});
{code} |