Documentation
Shell XAML
Shell.xaml is the main entry point of the app and defines the data model and navigation.
<?xml version="1.0" encoding="utf-8" ?>
<Shell xmlns="http://schemas.snapworx.at/appshell/2016/xaml"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Sample">
<Shell.Entities>
<!--Definition of data source entities used in lists and forms-->
</Shell.Entities>
<Shell.Tiles>
<!--Optional definition of map tiles used as background or map overlay-->
</Shell.Tiles>
<Shell.Views>
<!--Definition of UI views / navigation targets-->
</Shell.Views>
</Shell>
Entities
<Shell.Entities>
<Entity Id="Table1" Key="id" SyncType="Automatic" Table="table1" RevisionField="lastupdatetime">
<Field Name="id" Type="Guid" IsRequired="True" />
<Field Name="name" Type="String" MaxLength="256" />
<Field Name="geometry" Type="Geometry" />
<Field Name="lastupdatetime" Type="DateTime" />
</Entity>
</Shell.Entities>
Key field must be of type Guid if you want to create records offline
Entity
<Entity Id="" Title="" Table="" Key="" SyncType="" RevisionField="">
Field
<Field Name="" Type="" IsRequired="" MinLength="" MaxLength="" EpsgCode=""
LazyLoading="" ForeignEntity="" DefaultValue="" IntersectionMode=""
Precision="" StorageType="" FilterOperator="" FilterField="" Claim="" />
Field Types
- Guid
- String
- DateTime
- Number
- Integer
- Boolean
- Geometry
- Binary
- AutoIncrement
- UnixTimestamp
- Timestamp
AutoIncrement, Timestamp and UnixTimestamp are automatically updated on modification of an entity.
Sync Types
Value | Description |
---|---|
None | Entity is only available online |
Automatic | Entity is synced automatically on each login |
Automatic (with revision field) | Entity is synced automatically on each login, but only changes which have a higher value in the revison field since last sync (diff sync) |
Manual | Entity will only be synced once. You can sync it manually in the Synchronization view |
Intersection Modes
Intersection mode is used to define which geometry field is used for intersection. Currently only one geometry field with IntersectionMode="Intersects" is supported.
Value | Description |
---|---|
None | Geometry field is ignored |
Intersects | Geometry field is used to filter entities if an area is selected (Default) |
Applies only to geometry fields
Storage Types
Value | Description |
---|---|
Database | Binary field is stored in database (Default) |
FileSystem | Binary field is stored on the file system |
Applies only to binary fields with LazyLoading="True"
Filter Operators
Value |
---|
None |
Equal |
Contains |
GreaterThan |
GreaterEqual |
LessThan |
LessEqual |
Applies only to forms used in a filter
Validators
<Field>
<RegexValidator Message="" Pattern="" />
<MinValidator Message="" Minimum="" />
<MaxValidator Message="" Maximum="" />
</Field>
Filters
<Entity.Filters>
<Filter Id="" Sql="" IsDefault="" Title="" />
</Entity.Filters>
You can use placeholders in the Sql attribute with the following syntax
@{MyField}
In addition the following System placeholders are available
- @{System.UserId}
- @{System.UserName}
- @{System.DeviceId}
- @{System.Now}
- @{System.DeviceCulture}
- @{System.LanguageName}
- @{System.ActiveAreaId}
Triggers
<Entity.Triggers>
<Trigger Id="" Method="" Type="" />
<SqlTrigger Sql="" />
<MailTrigger From="" To="" Subject="" Body="" />
<UrlTrigger Url="" HttpMethod="" Headers="" Data="" />
<UpdateFieldTrigger Field="" Value="" />
<RemoveFieldTrigger Field="" />
</Entity.Triggers>
Id and Method attribute applies to all trigger types.
Trigger Methods
- BeforeSave
- BeforeUpdate
- BeforeDelete
- AfterSave
- AfterUpdate
- AfterDelete
- Manual
You can specify multiple methods separated with a comma
Tiles
Configuration of tile sources for map views (WMTS, WMS and TMS). Projection must be EPSG:3857.
<Shell.Tiles>
<UrlTileOverlay Id="" TileWidth="" TileHeight="" MinimumZoomLevel="" MaximumZoomLevel=""
MinimumCacheZoomLevel="" MaximumCacheZoomLevel="" MaximumSourceZoomLevel=""
Url="" CacheOnDemand="" Referer="" IsBackGround="" />
</Shell.Tiles>
Only one background layer can be active at a time
Views
Menu XAML
Defines a menu configuration.
<Menu xmlns="clr-namespace:AppShell;assembly=AppShell">
<MenuItem Target="TaskNavigation" Title="Tasks" Icon="Checklist" />
<MenuItem Target="NativeMapNavigation" Title="Native Map" Icon="Map" />
<MenuItem Target="SettingsNavigation" Icon="Setting" Type="Toolbar" />
<MenuItem Target="AppSelection" Icon="ChangeStatus" Type="Toolbar" />
<MenuItem Target="Logout" Icon="Logout" Type="Toolbar" />
</Menu>
Menu Items
<MenuItem Target="" Title="" Icon="" Type="" Claim="" ScriptTarget="" />
Target can be a view id from Shell.xaml or AppSelection/Logout
Menu Action Type
- Menu
- Toolbar
List XAML
Defines a list configuration.
Name of your XAML file is the view name in Shell.xaml
<?xml version="1.0" encoding="utf-8" ?>
<List xmlns="clr-namespace:AppShell;assembly=AppShell">
<List.ShellActions>
<!--Definition of shell actions on the top right-->
</List.ShellActions>
<List.RowActions>
<!--Definition of actions for each list row-->
</List.RowActions>
<List.Sort>
<!--Definition of default sorting-->
</List.Sort>
<!--Definition of various list cells-->
</List>
Shell Actions
<ShellAction Icon="" Type="" Claim="" />
<NavigateShellAction Target="" />
<SearchShellAction Filter="" Placeholder="" />
<OpenUriShellAction Uri="" />
<FilterShellAction Target="" Filter="" IsPersisted="" />
<NavigateMapShellAction Target="" Entity="" SourceField="" GeometryField="" />
Shell Action Type
- Primary
- Secondary
Row Actions
<RowAction Icon="" Claim="" />
<NavigateRowAction Target="" />
<ScriptRowAction />
<OpenUriRowAction Uri="" />
<OpenDocumentRowAction Field="" />
<OpenFileRowAction Field="" FileNameField="" FileName="" IsText="" />
<DownloadFileRowAction Field="" FileNameField="" FileName="" IsText="" />
Sort
<ListSort Name="" Order="" />
Sort Order
- Ascending
- Descending
Cells
<Cell Name="" Title="" ShortTitle="" Width="" DisplaySize="" WordWrap=""
ScriptValue="" Claim="" />
<ListCell StringFormat="" ForeignField="" />
<DistanceListCell />
<ImageListCell />
<MultiListCell>
<!--List Cells-->
</MultiListCell>
Display Size
- Small
- Medium
- Large
Word Wrap
- NoWrap
- WordWrap
- CharacterWrap
- HeadTruncation
- TailTruncation
- MiddleTruncation
Form XAML
Defines a form configuration.
Name of your XAML file is the view name in Shell.xaml
<?xml version="1.0" encoding="utf-8" ?>
<Form xmlns="clr-namespace:AppShell;assembly=AppShell">
<Form.ShellActions>
<!--Definition of shell actions on the top right-->
</Form.ShellActions>
<Form.Scripts>
<!--Definition of scripts-->
</Form.Scripts>
<!--Definition of various form fields-->
</Form>
Shell Actions
<ShellAction Icon="" Type="" Claim="" />
<SaveShellAction />
<DeleteShellAction />
<ScriptShellAction />
<PrevShellAction />
<NextShellAction />
<NavigateShellAction Target="" />
<OpenUriShellAction Uri="" />
<CaptureSignatureAction FieldName="" />
Shell Action Type
- Primary
- Secondary
Scripts
<StartupScript />
<LoadedScript />
<ChangeScript Name="" Field="" />
<SharedScript />
Fields
<Field Name="" Title="" Placeholder="" Help="" IsVisible="" IsRequired=""
IsEnabled="" DefaultValue="" Claim="" />
<Entry />
<Editor />
<DatePicker />
<TimePicker />
<DateTimePicker />
<Distance />
<Picker KeyMember="" DisplayMember="" Items="" />
<Geometry />
<ImagePicker PreviewField="" SizeField="" MimeTypeField="" />
<FilePicker FileNameField="" SizeField="" MimeTypeField="" />
<AutoComplete Entity="" DisplayField="" />
<OptionsGroup MultiSelect="" Columns="">
<Option Value="" Title="" />
<Option Field="" Title="" />
</OptionsGroup>
<Switch TrueValue="" FalseValue="" />
<Info />
<Signature />
<Table Entity="" EntityField="" RelationEntity="" Target="" RowCount="">
<Table.RowActions>
</Table.RowActions>
<Table.Actions>
</Table.Actions>
<Table.Sort>
</Table.Sort>
<TableCell Name="" Title="" />
</Table>
<Group IsExpanded="" />
<TabGroup IsExpanded="">
<FieldTab Name="" Title="">
<FieldTab.Fields>
</FieldTab.Fields>
</FieldTab>
</TabGroup>
<ImagePreview SourceField="" Entity="" RowCount="" IsExpanded="" />
Sort
<TableSort Name="" Order="" />
Field Actions
<FieldAction Icon="" AutoSave="" Claim="" />
<NavigateFieldAction Target="" />
<ScriptFieldAction />
<MessageFieldAction Message="" Cancel="" />
<DateTimeNowFieldAction />
<GeometryFieldAction Target="" Edit="" GeometryType="" />
<DownloadImageFieldAction />
<!--
If Resize is set to true (which is the default) the image will be resized
to 800 pixel while keeping the aspect ratio.
-->
<PickImageFieldAction Resize="" />
<TakeImageFieldAction Resize="" SaveToAlbum="" />
<PickFileFieldAction IsText="" />
<DownloadFileFieldAction FileName="" IsText="" />
<OpenFileFieldAction FileName="" IsText="" />
<ExecuteTriggerFieldAction Trigger="" />
<OpenUriFieldAction Uri="" />
<OpenDocumentFieldAction />
<CaptureSignatureFieldAction />
Map XAML
Defines a map configuration.
Name of your XAML file is the view name in Shell.xaml
<?xml version="1.0" encoding="utf-8" ?>
<NativeMap xmlns="clr-namespace:AppShell;assembly=AppShell" ShowScaleBar="" MinimumUpdateTime="" MinimumUpdateDistance="" FollowUser="" AutoRotate="">
<NativeMap.ShellActions>
<!--Definition of shell actions on the top right-->
</NativeMap.ShellActions>
<NativeMap.Layers>
<!--Definition of layers-->
</NativeMap.Layers>
</NativeMap>
MinimumUpdateTime in milliseconds MinimumUpdateDistance in meters
Shell Actions
<ShellAction Icon="" Type="" Claim="" />
<CurrentLocationAction />
<ToggleLayerAction />
<CaptureMarkerAction Target="" />
<NavigateSelectionAction />
<NavigateCarSelectionAction />
<CaptureDistanceAction />
<OpenUriShellAction Uri="" />
<CaptureCarDestinationAction />
<AutoCenterMapAction MarkerIcon="" />
<AutoRotateMapAction />
<GeometryShellAction Target="" Edit="" GeometryType="" CaptureFirst="" />
Shell Action Type
- Primary
- Secondary
Layers
<Layer Name="" Title="" IsActive="" AllowActivate="" Claim="" />
<NativeLayer Type="" />
<TileLayer TileRef="" />
<VectorLayer Entity="" MinimumZoomLevel="" MaximumZoomLevel="" ZIndex="" Filter="" AllowSelection="" IsSnapable="">
<FeatureInfo Title="" Detail="" Preview="">
<!--Shell Actions-->
</FeatureInfo>
</VectorLayer>
<MarkerVectorLayer Icon="" SelectionIcon="" IconSize="" AnchorPoint="" Label="" LabelSize="" LabelColor="" Rotation="" />
<LineVectorLayer Color="" StrokeWidth="" SelectionColor="" IsDashed="" SelectionBuffer="" />
<PolygonVectorLayer Color="" FillColor="" StrokeWidth="" SelectionColor="" SelectionFillColor="" IsDashed="" />
<CircleVectorLayer Color="" FillColor="" StrokeWidth="" SelectionColor="" SelectionFillColor="" IsDashed="" Radius="" />
Native Layer Type
- Roads
- Satellite
- Hybrid
JavaScript API
General
message(message: string, callback?: () => void): void;
alert(message: string, accept: string, cancel: string, resultCallback: (result: boolean) => void): void;
getUserName(): string;
getUserId(): string;
getActiveAreaId(): string;
getSessionKey(key: string): any;
setSessionKey(key: string, value: any): void;
removeSessionKey(key: string): void;
hasSessionKey(key: string): boolean;
getClipboardText(resultCallback: (text: string) => void): void;
setClipboardText(text: string): void;
syncEntity(name: string, resultCallback: () => void): void;
syncEntities(resultCallback: () => void): void;
executeAction(name: string, resultCallback: () => void): void;
/*
Supported formats:
AZTEC,CODABAR,CODE_39,CODE_93,CODE_128,DATA_MATRIX,EAN_8,EAN_13,ITF,MAXICODE,PDF_417,
QR_CODE,RSS_14,RSS_EXPANDED,UPC_A,UPC_E,UPC_EAN_EXTENSION,MSI,PLESSEY,IMB
If no format is passed, QR_CODE is assumed.
You can specify multiple formats separated by a comma.
*/
scanCode(resultCallback: (code: string) => void, formats?: string): void;
httpRequest(url: string, method: string, headers: {}, data: string, resultCallback: (result: string) => void): void;
navigate(target: string, parameters: {}): void;
executeTrigger(entity: string, triggerName: string, values: {}): void;
getI18NResource(key: string, fallback: string);
formatString(value: string, parameters: {}): string;
getDeviceCulture(): string;
getLanguageName(): string;
isConnected(): boolean;
setVisible(name: string, visible: boolean): void;
isVisible(name: string): boolean;
show(name: string): void;
hide(name: string): void;
setEnabled(name: string, enabled: boolean): void;
isEnabled(name: string): boolean;
enable(name: string): void;
disable(name: string): void;
getEntities(entity: string, filter: string, filterParameters: {}, resultCallback: (entities: {}[]) => void);
getEntity(entity: string, id: string, resultCallback: (entity: {}) => void);
getEntitiesByLocation(entity: string, geometry: Geometry, resultCallback: (entity: {}[]) => void);
getEntityByLocation(entity: string, geometry: Geometry, resultCallback: (entity: {}) => void);
getEntityCount(entity: string, filter: string, filterParameters: {}): number;
getEntityFieldById(entity: string, field: string, id: string, resultCallback: (value: any) => void);
getEntityField(entity: string, field: string, filter: string, filterParameters: {}, resultCallback: (value: any) => void);
saveEntity(entity: string, values: {}, resultCallback: (id: string) => void);
updateEntity(entity: string, id: string, values: {}, resultCallback: () => void);
deleteEntity(entity: string, id: string, resultCallback: () => void);
isNfcAvailable(): boolean;
isNfcEnabled(): boolean;
isNfcWritingSupported(): boolean;
readNfcTag((tag: NfcTag) => void);
writeNfcTag(record: NfcRecord, () => void);
clearNfcTag(() => void);
class NfcTag {
serialNumber: string;
isWritable: boolean;
isEmpty: boolean;
isSupported: boolean;
capacity: number;
records: NfcRecord[];
}
class NfcRecord {
typeFormat: string; // Empty, WellKnown, Mime, Uri
mimeType: string;
value: string;
languageCode: string;
}
List
getValues(): {};
getValue(fieldName: string): any;
setValue(fieldName: string, value: any): void;
Form
getValues(): {};
getValue(fieldName: string): any;
setValue(fieldName: string, value: any): void;
setItems(fieldName: string, items: {}[]): void;
copyValue(sourceFieldName: string, targetFieldName: string): void;
//Returns distance from current GPS position to geometry center in meters
getDistance(fieldName: string): number;
setDateNow(fieldName: string): void;
setGpsPosition(fieldName: string, accuracyFieldName?: string, altiudeFieldName?: string): void;
collapse(groupName: string): void;
expand(groupName: string): void;
setRequired(fieldName: string, required: boolean): void;
isRequired(fieldName: string): boolean;
isInsert(): boolean;
setValid(valid: boolean): void;
isValid(): boolean;
refresh(fieldName: string): void;
done(): void;
cancel(): void;
Map
getMapBounds(): []; //left, bottom, right, top
getMapRotation(): number;
getMapTapLocation(): Location;
setLayerActive(name: string, active: boolean): void;
isLayerActive(name: string): boolean;
Scripts can be used in multiple ways
<!--As row action in a list-->
<List.RowActions>
<ScriptRowAction>
<ScriptRowAction.Script>
</ScriptRowAction.Script>
</ScriptRowAction>
</List.RowActions>
<!--After a selection in a list-->
<List.SelectionScript>
</List.SelectionScript>
<!--Before or after any action-->
<DeleteShellAction>
<DeleteShellAction.BeforeScript>
</DeleteShellAction.BeforeScript>
<DeleteShellAction.AfterScript>
</DeleteShellAction.AfterScript>
</DeleteShellAction>
<!--As field action in a form-->
<Entry Name="entry" Title="Entry">
<ScriptFieldAction>
</ScriptFieldAction>
</Entry>
<!--After loading a form-->
<Form.Scripts>
<StartupScript>
</StartupScript>
</Form.Scripts>
<!--After changing a field in a form-->
<Form.Scripts>
<ChangeScript Field="FieldName" Name="OnFieldNameChanged">
</ChangeScript>
</Form.Scripts>
<!--After a selection in a map-->
<NativeMap.SelectionScript>
</NativeMap.SelectionScript>
<!--After a tap in a map-->
<NativeMap.TapScript>
</NativeMap.TapScript>
<!--After a selection in a map for a specific vector layer-->
<MarkerVectorLayer.SelectionScript>
</MarkerVectorLayer.SelectionScript>
Claims
Claims can be used to restrict visibility of fields, actions, menu items and layers. If no claim is defined, the item is visible to all users.
The following syntax is supported:
- Claim1
- Claim1,Claim2 (OR)
- Claim1+Claim2 (AND)
<Entry Name="title" Title="Title" Claim="Claim1+Claim2" />
Localization
Add a file named Shell.<language>.resx (e.g.: Shell.de.resx) in a folder named Resources inside the shell folder.
The resource key is defined in the following way:
<Type>_<Key>
- PageTitle
- Menu
- Label (used for script localizations)
<View>_<Type>_<Key>
- Field
- Help
- List
- Table
- FieldTab
- Filter
- OptionsGroup
- KeyValueList
- FeatureInfo_Title
- FeatureInfo_Detail
Examples
PageTitle_TaskList
Menu_TaskList
Label_MessageText
TaskList_List_Cell1
TaskForm_Field_Name
TaskForm_Table_Table1_Cell1
TaskForm_KeyValueList_Picker1_Key1
Graphics
Icons
Icon | Name | Icon | Name | Icon | Name | Icon | Name | Icon | Name |
---|---|---|---|---|---|---|---|---|---|
AppSelect | AreaList | Back | Bell | Chat | |||||
Checklist | Compass | DataSource | Delete | DeviceList | |||||
Distance | Down | Download | Filter | Flag | |||||
Gps | Hamburger | Help | Home | Info | |||||
Left | LineList | List | Logging | Logout | |||||
Map | Message | More | New | Password | |||||
PinList | QRCode | Request | Right | RightList | |||||
SatelliteMap | Save | Setting | Start | Stop | |||||
StreetMap | Summary | Synchronize | Text | TopoMap | |||||
Up | Url | User | UserList | Walk | |||||
Work | World | X |
Actions
Icon | Name | Icon | Name | Icon | Name | Icon | Name | Icon | Name |
---|---|---|---|---|---|---|---|---|---|
Alert | Attachment | Bicycle | Bin | Camera | |||||
Car | Check | Click | Clock | Delete | |||||
Edit | EditArea | EditLine | EditPin | Expand | |||||
Gps | Helicopter | Info | Layer | Map | |||||
Micro | Minus | More | NewArea | NewLine | |||||
NewPin | Pause | Phone | Plus | ||||||
Prev | Question | Refresh | Save | Search | |||||
Signature | Start | Stop | View | Walk | |||||
World |
Map
Icon | Name | Icon | Name | Icon | Name | Icon | Name | Icon | Name |
---|---|---|---|---|---|---|---|---|---|
Area | Arrow | Bicycle | Car | Delete | |||||
Edit | End | Helicopter | Line | Pin | |||||
Plus | Start | Walk |
Server Side Customization
You need Visual Studio to create custom triggers and providers
Create a C# class library and reference the following assemblies from the Mobile/bin directory:
AppShell.Core.dll AppShell.Services.Core.dll
Compile the class library and copy it into the Mobile/bin directory.
Custom Triggers
The following trigger will simply add the values of the two form fields Field1 and Field2 and store the result in the SumField.
using AppShell.Core;
using AppShell.Services.Core;
using System;
using System.Threading.Tasks;
namespace SampleTriggers
{
[Trigger(nameof(AddTrigger))]
public class AddTrigger : ITrigger
{
public Task ExecuteAsync(TriggerContext context)
{
context.Values["SumField"] = Convert.ToInt32(context.Values["Field1"]) + Convert.ToInt32(context.Values["Field2"]);
return Task.FromResult(0);
}
}
}
You can use the custom trigger in the Shell.xaml like that:
<Entity.Triggers>
<Trigger Type="AddTrigger" Method="BeforeSave,BeforeUpdate" />
</Entity.Triggers>
Custom Providers
IUserSessionProvider
The user session provider is called when an app is selected in the app selection screen.
using AppShell.Services.Core;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace SampleProviders
{
public class MyUserSessionProvider : IUserSessionProvider, IAutoRegister<IUserSessionProvider, MyUserSessionProvider>
{
protected readonly IDatabaseProvider databaseProvider;
public MyUserSessionProvider(IDatabaseProvider databaseProvider)
{
this.databaseProvider = databaseProvider;
}
public async Task<IDictionary<string, object>> GetUserSessionAsync(string appName, string userId)
{
if (appName != "MyApp")
return new Dictionary<string, object>();
using (IDatabase database = await databaseProvider.GetAppConnectionAsync(appName))
{
string value = await database.ExecuteScalarAsync<string>("SELECT MyValue FROM MyTable WHERE Id = @Id", new { Id = 1 });
return new Dictionary<string, object>() { { "MyValue", value } };
}
}
}
}
IUserHandler
The user handler is called after a login, after an app has been selected in the app selection screen and after the user actually logged into the selected app.
using AppShell.Services.Core;
namespace SampleProviders
{
public class MyUserHandler : IUserHandler, IAutoRegister<IUserHandler, MyUserHandler>
{
public void AfterLogin(string userId)
{
}
public void AfterAppSelection(string appName, string userId)
{
}
public void AfterShellSelection(string shellName, string userId)
{
}
public void AfterActivity(string userId)
{
}
}
}
Samples
Shell
Apply a default filter to an entity
<Entity.Filters>
<Filter Id="OpenTasks" Sql="endtime IS NULL" IsDefault="True" />
</Entity.Filters>
Filter an entity by user id
<Entity.Filters>
<Filter Id="UserTasks" Sql="userid = @{System.UserId}" IsDefault="True" />
</Entity.Filters>
Filter an entity by a date field
<Entity.Filters>
<Filter Id="TasksNext7Days" Sql="begintime < (@{System.Now} + 86400*7*1e7)" />
</Entity.Filters>
Dates are stored in ticks (100 nanoseconds) since 12:00:00 midnight, January 1, 0001 in the Gregorian calendar 1e7 is equal to one second in ticks and 86400 are 24 hours in seconds
Add a regex validator to a field
<Field Name="name" Type="String" IsRequired="True">
<RegexValidator Pattern="^[a-zA-Z0-9]+$$" />
</Field>
Add a trigger to an entity which is executed before an insert or update and sets the current user id
<Entity.Triggers>
<UpdateFieldTrigger Method="BeforeSave,BeforeUpdate" Field="userid" Value="@{System.UserId}" />
</Entity.Triggers>
Using a TMS tile source
<Shell.Tiles>
<UrlTileOverlay Id="tms" TileWidth="256" TileHeight="256" MinimumZoomLevel="0" MaximumZoomLevel="20"
Url="https://<tileserver>/{z}/{x}/{y}.png" />
</Shell.Tiles>
Using a restful WMTS tile source
<Shell.Tiles>
<UrlTileOverlay Id="wmts" TileWidth="512" TileHeight="512" MinimumZoomLevel="0" MaximumZoomLevel="20"
Url="https://maps.wien.gv.at/basemap/bmaphidpi/normal/google3857/{z}/{y}/{x}.jpeg" />
</Shell.Tiles>
Using a WMS tile source
<Shell.Tiles>
<UrlTileOverlay Id="wms" TileWidth="512" TileHeight="512" MinimumZoomLevel="10" MaximumZoomLevel="20"
Url="https://data.wien.gv.at/daten/geo?version=1.3.0&service=WMS&request=GetMap&crs=EPSG:4326&bbox={bbox}&width={width}&height={height}&layers=ogdwien:SPIELPLATZOGD&styles=&format=image/png&transparent=true" />
</Shell.Tiles>
Using a M.App Enterprise "My Geoservices" WMS tile source
<Shell.Tiles>
<UrlTileOverlay Id="wms" TileWidth="512" TileHeight="512" MinimumZoomLevel="0" MaximumZoomLevel="20"
Url="https://<yourserver>/api/v1/geoservices/wms/<tenant>/<wmsname>?version=1.3.0&service=WMS&request=GetMap&crs=EPSG:3857&bbox={bbox}&width={width}&height={height}&layers=<layers>&styles=&format=image/png&transparent=true" />
</Shell.Tiles>
Using a M.App Enterprise "My Geoservices" WMTS tile source with caching on demand
<Shell.Tiles>
<UrlTileOverlay Id="wmts" TileWidth="256" TileHeight="256" MinimumZoomLevel="0" MaximumZoomLevel="20"
Url="https://<yourserver>/api/v2/geoservices/wmts/<tenant>/<wmtsname>/google3857/{z}/{y}/{x}" />
</Shell.Tiles>
Using a M.App Enterprise "My Geoservices" WMTS tile source which has been published
<Shell.Tiles>
<UrlTileOverlay Id="wmts" TileWidth="256" TileHeight="256" MinimumZoomLevel="0" MaximumZoomLevel="20"
Url="https://<yourserver>/api/v2/geoservices/wmts/<tenant>/<wmtsname>/<cacheversion>/google3857/{z}/{y}/{x}" />
</Shell.Tiles>
You need to replace <cacheversion> with the current cache version from your warehouse:
<warehousepath>\<tenant>\WmtsCache\<wmtsname>\google3857\<cacheversion>
Using a M.App Enterprise "Imagery" WMTS tile source
<Shell.Tiles>
<UrlTileOverlay Id="wmts" TileWidth="256" TileHeight="256" MinimumZoomLevel="0" MaximumZoomLevel="20"
Url="https://<yourserver>/erdas-iws/ogc/wmts/<tenant>?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=<layername>&STYLE=default&FORMAT=image/jpeg&TILEMATRIXSET=OGC:1.0:GoogleMapsCompatible&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}" />
</Shell.Tiles>
List
Show a field in a list
<List xmlns="clr-namespace:AppShell;assembly=AppShell">
<ListCell Name="name" Title="Name" />
</List>
Show distance from current GPS position to geometry center
<List xmlns="clr-namespace:AppShell;assembly=AppShell">
<DistanceListCell Name="geometry" Title="Distance" />
</List>
Show an image in a list depending on a field
<List xmlns="clr-namespace:AppShell;assembly=AppShell">
<ImageListCell Name="status" Title="Status" Width="48" StringFormat="{}Icons.Status{0}.png" />
</List>
If the value of status would be 2, the icon would resolve to Icons/Status2.png in the shell folder
Show an image in a list depending on a field with a mapping
<List xmlns="clr-namespace:AppShell;assembly=AppShell">
<ImageListCell Name="status" Title="Status" Width="48" StringFormat="{}Icons.{0}.png" Fallback="Red">
<Mapping Key="0" Value="Red" />
<Mapping Key="1" Value="Yellow" />
<Mapping Key="2" Value="Green" />
</ImageListCell>
</List>
If the value of status would be 2, the icon would resolve to Icons/Green.png in the shell folder If the value is not found in the mapping it will fallback to Icons/Red.png
Show an image in a list depending on a script value
<List xmlns="clr-namespace:AppShell;assembly=AppShell">
<ImageListCell Name="begintime" Title="Status" Width="48" StringFormat="{}Icons.{0}.png">
<ImageListCell.ScriptValue>
var dateNow = new Date();
Context.getValue('begintime') < dateNow ? 'Red' : 'Green';
</ImageListCell.ScriptValue>
</ImageListCell>
</List>
If begintime is in the past it will show Icons/Red.png, otherwise it will show Icons/Green.png
Show multiple fields in one cell
<MultiListCell Name="NameDescription" Title="Name / Description">
<ListCell Name="name" />
<ListCell Name="description" />
</MultiListCell>
Show date value with a specific format
<List xmlns="clr-namespace:AppShell;assembly=AppShell">
<ListCell Name="begindate" Title="Begin Date" StringFormat="d" />
</List>
Format | Description | en-US | de-DE |
---|---|---|---|
d | Short date pattern. | 6/15/2009 | 15.06.2009 |
D | Long date pattern. | Monday, June 15, 2009 | Montag, 15. Juni 2009 |
f | Full date/time pattern (short time). | Monday, June 15, 2009 1:45 PM | Montag, 15. Juni 2009 13:45 |
F | Full date/time pattern (long time). | Monday, June 15, 2009 1:45:30 PM | Montag, 15. Juni 2009 13:45:30 |
g | General date/time pattern (short time). | 6/15/2009 1:45 PM | 15.06.2009 13:45 |
G | General date/time pattern (long time). | 6/15/2009 1:45:30 PM | 15.06.2009 13:45:30 |
M | Month/day pattern. | June 15 | 15. Juni |
s | Sortable date/time pattern. | 2009-06-15T13:45:30 | 2009-06-15T13:45:30 |
t | Short time pattern. | 1:45 PM | 13:45 |
T | Long time pattern. | 1:45:30 PM | 13:45:30 |
u | Universal sortable date/time pattern. | 2009-06-15 13:45:30Z | 2009-06-15 13:45:30Z |
U | Universal full date/time pattern. | Monday, June 15, 2009 11:45:30 AM | Montag, 15. Juni 2009 11:45:30 |
Y | Year month pattern. | June 2009 | Juni 2009 |
Standard Date and Time Format Strings
Sort a list
<List xmlns="clr-namespace:AppShell;assembly=AppShell">
<List.Sort>
<ListSort Name="geometry" Order="Descending" />
</List.Sort>
<DistanceListCell Name="geometry" Title="Distance" />
</List>
Add a row action which navigates to a map view and centers the map to geometry center
<List xmlns="clr-namespace:AppShell;assembly=AppShell">
<List.RowActions>
<NavigateRowAction Icon="View" Target="NativeMapNavigation">
<Parameter Name="Center" Value="@{geometry}" />
<Parameter Name="ZoomLevel" Value="15" />
<Parameter Name="SelectionId" Value="@{id}" />
</NavigateRowAction>
</List.RowActions>
</List>
Map value of a field to a display value in a list cell
<ListCell Name="DeviceType" Title="Device Type">
<Mapping Key="0" Value="iOS" />
<Mapping Key="1" Value="Android" />
<Mapping Key="2" Value="Windows" />
</ListCell>
Show value of a foreign entity in a list cell
<ListCell Name="DataSource_Id" Title="DataSource" ForeignField="Name" />
<!--Make sure to define ForeignEntity in your Shell.xaml-->
<Field Name="DataSource_Id" Type="Guid" IsRequired="True" ForeignEntity="DataSource" />
Enable search in a list
<List.ShellActions>
<SearchShellAction Filter="NameSearch" Placeholder="Your search hint" />
</List.ShellActions>
<!--Shell.xaml-->
<Entity.Filters>
<Filter Id="NameSearch" Sql="name LIKE @{NameSearch}" />
</Entity.Filters>
Enable search in a list on a foreign field
<List.ShellActions>
<SearchShellAction Filter="DataSourceSearch" Placeholder="Your search hint" />
</List.ShellActions>
<ListCell Name="DataSource_Id" Title="DataSource" ForeignField="Name" />
<!--Shell.xaml-->
<Entity.Filters>
<Filter Id="DataSourceSearch" Sql="DataSource_Id__Name LIKE @{DataSourceSearch}" />
</Entity.Filters>
<!--Make sure to define ForeignEntity in your Shell.xaml-->
<Field Name="DataSource_Id" Type="Guid" IsRequired="True" ForeignEntity="DataSource" />
Enable filtering in a list
<List.ShellActions>
<FilterShellAction Target="TasksFilter" />
</List.ShellActions>
<!--TasksFilter.xaml-->
<?xml version="1.0" encoding="utf-8" ?>
<Form xmlns="clr-namespace:AppShell;assembly=AppShell">
<Entry Name="Name" Title="Name" FilterOperator="Contains" />
<DateTimePicker Name="From" Title="From" FilterOperator="GreaterEqual" FilterField="begintime" />
<DateTimePicker Name="To" Title="To" FilterOperator="LessEqual" FilterField="begintime" />
</Form>
<!--Shell.xaml-->
<Navigation Id="MasterDetailNavigation">
<Form Id="TasksFilter" Title="Filter" View="TasksFilter" Entity="{x:Reference Tasks}"/>
</Navigation>
Form
Picker with static values
<Form xmlns="clr-namespace:AppShell;assembly=AppShell">
<Picker Name="color" Title="Color" KeyMember="Key" DisplayMember="Value"
Items="{KeyValueList Red/Green/Blue}" />
</Form>
Picker with static keys and values
<Form xmlns="clr-namespace:AppShell;assembly=AppShell">
<Picker Name="country" Title="Country" KeyMember="Key" DisplayMember="Value"
Items="{KeyValueList at-Austria/de-Germany/us-United States}" />
</Form>
Picker with values from an entity
<Form xmlns="clr-namespace:AppShell;assembly=AppShell">
<Picker Name="task_id" Title="Task" KeyMember="id" DisplayMember="name"
Items="{Entity Tasks}" />
</Form>
Picker with values from an entity sorted by a field
<Form xmlns="clr-namespace:AppShell;assembly=AppShell">
<Picker Name="task_id" Title="Task" KeyMember="id" DisplayMember="name"
Items="{Entity Tasks, Sort=name}" />
</Form>
Picker with values from an entity and a filter applied
<Entity Id="Tasks" Table="tasks" Key="id" SyncType="Automatic">
<Entity.Filters>
<Filter Id="OpenTasks" Sql="enddate IS NULL" />
</Entity.Filters>
...
</Entity>
<Form xmlns="clr-namespace:AppShell;assembly=AppShell">
<Picker Name="task_id" Title="Task" KeyMember="id" DisplayMember="name"
Items="{Entity Tasks, Filter=OpenTasks}" />
</Form>
Picker with values filtered based on another picker
<Entity Id="Countries" Table="countries" Key="id" SyncType="Automatic">
<Field Name="id" Type="Guid" IsRequired="True" />
<Field Name="name" Type="String" IsRequired="True" />
</Entity>
<Entity Id="Cities" Table="cities" Key="id" SyncType="Automatic">
<Entity.Filters>
<Filter Id="CountryFilter" Sql="country_id = @{country_id}" />
</Entity.Filters>
<Field Name="id" Type="Guid" IsRequired="True" />
<Field Name="country_id" Type="Guid" IsRequired="True" />
<Field Name="name" Type="String" IsRequired="True" />
</Entity>
<Form xmlns="clr-namespace:AppShell;assembly=AppShell">
<Picker Name="country_id" Title="Country" KeyMember="id" DisplayMember="name"
Items="{Entity Countries}" />
<Picker Name="city_id" Title="City" KeyMember="id" DisplayMember="name"
Items="{Entity Cities, Filter=CountryFilter}" />
</Form>
DateTimePicker with a field action to set current date and time
<Form xmlns="clr-namespace:AppShell;assembly=AppShell">
<DateTimePicker Name="begin" Title="Begin">
<DateTimeNowFieldAction />
</DateTimePicker>
</Form>
Using a script field action to set a field value
<Form xmlns="clr-namespace:AppShell;assembly=AppShell">
<Entry Name="scriptfield" Title="Script Field">
<ScriptFieldAction Icon="Alert">
Context.setValue('scriptfield', 'Test');
</ScriptFieldAction>
</Entry>
</Form>
Validate if a user is near an object before saving it
<Form xmlns="clr-namespace:AppShell;assembly=AppShell">
<Form.ShellActions>
<SaveShellAction>
<SaveShellAction.BeforeScript>
var distance = Context.getDistance('geometry');
if (!distance || distance > 100) {
Context.setError('geometry', 'Distance must be < 100m to the venue');
Context.setValid(false);
}
</SaveShellAction.BeforeScript>
</SaveShellAction>
</Form.ShellActions>
</Form>
Add an image picker to your form
<ImagePicker Name="image" Title="Image" PreviewField="previewimage" SizeField="imagesize" MimeTypeField="imagemimetype">
<DownloadImageFieldAction />
<PickImageFieldAction />
<TakeImageFieldAction />
</ImagePicker>
PreviewField (optional field to store a thumbnail)
SizeField (optional field to store size of captured image)
MimeTypeField (optional field to store mime type of captured image)
Add an image preview to your form
<ImagePreview Name="ImagePreview" Title="Images" SourceField="previewimage" Entity="Image" RowCount="5" IsExpanded="True">
<ImagePreview.Actions>
<DownloadImageFieldAction />
</ImagePreview.Actions>
<ImagePreview.Fields>
<Entry Name="name" Title="Name">
<TakeImageFieldAction Entity="Image" />
</Entry>
</ImagePreview.Fields>
</ImagePreview>
SourceField (field which is used for preview)
Add support for paging to your form
<Form xmlns="clr-namespace:AppShell;assembly=AppShell">
<Form.ShellActions>
<PrevShellAction />
<NextShellAction />
</Form.ShellActions>
</Form>
Add an options group with a single selection
<OptionsGroup Name="Color" Title="Choose a color">
<Option Value="red" Title="Red" />
<Option Value="green" Title="Green" />
<Option Value="blue" Title="Blue" />
</OptionsGroup>
Add an options group with multiple selections allowed separated by a comma
<OptionsGroup Name="Color" Title="Choose colors" MultiSelect="True">
<Option Value="red" Title="Red" />
<Option Value="green" Title="Green" />
<Option Value="blue" Title="Blue" />
</OptionsGroup>
Add an options group with multiple selections allowed saved into multiple boolean fields
<OptionsGroup Title="Choose colors" MultiSelect="True">
<Option Field="red" Title="Red" />
<Option Field="green" Title="Green" />
<Option Field="blue" Title="Blue" />
</OptionsGroup>
Group fields in a form
<Group Name="History" Title="History">
<Entry Name="CreatedBy" Title="Created By" IsEnabled="False" />
<DateTimePicker Name="CreationTime" Title="Creation Time" IsEnabled="False" />
<Entry Name="UpdatedBy" Title="Update By" IsEnabled="False" />
<DateTimePicker Name="LastUpdateTime" Title="Update Time" IsEnabled="False" />
</Group>
Show fields in different tab groups
<TabGroup Name="Tabs" Title="Tabs" IsExpanded="True">
<FieldTab Name="Tab1" Title="Tab 1">
<FieldTab.Fields>
<Entry Name="Entry1" Title="Entry 1" />
</FieldTab.Fields>
</FieldTab>
<FieldTab Name="Tab2" Title="Tab 2">
<FieldTab.Fields>
<Entry Name="Entry2" Title="Entry 2" />
</FieldTab.Fields>
</FieldTab>
<FieldTab Name="Tab3" Title="Tab 3">
<FieldTab.Fields>
<Entry Name="Entry3" Title="Entry 3" />
</FieldTab.Fields>
</FieldTab>
</TabGroup>
Capture a point geometry
<Geometry Name="geometry" Title="Geometry">
<GeometryFieldAction Icon="EditPin" Target="NativeMap" Edit="True" />
</Geometry>
Capture a line geometry
<Geometry Name="geometry" Title="Geometry">
<GeometryFieldAction Icon="EditLine" GeometryType="LineString" Target="NativeMap" Edit="True" />
</Geometry>
Capture a polygon geometry
<Geometry Name="geometry" Title="Geometry">
<GeometryFieldAction Icon="EditArea" GeometryType="Polygon" Target="NativeMap" Edit="True" />
</Geometry>
Populate an entry from a web service
<Form xmlns="clr-namespace:AppShell;assembly=AppShell">
<Form.Scripts>
<StartupScript>
Context.httpRequest('https://jsonplaceholder.typicode.com/todos/1', 'GET', {}, null, function (result) {
Context.setValue('scriptfield', JSON.parse(result).title);
});
</StartupScript>
</Form.Scripts>
<Entry Name="scriptfield" Title="Script Field" />
</Form>
Call a web service before saving
<Form xmlns="clr-namespace:AppShell;assembly=AppShell">
<Form.ShellActions>
<SaveShellAction>
<SaveShellAction.BeforeScript>
Context.httpRequest('<apiurl>', 'POST', {}, JSON.stringify({ data: Context.getValue('scriptfield') }), function (result) {
});
</SaveShellAction.BeforeScript>
</SaveShellAction>
</Form.ShellActions>
<Entry Name="scriptfield" Title="Script Field" />
</Form>
Confirm an action before executing it
<DeleteShellAction>
<DeleteShellAction.BeforeScript>
Context.alert('Would you really like to delete?', 'Yes', 'No', function (result) {
if (!result)
Context.cancel();
else
Context.done();
});
</DeleteShellAction.BeforeScript>
</DeleteShellAction>
Set items of a Picker from a script
<StartupScript>
Context.setItems('scriptpicker', [{ id: 'blue', name: 'Blue' }, { id: 'red', name: 'Red' }]);
</StartupScript>
<Picker Name="scriptpicker" Title="Script Picker" KeyMember="id" DisplayMember="name" />
Map
Map with native roads and satellite layer
<NativeMap xmlns="clr-namespace:AppShell;assembly=AppShell">
<NativeMap.Layers>
<NativeLayer Type="Roads" />
<NativeLayer Type="Satellite" />
</NativeMap.Layers>
</NativeMap>
Reference a tile layer from Shell.xaml
<NativeMap xmlns="clr-namespace:AppShell;assembly=AppShell">
<NativeMap.Layers>
<TileLayer TileRef="basemap" />
</NativeMap.Layers>
</NativeMap>
Add a marker vector layer with one of the embedded map icons
<NativeMap xmlns="clr-namespace:AppShell;assembly=AppShell">
<NativeMap.Layers>
<MarkerVectorLayer Entity="Wifis" MinimumZoomLevel="5" MaximumZoomLevel="20" Icon="Map.Pin.png" />
</NativeMap.Layers>
</NativeMap>
Add a marker vector layer with an icon you deliver within the shell folder In this example a folder Icons with a file MyIcon.png inside is expected in the shell folder. You must also include MyIcon@2x.png, MyIcon@3x.png, MyIcon@4x.png for high DPI devices.
<NativeMap xmlns="clr-namespace:AppShell;assembly=AppShell">
<NativeMap.Layers>
<MarkerVectorLayer Entity="Wifis" MinimumZoomLevel="5" MaximumZoomLevel="20" Icon="Icons.MyIcon.png" />
</NativeMap.Layers>
</NativeMap>
Add a marker vector layer with an icon depending on a field
<NativeMap xmlns="clr-namespace:AppShell;assembly=AppShell">
<NativeMap.Layers>
<MarkerVectorLayer Entity="Wifis" MinimumZoomLevel="5" MaximumZoomLevel="20" Icon="wifitype" />
</NativeMap.Layers>
</NativeMap>
Add a marker vector layer with a filter applied
<Entity.Filters>
<Filter Id="FreeWifiFilter" Sql="isfree = 1" />
</Entity.Filters>
<NativeMap xmlns="clr-namespace:AppShell;assembly=AppShell">
<NativeMap.Layers>
<MarkerVectorLayer Entity="Wifis" MinimumZoomLevel="5" MaximumZoomLevel="20" Icon="Map.Pin.png" Filter="FreeWifiFilter" />
</NativeMap.Layers>
</NativeMap>
Show feature info on selection of a marker
<NativeMap xmlns="clr-namespace:AppShell;assembly=AppShell">
<NativeMap.Layers>
<MarkerVectorLayer Entity="Wifis" MinimumZoomLevel="5" MaximumZoomLevel="20" Icon="Map.Pin.png">
<FeatureInfo Title="@{id}" Detail="Address: @{address}">
<NavigateShellAction Icon="Edit" Target="TaskForm">
<Parameter Name="Id" Value="@{id}" />
</NavigateShellAction>
</FeatureInfo>
</MarkerVectorLayer>
</NativeMap.Layers>
</NativeMap>
Add a line vector layer
<NativeMap xmlns="clr-namespace:AppShell;assembly=AppShell">
<NativeMap.Layers>
<LineVectorLayer Entity="Street" Color="#FF0000" SelectionColor="#00FF00" StrokeWidth="5" />
</NativeMap.Layers>
</NativeMap>
Add a polygon vector layer
<NativeMap xmlns="clr-namespace:AppShell;assembly=AppShell">
<NativeMap.Layers>
<PolygonVectorLayer Entity="Boundary" Color="#FF0000" FillColor="#88FF0000"
SelectionColor="#00FF00" SelectionFillColor="#8800FF00" />
</NativeMap.Layers>
</NativeMap>
Capture a point geometry
<NativeMap xmlns="clr-namespace:AppShell;assembly=AppShell">
<NativeMap.ShellActions>
<GeometryShellAction Entity="PointEntity" Target="PointForm" />
</NativeMap.ShellActions>
</NativeMap>
Capture a line geometry
<NativeMap xmlns="clr-namespace:AppShell;assembly=AppShell">
<NativeMap.ShellActions>
<GeometryShellAction Entity="LineEntity" GeometryType="LineString" Target="LineForm" />
</NativeMap.ShellActions>
</NativeMap>
Capture a polygon geometry
<NativeMap xmlns="clr-namespace:AppShell;assembly=AppShell">
<NativeMap.ShellActions>
<GeometryShellAction Entity="PolygonEntity" GeometryType="Polygon" Target="PolygonForm" />
</NativeMap.ShellActions>
</NativeMap>