In this tutorial we are going to extend the first article published here. We will start from the same data and shell and we will extend the configuration step by step.
The present article is divided in incremental labs. The material attached to this post is organized in the following way:
In the description below we assume you are working with a Postgresql database. You will find anyway the same scripts for Oracle or SqlServer databases.
The goal of this first lab is to add a Map in your mobile App configuration. The Map will contain just the native Map available on your mobile device.
1. Copy NativeMap.xaml from templates folder
2. Shell.xaml -> Add to MasterDetail.Views:
<Navigation Id="MapNavigation">
<NativeMap Id="NativeMap" Title="Map" Center="40.6239644;-74.00624" View="NativeMap" ZoomLevel="14" MapType="Roads"/>
</Navigation>
3. Menu.xaml ->
a. Add to Menu:
<MenuItem Target="MapNavigation" Title="Map" Icon="Map" Type="Toolbar"/>
b. Add to Logout item
Type="Toolbar"
4. Test App (logout and login again)
The goal of this lab is to add a geometry field to our Locations table and to be able to capture this geometry within the mobile App.
1. PostgreSql2.sql -> add geometry field
2. Shell.xaml -> Add to Entity Id="Locations":
<Field Name="geometry" Type="Geometry" />
3. NativeMap.xaml -> add to NativeMap.Layers:
<MarkerVectorLayer Entity="Locations" Icon="Map.Pin.png" ZIndex="3" MinimumZoomLevel="13" MaximumZoomLevel="22">
</MarkerVectorLayer>
4. LocationForm.xaml -> add to Form:
<Geometry Name="geometry" Title="Geometry">
<GeometryFieldAction Target="NativeMap" Capture="True">
<Parameter Name="ZoomLevel" Value="19" />
</GeometryFieldAction>
</Geometry>
5. Test App
In this lab we are adding some new tools to interact with the map and get info about the selected object on the map (FeatureInfo with included details and actions to edit the object and to navigate to the object using the native mobile navigator App).
1. NativeMap.xaml ->
a. Add to NativeMap.ShellActions:
<NavigateSelectionAction Type="Secondary" />
<ToggleLayerAction Type="Secondary" />
<CaptureDistanceAction Type="Secondary" />
<CurrentLocationAction Type="Secondary" />
<AutoCenterMapAction Type="Secondary" />
a. Add to MarkerVectorLayer:
<FeatureInfo Title="Name: @{name}" Detail="Comments: @{description}">
<NavigateShellAction Icon="Edit" Target="LocationForm">
<Parameter Name="Id" Value="@{id}" />
</NavigateShellAction>
<NavigateSelectionAction />
</FeatureInfo>
2. Test App
The goal of this lab is to learn how to create a one to many relationship to store, in this particular case, several pictures related to the currently selected location.
The idea to implement such a relationship is to create an additional table (entity in our Shell) and to store for each new row the primary key of the current Location. To implement this behavior we will have the following configuration:
1. PostgreSql3.sql -> add image table
2. Shell.xaml ->
a. add to Entities:
<Entity Id="Image" Table="images" Key="id" SyncType="Automatic" RevisionField="lastupdatetime">
<Field Name="id" Type="Guid" IsRequired="True"/>
<Field Name="imagetype" Type="String"/>
<Field Name="entity_id" Type="Guid" ForeignEntity="Locations"/>
<Field Name="description" Type="String"/>
<Field Name="image" Type="Binary" LazyLoading="True"/>
<Field Name="image_thumb" Type="Binary" LazyLoading="False"/>
<Field Name="image_size" Type="Number"/>
<Field Name="image_mimetype" Type="String"/>
<Field Name="lastupdatetime" Type="Timestamp"/>
</Entity>
b. Add to MasterDetail.Views:
<Navigation Id="LocationImageNavigation">
<Form Id="LocationImageForm" Title="Images" View="LocationImageForm" Entity="{x:Reference Image}"/>
</Navigation>
3. LocationForm.xaml -> add to Form:
<Table Name="Images" Title="Images" Entity="Image" Target="LocationImageForm" RowCount="5" LazyLoading="False">
<Table.RowActions>
<DownloadImageRowAction Field="image"/>
</Table.RowActions>
<Table.Actions>
<NavigateFieldAction Icon="Camera" Target="LocationImageForm">
<Parameter Name="Field.Value.entity_id" Value="@{id}" />
</NavigateFieldAction>
</Table.Actions>
<TableCell Name="imagetype" Title="Type"/>
<TableCell Name="description" Title="Comment"/>
<TableCell Name="lastupdatetime" Title="Created"/>
</Table>
4. Copy LocationImageForm.xaml from templates folder
5. Test App
Goal of this lab is to add a new entity and use the whole table in a picker of the LocationForm using the key/value pairs (id/type fields in the newly created table loc_type).
1. PostgreSql4.sql -> add table loc_type (picklist) and field type to locations
2. Shell.xaml ->
a. add Entity Id="LocationType":
<Entity Id="LocationType" Key="id" SyncType="Automatic" Table="loc_type">
<Field IsRequired="True" Name="id" Type="Guid" />
<Field Name="type" Type="String"/>
</Entity>
b. Add to Entity Id="Locations":
<Field Name="type" Type="String" />
3. LocationForm.xaml -> add to Form:
<Picker Name="type" Title="Location Type" KeyMember="id" DisplayMember="type" Items="{Entity LocationType}"/>
4. Test App (you must wipe local data while connecting because data model has changed)
In this lab we start adding custom actions in our form using scripts. JavaScript functions can be executed based on several events in App lifecycle:
The Javascript API is documented here.
We are going to add a function OnFormStartup to check the current status in the form and eventually set it to a new value (Open) and a custom action placed by the status entry to manually set it (for asking for approval).
1. PostgreSql5.sql -> add field status to locations
2. Shell.xaml -> add to Entity Id="Locations":
<Field Name="status" Type="String" />
3. LocationForm.xaml ->
a. Add to Form:
<Entry Name="status" Title="Status" IsEnabled="false"/>
b. Add to Form:
<Form.Scripts>
<StartupScript Name="OnFormStartup">
if (!Context.getValue('status')) {
Context.setValue('status','Open');
}
</StartupScript>
</Form.Scripts>
c. Update Entry Name="status" to (change status for requesting approval):
<Entry Name="status" Title="Status" IsEnabled="false">
<ScriptFieldAction Icon="Click">
Context.setValue('status','Awaiting Approval');
</ScriptFieldAction >
</Entry>
In this lab we are going to add a new Navigation to display the list of Locations based on the specific status (Open/Awaiting Approval). This will be handled by a Filter set in the Entity and then referenced in the two LocationNavigation and LocationToApproveNavigation.
1. Shell.xaml ->
a. add Entity.Filters to Entity Id="Locations" to handle sql query:
<Entity.Filters>
<Filter Id="LocationsOpen" Sql="status = 'Open'" IsDefault="False" />
<Filter Id="LocationsToApprove" Sql="status = 'Awaiting Approval'" IsDefault="False" />
</Entity.Filters>
b. Add Navigation to handle a different list:
<Navigation Id="LocationToApproveNavigation">
<List Id="LocationToApproveList" Title="Locations To Approve" View="LocationList" Entity="{x:Reference Locations}">
</List>
</Navigation>
c. Add List.Filters to both Navigation elements:
i. Navigation id="LocationNavigation":
<List.Filters>
<FilterRef Filter="{x:Reference LocationsOpen}" Title="Locations" IsDefault="True"/>
</List.Filters>
ii. Navigation id="LocationToApproveNavigation":
<List.Filters>
<FilterRef Filter="{x:Reference LocationsToApprove}" Title="Locations To Approve" IsDefault="True"/>
</List.Filters>
a. Add Menuitem to show the new Navigation:
<MenuItem Target="LocationToApproveNavigation" Title="Locations To Approve" Icon="Work" />
In this lab we are adding 2 more custom actions.
The first one is used to fix an improper behavior while we capture pictures: if the Location has NOT been saved already and we try to capture a new Image, the newly created image will be saved without any id set in entity_id. With this custom script we are checking if the the Location has been saved (there is an id set in the form) and in case it is not we cancel the action itself.
The second action is used to customize the default behavior of the DeleteShellAction: we prompt the user if he really wants to delete the current Location, in this case we delete it, otherwise we cancel the action.
1. LocationForm.xaml ->
a. Disable camera action if location has not been saved yet
Add to <NavigateFieldAction Icon="Camera" Target="LocationImageForm">:
<NavigateFieldAction.BeforeScript>
var id = Context.getValue('id');
if (!id) {
Context.message('Location must be saved before start capturing pictures');
Context.cancel();
}
</NavigateFieldAction.BeforeScript>
b. Ask the user for confirmation before deleting a Location
Change DeleteShellAction this way:
<DeleteShellAction.BeforeScript>
Context.alert('Would you really like to delete?', 'Yes', 'No', function (result) {
if (!result)
Context.cancel();
else
Context.done();
});
</DeleteShellAction.BeforeScript>