Locly Native JSON documentation

App Version 43.0.0+
April 2017

This document aims to give a more technical overview of some of the advanced features of the Locly Platform. It provides examples on how to achieve some of the more advanced tasks which can be configured through JSON.

At its core the Locly platform uses a number JSON models to represent how and what content to show within the app. The most common functionality is exposed within the editor UI with easy controls. It is worth noting that when editing JSON it is possible to break the models so that the app is no longer able to use them. The Locly Platform does make periodic backups of your app to ensure any breaking changes are reversible.

Models

Locly native uses three primary model types. These are:

Collections and Cards are both considered content items as they provide a way to display content to the users. These items do have common elements, for example both have covers, location information etc.

Assets

Assets can be considered media files such as images, audio, video etc. When these are uploaded the platform automatically converts them into the relevant formats for the different devices and makes them available for use in the app. When referencing assets in your app you could reference online assets, but it's highly recommended that you upload them to locly first. By doing this, the app is able to cache the assets so that they work offline.

When uploading assets through the JSON editors you're quite often confronted with an id string once complete. An example of this may look like /loclyimage/lg/demo_2070c93c10b74151a4c58accf9425acd/a40f763a012748b7a0d3ece6cff9e210.png. When you want to use an asset you should use this id string to include in a model.

App

The App model defines the global settings for the device app. Each device app has a single App model.

App: Setup Dialog

The first time the Locly app is launched the user is presented with a number of settings screens. These ensure they have enabled the correct functions on their device such as bluetooth, location services etc. Each of these screens are prefixed by a language key (excluding the language picker screen). You can customise each dialog as below...

Language

The language picker dialog allows the user to pick the language they want to view the app content within

"setup": {
    "language": {
        "backgroundImage": "/loclyimage/lg/abc/def.png",
        "description": "Mae modd newid iaith unrhyw adeg / You can change this later",
        "title": "Dewiswch eich Iaith / Select your language"
    }
}

Bluetooth

The bluetooth dialog prompts the user to enable bluetooth on their device. The behaviour on each platform is:

"setup": {
    "bluetooth": {
        "en": {
            "backgroundImage": "/loclyimage/lg/abc/def.png",
            "bluetoothEnableText": "Enable",
            "bluetoothEnableSkipText": "Don't Enable",
            "description": "Switch on Bluetooth to discover nearby content transmitted by beacons",
            "iosBluetoothEnableHintText": "When prompted, tap settings and turn Bluetooth on",
            "nextText": "Next",
            "title": "Bluetooth Low Energy"
        }
    }
}

Location Authorization

(iOS only) The location authorization dialog will inform the user that the app will request location access. Once the user taps the enable button iOS will show a modal dialog asking them to confirm

"setup": {
    "location_auth": {
        "en": {
            "backgroundImage": "/loclyimage/lg/abc/def.png",
            "description": "Switch on Location Services to discover nearby content",
            "locationAuthorizeText": "Authorize",
            "locationDontAuthorizeText": "Don't Authorize",
            "nextText": "Next",
            "title": "Location Services"
        }
    }
}

Location Mode

(Android only) The location mode dialog asks the user to enable high accuracy location mode on their device

"setup": {
    "location_mode": {
        "en": {
        "backgroundImage": "/loclyimage/lg/abc/def.png",
        "description": "Location may already be enabled, but you need to adjust the location setting on your device to be High Accuracy",
        "subtitle": "Switch on High Accuracy location to discover nearby content",
        "locationModeText": "Turn On",
        "locationModeSkipText": "Don't Turn On",
        "nextText": "Next",
        "title": "Location Mode"
      }
    }
}

Notification Authorization

The notification dialog asks the user for permission to send notifications when the app is not active

"setup": {
    "notification_auth": {
      "en": {
        "backgroundImage": "/loclyimage/lg/abc/def.png",
        "description": "To be alerted about relevant nearby information",
        "nextText": "Next",
        "notificationAuthorizeText": "Authorize",
        "notificationDontAuthorizeText": "Don't Authorize",
        "title": "‘Notifications"
      }
    }
}

App: Intro Tour

The tour launches the first time the user opens the app. The user is taken through a number of scenes each of which present a different screen of information. For example you can define different scenes in an array as below.

Each scene has either no background, a looping background video or a background image with some overlaid content in the NativeJSON Content format

"tour": {
    "scenes": [
        {
            "backgroundVideo": "/loclyvideo/video/abc/def.mp4", // Used to indicate the background video if needed
            "backgroundImage": "/loclyImage/image/abc/def.jpg", // User to indicate the background image if needed
            "items": [ // View elements to overlay the background
                {
                    "style": "welcome",
                    "text": "Welcome",
                    "type": "text"
                }
            ],
            "scrollEnabled": false, // Scroll the items if they flow longer than the length of the screen
            "styles": { // Styles to customise the view items
                "welcome": {
                    "backgroundColor": "transparent",
                    "bottom": 30,
                    "color": "white",
                    "fontSize": 36,
                    "left": 0,
                    "position": "absolute",
                    "right": 0,
                    "textAlign": "center"
                }
            }
        }
        { ... }
    ]
}

App: Beacon Configuration

To use iBeacons the app must be configured to use iBeacon and the listenable regions must be configured before the app will begin to detect iBeacon. You can configure these in the following way...

{
    "ibeacon": {
        "use": true,
        "regions": [
            "ACAC0102-03AA-47C8-9437-43030201ACAC"
        ]
    }
}

Proximity Bounds

Locly is able to group items into different ibeacon proximity classes. These are known as immediate, near, far and unknown. These estimated bounds can be used to give different displays etc throughout the app. (See Visibility based styles on how to do this). To configure the bounds of the proximity classes you can set the topmost distance where the class will still be applied. For example the default is...

{
    "ibeacon": {
        "use": true,
        "regions": [
            "ACAC0102-03AA-47C8-9437-43030201ACAC"
        ],
        "proximityBound": {
            "immediate": 0.5,
            "near": 3.0,
            "far": 100.0
        }
    }
}

Beacon specific Proximity Bounds

On occasions you may need to set the proximity on a specific beacon. This requires configuring each beacon, but can done as below...

Note that the id of the beacon is the uppercase uuid without dashes along with the major and minor separated by colons

{
    "ibeacon": {
        "use": true,
        "regions": [
            "ACAC0102-03AA-47C8-9437-43030201ACAC"
        ],
        "proximityBound": {
            "immediate": 0.5,
            "near": 3.0,
            "far": 100.0,
            "beacons": {
                "ACAC010203AA47C8943743030201ACAC:100:100": {
                    "immediate": 5.0,
                    "near": 10.0,
                    "far": 50.0,
                }
            }
        }
    }
}

App: Notifications

Notifications allow your app to send the user a snippet of information either when the app is active or in the background. You are able to configure notifications within the Platform UI however there are some optional configuration options not available within the UI

Notifications can be split into types, iBeacon and geo. iBeacon notifications can fire when a device interacts with a beacon and a geo notification can fire when the device enters or exits a geofenced zones.

iBeacon Notifications

Beacon notifications always have the type set to ibeacon. An example of the fields available to an iBeacon notification are...

{
    "notifications": {
        "n1": {
            "id": "n1", // The unique ID for this notification

            "uuid": "ACAC0102-03AA-47C8-9437-43030201AC03", // The beacon UUID
            "major": 100, // The beacon major - Optional
            "minor": 100, // The beacon minor - Optional
            "event": "enter", // The event to fire on. One of enter, exit, range
            "type": "ibeacon",

            "changedTime": 1466435868850, // Time the notification was last changed
            "limitSecs": 60, // Minimum amount of seconds before this notification can be shown again
            "notifyWhenInactive": true, // Notify when the app is in the background
            "notifyWhenActive": true, // Notify when the app is in the foreground
            "languages": ["en", "cy"], // Optional - The languages this notification is applicable for


            "title": { // The title to show in the notification. Also accepts a plain string for non language specific notification
                "en": "Notification Title"
            },
            "body": { // The body of this notification. Also accepts a plain string for non language specific notification
                "en": "My Notification Body"
            },
            "link": { // Link to open when tapped. Also accepts a plain string for non language specific notification
                "en": "loclydiscover://card/8bb6cecef48444cd94dc7bf4f4255c0a"
            },

            // Background only items
            "sound": true, // Play a sound on notification (background only)

            // Foreground only items
            "autoCloseSecs": 10, // The amount of seconds before closing the notification (foreground only)
            "autoNavigateSecs": 10, // The seconds to wait before navigating to the linked item automatically (foreground only)
            "closerThanMeters": 10 // Activate only when closer than a certain distance (foreground & ranging event only)
        }
    }
}
Example: Enter Notification

Will fire as soon as the app enters a beacon region

{
    "notifications": {
        "n1": {
            "id": "n1"
            "uuid": "ACAC0102-03AA-47C8-9437-43030201AC03"
            "event": "enter",
            "type": "ibeacon",          
            "changedTime": 1466435868850,
            "limitSecs": 60,
            "title": "Welcome to my beacon region",
            "body": "You have entered Locly A",
            "sound": true,
            "link": "loclydiscover://card/8bb6cecef48444cd94dc7bf4f4255c0a",
            "notifyWhenInactive": true,
            "notifyWhenActive": true
        }
    }
}
Example: Exit Notification

Will fire shortly after the app exits a region

{
    "notifications": {
        "n1": {
            "id": "n1"
            "uuid": "ACAC0102-03AA-47C8-9437-43030201AC03"
            "event": "exit",
            "type": "ibeacon",          
            "changedTime": 1466435868850,
            "limitSecs": 60,
            "title": "Goodbye from my beacon region",
            "body": "You have left Locly A",
            "sound": true,
            "link": "loclydiscover://card/8bb6cecef48444cd94dc7bf4f4255c0a",
            "notifyWhenInactive": true,
            "notifyWhenActive": true
        }
    }
}
Example: Enter notification after time

Will fire 10 minutes after entering a region

{
    "notifications": {
        "n1": {
            "id": "n1"      
            "uuid": "ACAC0102-03AA-47C8-9437-43030201AC03"
            "event": "enter",
            "type": "ibeacon",          
            "changedTime": 1466435868850,
            "limitSecs": 60,
            "title": "Welcome to my beacon region",
            "body": "You entered Locly A 10 minutes ago",
            "sound": true,
            "link": "loclydiscover://card/8bb6cecef48444cd94dc7bf4f4255c0a",
            "afterSecs": 600,
            "notifyWhenInactive": true,
            "notifyWhenActive": true
        }
    }
}
Example: Ranging Notification

Will fire when the device is within 10 meters of a beacon (foreground only)

{
    "notifications": {
        "n1": {
            "id": "n1"      
            "uuid": "ACAC0102-03AA-47C8-9437-43030201AC03"
            "major": 100, // Must be specified for this event type
            "minor": 100, // Must be specified for this event type
            "event": "range",
            "type": "ibeacon",          
            "changedTime": 1466435868850,
            "limitSecs": 600, // Range events are generated every second the beacon is visible. Make sure the minimum time between repeat notifications is high enough not generate false repeats
            "title": "You're close!",
            "body": "You're within 10 meters of the beacon",
            "link": "loclydiscover://card/8bb6cecef48444cd94dc7bf4f4255c0a",
            "notifyWhenActive": true,
            "closerThanMeters": 10.0
        }
    }
}
Beacon Notification Logic

Beacon notifications support setting and evaluating simple logic rules before running. This can be used to orchestrate more complex notification experiences. There are a number of ways to set logic on fields for notifications. One is by having a silent logic notification, the other is when opening a piece of content.

Example: Set logic value with beacon notification
{
    "notifications": {
        "n3": {
            "id": "n1"
            "uuid": "ACAC0102-03AA-47C8-9437-43030201AC03"
            "event": "enter",
            "type": "ibeacon_logic",
            "changedTime": 1466435868850,
            "limitSecs": 60,
            "notifyWhenInactive": true,
            "notifyWhenActive": true,
            "key": "mykey", // Required
            "value": "myvalue" // The value to set
        }
    }
}
Example: Set logic timestamp with beacon notification
{
    "notifications": {
        "n3": {
            "id": "n1"
            "uuid": "ACAC0102-03AA-47C8-9437-43030201AC03"
            "event": "enter",
            "type": "ibeacon_logic",
            "changedTime": 1466435868850,
            "limitSecs": 60,
            "notifyWhenInactive": true,
            "notifyWhenActive": true,
            "key": "mykey", // Required
            "timestamp": true // True to mark the timestamp
        }
    }
}
Example: Conditionally firing beacon notification

All items in the logic array must return a true value for the notification to fire

{
    "notifications": {
        "n1": {
            "id": "n1"
            "uuid": "ACAC0102-03AA-47C8-9437-43030201AC03"
            "event": "enter",
            "type": "ibeacon",          
            "changedTime": 1466435868850,
            "limitSecs": 60,
            "title": "Welcome to my beacon region",
            "body": "You have entered Locly A",
            "sound": true,
            "link": "loclydiscover://card/8bb6cecef48444cd94dc7bf4f4255c0a",
            "notifyWhenInactive": true,
            "notifyWhenActive": true,
            "logic": [ // Supply one or more values to check
                // Check a value for equality
                {
                    "type": "equals",
                    "key": "mykey",
                    "value": "myvalue"
                },
                // Check a value for inequality
                {
                    "type": "notequals",
                    "key": "mykey",
                    "value": "myvalue"
                },
                // Check a timestamp is less than a time period. (measured in millis)
                {
                    "type": "timestamp_lt",
                    "key": "mykey",
                    "value": 10000 // 10 seconds
                },
                // Check a timestamp is more than a time period. (measured in millis)
                {
                    "type": "timestamp_gt",
                    "key": "mykey",
                    "value": 60000 // 60 seconds
                }
            ]
        }
    }
}

Geo Notifications

Geo notifications always have the type set to geo. Geo notifications are only able to fire in the background and are support a subset of the options that iBeacon notifications support. An example of the fields available to a geo notification are...

{
    "notifications": {
        "n1": {
            "id": "n1", // The unique ID for this notification

            "event": "enter", // The event to fire on. One of enter, exit
            "type": "geo",
            "changedTime": 1466435868850, // Time the notification was last changed
            "latitude": 54.123, // The latitude of the geofence
            "longitude": -4.123, // The longitude of the geofence
            "radiusIOS": 300, // The radius of the geofence on ios. Can be between 50meters and 400meters
            "radiusAndroid": 300, // the radius of the geofence on android. Can be between 200meters and 1000meters.
            "languages": ["en", "cy"], // Optional - The languages this notification is applicable for
            "title": { // The title to show in the notification. Also accepts a plain string for non language specific notification
                "en": "Notification Title"
            },
            "body": { // The body of this notification. Also accepts a plain string for non language specific notification
                "en": "My Notification Body"
            },
            "link": { // Link to open when tapped. Also accepts a plain string for non language specific notification
                "en": "loclydiscover://card/8bb6cecef48444cd94dc7bf4f4255c0a"
            },
            "sound": true // Play a sound on notification (background only)
        }
    }
}

App: Themes

Many visible objects within the app have styles associated with them. The app allows for a global style set to be made available to all objects. These can also be provided for tablet and phone separately. For information about styles and how they are made available see the styles section

{
    "global_styles": {
        "styles": { ... },
        "styles_phone": { ... },
        "styles_tablet": { ... }
    }
}

App: Bookmarks

You can configure how the bookmarks scene acts and appears within the app. For example you could overwrite the covers of all the items so that they appear uniform in much the same way you can in a collection.

Example: Force avatar type

If a card or set of cards have a mixed cover types (e.g. fullbleed, infocard, etc...) you can force them all to have avatar type

{
    "bookmarks": {
        "itemCoverOverwrite": {
            "type": "avatar"
        }
    }
}

Content (Collections & Cards)

Items that can be displayed to the user are grouped together in the super-type of content. This encompasses the common elements that are available to Collections and Cards

Content: Covers

Each content item has a cover. A cover is a way for another content object to display it.

For example, image a collection that lists a card. The collection doesn't understand how to display the full content of the card, nor does it have the space to do so. To facilitate showing the card in the list, the card defines a cover that the collection does understand. This cover contains information such as name, description and styles.

The Locly app has a number of different types of covers. You can find below for each type:

Each cover has a standard set of arguments. These are as below

{
    "cover": {
        "description": "The description that is displayed below the items name",
        "image": "/loclyimage/lg/abc/def.png",
        "styles": { ... }, // Optional styles to overwrite global styles
        "styles_phone": { ... }, // Optional styles to overwrite global styles
        "styles_tablet": { ... }, // Optional styles to overwrite global styles
        "type": "..." // One of avatar, fullbleed, etc...
    }
}

Avatar

JSON Definition
{
    "cover": {
        "type": "avatar",
        "av_descriptionNumberOfLines": 0, // The maximum number of lines to show in the description (Optional)
        "av_showName" : true, // Show the name of the item (Optional)

        "description": "The description that is displayed below the items name",
        "image": "/loclyimage/lg/abc/def.png"
    }
}
Default Styles
av_cover_layout: {
    backgroundColor: 'white',
    overflow: 'hidden',
    borderBottomWidth: <StyleSheet.hairlineWidth>,
    borderBottomColor: '#E8E8E8'
}
av_cover_layoutInner: {
    flexDirection: 'row',
    alignItems: 'center',
    height: 150
}
av_cover_avatarContainer: {
    flex: 1,
    alignSelf: 'stretch'
}
av_cover_avatar: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    resizeMode: 'cover'
}
av_cover_textContainer: {
    flex: 2,
    paddingLeft: 10,
    paddingRight: 10,
    paddingTop: 5,
    paddingBottom: 5
}
av_cover_title: {
    fontSize: 18,
    lineHeight: 22,
    fontWeight: 'bold',
    color: '#2E2E2E',
    backgroundColor: 'transparent'
}
av_cover_description: {
    fontSize: 16,
    lineHeight: 18,
    color: '#545454',
    backgroundColor: 'transparent'
}
Reference Layout XML
<TouchableOpacity style='av_cover_layout'>
    <View style='av_cover_layoutInner'>
        <View style='av_cover_avatarContainer'>
            <Image style='av_cover_avatar' />
        </View>
        <View style='av_cover_textContainer'>
            <Text style='av_cover_title'>Cover Title</Text>
            <Text style='av_cover_description'>Cover Description</Text>
        </View>
    </View>
</TouchableOpacity>

Full Bleed

JSON Definition
{
    "cover": {
        "type": "fullbleed",
        "fb_overlayColor": "rgba(0, 0, 0, 0.3)", // The colour to overlay the background image with (optional)
        "fb_showDescription": false, // defaults to false
        
        "description": "The description that is displayed below the items name",
        "image": "/loclyimage/lg/abc/def.png"
    }
}
Default Styles
fb_cover_layout: {
    height: 300,
    backgroundColor: 'white',
    overflow: 'hidden'
}
fb_cover_layoutInner: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center'
}
fb_cover_cover: {
    position: 'absolute',
    top: 0,
    bottom: 0,
    right: 0,
    left: 0,
    resizeMode: 'cover'
}
fb_cover_title: {
    fontSize: 50,
    color: 'black',
    backgroundColor: 'transparent',
    textAlign: 'center'
}
fb_cover_description: {
    fontSize: 35,
    color: 'white',
    fontFamily: 'Source Sans Pro',
    fontWeight: '100',
    backgroundColor: 'transparent',
    textAlign: 'center'
}
Reference Layout XML
<TouchableOpacity style='fb_cover_layout'>
    <View style='fb_cover_layoutInner'>
        <Image style='fb_cover_cover' />
        <Text style='fb_cover_title'>Cover Title</Text>
        <Text style='fb_cover_description'>Cover Description</Text>
    </View>
</TouchableOpacity>

Info Card

JSON Definition
{
    "cover": {
        "type": "infocard",
        "ic_showDistance": true, // If the item has a geo-location show the straight line distance from it
        "ic_distanceIconName": "location-on", // The name of the icon
        "ic_distanceIconSize": 20, // The size of the icon
        "ic_distanceIconColor": "black", // The colour of the icon
        "ic_nearDistanceUnit": "yd", // The nearby distance unit. One of m, km, mi, yd
        "ic_farDistanceUnit": "mi", // The far distance unit. One of m, km, mi, yd
        "ic_farDistanceBottomBound": 5, // The bottom boundary at which the far distance is shown (unit is ic_farDistanceUnit)

        "description": "The description that is displayed below the items name",
        "image": "/loclyimage/lg/abc/def.png"
    }
}
Default Styles
ic_cover_layout: {
    backgroundColor: 'white',
    overflow: 'hidden'
}
ic_cover_layoutInner: { }
ic_cover_cover: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    resizeMode: 'cover',
    height: 200
}
ic_cover_textPanel: {
    marginTop: 200,
    padding: 10
}
ic_cover_title: {
    fontSize: 25,
    marginBottom: 10,
    color: 'black',
    backgroundColor: 'transparent'
}
ic_cover_description: {
    fontSize: 14,
    lineHeight: 22,
    color: '#2E2E2E',
    backgroundColor: 'transparent'
}
ic_distance_container: {
    position: 'absolute',
    top: 0,
    left: 0,
    backgroundColor: 'rgba(255, 255, 255, 0.6)',
    padding: 5
}
ic_distance_icon: {
    alignSelf: 'center',
    backgroundColor: 'transparent'
}
ic_distance_text: {
    textAlign: 'center',
    backgroundColor: 'transparent'
}
ic_distance_number: { }
ic_distance_unit: { }
Reference Layout XML
<TouchableOpacity style='ic_cover_layout'>
    <View style='ic_cover_layoutInner'>
        <Image style='ic_cover_cover' />
        <View style='ic_cover_textPanel'>
            <Text style='ic_cover_title'>Cover Title</Text>
            <Text style='ic_cover_description'>Cover Description</Text>
        </View>
        <View style='ic_distance_container'>
            <Icon style='ic_distance_icon' />
            <Text style='ic_distance_text'>
                <Text style='ic_distance_number'>3.0 </Text>
                <Text style='ic_distance_unit'>km</Text>
            </Text>
        </View>
    </View>
</TouchableOpacity>

List Item

JSON Definition
{
    "cover": {
        "type": "listitem",     
        "description": "The description that is displayed below the items name",
        "image": "/loclyimage/lg/abc/def.png"
    }
}
Default Styles
li_cover_layout: {
    backgroundColor: 'white',
    overflow: 'hidden',
    borderBottomWidth: <StyleSheet.hairlineWidth>,
    borderBottomColor: '#E8E8E8'
}
li_cover_layoutInner: {
    flexDirection: 'row',
    alignItems: 'center',
    paddingTop: 5,
    paddingBottom: 5,
    paddingLeft: 10,
    paddingRight: 10
}
li_cover_thumb: {
    resizeMode: 'cover',
    height: 50,
    width: 50,
    marginRight: 10
}
li_cover_textContainer: {
    flex: 1
}
li_cover_title: {
    fontSize: 18,
    lineHeight: 22,
    fontWeight: 'bold',
    color: '#2E2E2E',
    backgroundColor: 'transparent'
}
li_cover_description: {
    fontSize: 14,
    lineHeight: 16,
    color: '#545454',
    backgroundColor: 'transparent'
}
Reference Layout XML
<TouchableOpacity style='li_cover_layout'>
    <View style='li_cover_layoutInner'>
        <Image style='li_cover_thumb' />
        <View style='li_cover_textContainer'>
            <Text style='li_cover_title'>Cover Title</Text>
            <Text style='li_cover_description'>Cover Description</Text>
        </View>
    </View>
</TouchableOpacity>

Square Grid

The square grid cover is only available to grid display mode

JSON Definition
{
    "cover": {
        "type": "squaregrid",
        "description": "The description that is displayed below the items name",
        "image": "/loclyimage/lg/abc/def.png"
    }
}
Default Styles
sg_cover_layout: { }
sg_cover_image: {
    resizeMode: 'cover'
}
Reference Layout XML
<TouchableOpacity style='sg_cover_layout'>
    <Image style='sg_cover_image' />
</TouchableOpacity>

Horizontal Gallery

The horizontal gallery cover is only available to the horizontalgallery display mode

JSON Definition
{
    "cover": {
        "type": "horizontalgallery",
        "description": "The description that is displayed below the items name",
        "image": "/loclyimage/lg/abc/def.png"
    }
}
Default Styles
hg_cover_container: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0
}
hg_cover_card: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0
}
hg_cover_cardInner: {
    position: 'absolute',
    top: 20,
    left: 20,
    right: 20,
    bottom: 20,
    backgroundColor: 'white',
    elevation: 2,
    shadowColor: 'black',
    shadowOpacity: 0.25,
    shadowRadius: 2,
    shadowOffset: { width: 0, height: 0 }
}
hg_cover_image: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    resizeMode: 'cover'
}
hg_cover_textPanel: {
    position: 'absolute',
    bottom: 0,
    left: 0,
    right: 0,
    backgroundColor: 'rgba(255, 255, 255, 0.75)',
    padding: 10
}
hg_cover_title: {
    fontFamily: 'Source Sans Pro',
    marginBottom: 0,
    marginTop: 0,
    color: '#000000',
    backgroundColor: 'transparent',
    fontWeight: '400',
    fontSize: 26,
    lineHeight: 30
}
hg_cover_description: {
    color: '#2E2E2E',
    backgroundColor: 'transparent',
    fontFamily: 'Source Sans Pro',
    fontSize: 17,
    lineHeight: 22,
    marginBottom: 10,
    marginTop: 5
}
Reference Layout XML
<View style='hg_cover_container'>
    <TouchableOpacity style='hg_cover_card'>
        <View style='hg_cover_cardInner'>
            <Image style='hg_cover_image' />
            <View style='hg_cover_textPanel'>
                <Text style='hg_cover_title'>Cover Title</Text>
                <Text style='hg_cover_description'>Cover Description</Text>
            </View>
        </View>
    </TouchableOpacity>
</View>

Full Bleed Info

JSON Definition
{
    "cover": {
        "type": "fullbleedinfo",
        "fbi_iconImage": "/loclyimage/lg/abc/def.png",
        "fbi_showTitle": true,
        "fbi_showDescription": true,
        "fbi_descriptionNumberOfLines": 2,
        "showDistance": true,
        
        "description": "The description that is displayed below the items name",
        "image": "/loclyimage/lg/abc/def.png"
    }
}
Default Styles
fbi_cover_layout: {
    height: 200,
    backgroundColor: 'white',
    overflow: 'hidden'
}
fbi_cover_layoutInner: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center'
}
fbi_cover_cover: {
    width: null,
    height: null,
    position: 'absolute',
    top: 0,
    bottom: 0,
    right: 0,
    left: 0,
    resizeMode: 'cover'
}
fbi_cover_title: {
    fontSize: 30,
    color: 'white',
    fontFamily: 'Source Sans Pro',
    fontWeight: '100',
    backgroundColor: 'transparent',
    textAlign: 'center'
}
fbi_cover_description: {
    fontSize: 20,
    color: 'white',
    fontFamily: 'Source Sans Pro',
    fontWeight: '100',
    backgroundColor: 'transparent',
    textAlign: 'center'
}
fbi_cover_iconImage: {
    width: 50,
    height: 50,
    resizeMode: 'contain'
}
fbi_cover_distanceText: {
    fontSize: 18,
    color: 'white',
    fontFamily: 'Source Sans Pro',
    fontWeight: '100',
    backgroundColor: 'transparent',
    textAlign: 'center'
}
fbi_cover_distanceNumber: { }
ic_cover_distanceUnit: { }
Reference Layout XML
<TouchableOpacity style='fbi_cover_layout'>
    <View style='fbi_cover_layoutInner'>
        <Image style='fbi_cover_cover' />
        <Image style'fbi_cover_iconImage' />
        <Text style='fbi_cover_title'>Cover Title</Text>
        <Text style='fbi_cover_description'>Cover Description</Text>
        <Text style='fbi_cover_distanceText'>
            <Text style='fbi_cover_distanceNumber'>1000</Text>
            <Text style='ic_cover_distanceUnit'>km</Text>
        </Text>
    </View>
</TouchableOpacity>

Content: Visibility

It's possible to define whether an item is show in parent lists or not by defining visibility on the item. This is done by specifying a lambda function. Read more about lambda function

{
    "visibility": {
        "processorFn": "return true"
    }
}

The state provided to visibility functions is

    var state = {
        "model": contentItem, // The model that the visibility function belongs to
        "ibeacon": {
            /**
            * @param beaconId: the id of the beacon to get the last seen info for
            * @return the saved information about this beacon last time it was seen in the format:
            *           { uuid, major, minor, time, rssi }
            */
            "getLastSeenInfo": function (beaconId) { ... },

            /**
            * @param beaconIds: a list of beacon ids to get the last seen info for
            * @return the saved information about a set of beacons last time they were seen in the format:
            *           { beaconId1: { uuid, major, minor, time, rssi },  ... }
            */
            "getLastSeenInfos": function (beaconIds) { },

            /**
            * @return the currently visible beacons in the format:
            *           <Map>{ beaconId1: { id, uuid, major, minor, transmissionPower, lastSeen, hasExpired, lastRSSI, RSSI, lastDistance, distance }, ... }
            */
            "getBeacons": function () { }
        },
        "geolocation": {
            /**
            * @object { latitude, longitude } the current coordinates of the user
            */
            "coords": {
                "latitude": 57.64911,
                "longitude": 10.40744
            },

            /**
            * @int: the timestamp of the given location
            */
            "timestamp": 123456789,

            /**
            * @string: the geohash of the current user coordinates
            */
            "geohash": "u4pruydqqvj",

            /**
            * @bool: true if there is location information available
            */
            "hasLocationInfo": true
        },
        "localStorage": {
            /**
            * @param k: the key of the item to get
            * @return the value or undefined
            */
            "getItem": function (k) { }
            
            /**
            * @param c: the context key of the item
            * @param k: the key of the item to get
            * @return the value or undefined
            */
            "getCtxItem": function (c, k) { }
            
            /**
            * @param k: the key of the item to remove
            */
            "removeItem": function (k) { }
            
            /**
            * @param c: the context key of the item
            * @param k: the key of the item to remove
            */
            "removeCtxItem": function (c, k) { }
            
            /**
            * @param k: the key of the item to set
            * @param v: the value to set
            */
            "setItem": function (k, v) { }
            
            /**
            * @param c: the context key of the item
            * @param k: the key of the item to set
            * @param v: the value to set
            */
            "setCtxItem": function (c, k, v) { }
        }
    }

Content: Access

It's possible to define whether an item is openable from a parent item by defining access on the item. This is done by specifying a lambda function. Read more about lambda function

{
    "access": {
        "processorFn": "return true"
    }
}

The state provided to visibility functions is

    var state = {
        "model": contentItem, // The model that the visibility function belongs to
        "ibeacon": {
            /**
            * @param beaconId: the id of the beacon to get the last seen info for
            * @return the saved information about this beacon last time it was seen in the format:
            *           { uuid, major, minor, time, rssi }
            */
            "getLastSeenInfo": function (beaconId) { ... },

            /**
            * @param beaconIds: a list of beacon ids to get the last seen info for
            * @return the saved information about a set of beacons last time they were seen in the format:
            *           { beaconId1: { uuid, major, minor, time, rssi },  ... }
            */
            "getLastSeenInfos": function (beaconIds) { },

            /**
            * @return the currently visible beacons in the format:
            *           <Map>{ beaconId1: { id, uuid, major, minor, transmissionPower, lastSeen, hasExpired, lastRSSI, RSSI, lastDistance, distance }, ... }
            */
            "getBeacons": function () { }
        },
        "geolocation": {
            /**
            * @object { latitude, longitude } the current coordinates of the user
            */
            "coords": {
                "latitude": 57.64911,
                "longitude": 10.40744
            },

            /**
            * @int: the timestamp of the given location
            */
            "timestamp": 123456789,

            /**
            * @string: the geohash of the current user coordinates
            */
            "geohash": "u4pruydqqvj",

            /**
            * @bool: true if there is location information available
            */
            "hasLocationInfo": true
        },
        "localStorage": {
            /**
            * @param k: the key of the item to get
            * @return the value or undefined
            */
            "getItem": function (k) { }
            
            /**
            * @param c: the context key of the item
            * @param k: the key of the item to get
            * @return the value or undefined
            */
            "getCtxItem": function (c, k) { }
            
            /**
            * @param k: the key of the item to remove
            */
            "removeItem": function (k) { }
            
            /**
            * @param c: the context key of the item
            * @param k: the key of the item to remove
            */
            "removeCtxItem": function (c, k) { }
            
            /**
            * @param k: the key of the item to set
            * @param v: the value to set
            */
            "setItem": function (k, v) { }
            
            /**
            * @param c: the context key of the item
            * @param k: the key of the item to set
            * @param v: the value to set
            */
            "setCtxItem": function (c, k, v) { }
        }
    }

Content: Lambda functions

Lambda functions are tiny snippets of JavaScript that the app can run to make a decision. This allows the app to makes more complex decisions than what would be configurable through a user interface. These for example could only make an item visible on certain days or only when nearby a beacon.

Lambda functions are provided with a state variable. The content of this variable depends on the context that it's being used within - documentation for which is given in those contexts. The following examples are applied in the context of visibility but can also be applied to other lambda uses such as access etc...

Example: Always show
{
  "processorFn": "return true"
}
Example: Never show
{
  "processorFn": "return false"
}
Example: Show on Mondays
{
  "processorFn": "return new Date().getDay() === 1"
}
Example: Show when near a beacon
{
  "processorFn": "return new Date().getTime() - ((state.ibeacon.getLastSeenInfo('ACAC010203AA47C8943743030201ACAC:100:100') ||{}).time || 0) < 10000"
}

Content: Maps

The locly platform supports maps in various different areas of the app. These all use a common renderer which means that what works in one map, normally works in another. For specific information about the keys used in different instances it's recommended you see those. The default configuration used for maps is...

{
    "initialRegion": { ... }, // the initial region to show
    "circles": [] // Circles to render on the map
    "polygons": [ ... ], // Polygons to render on the map
    "polylines": [ ... ], // Polylines to render on the map
    "markers": [ ... ], // The markers to show on the map

    "buildings": true,  // Show buildings or not
    "compass": true, // Show the compass or not
    "indoors": false, // Show indoors information or not
    "mapType": "standard", // One of 'standard', 'satellite', 'hybrid'
    "poi": true, // Show Points of interest or not
    "rotateEnabled": true, // Allow the user to rotate the map or not
    "scale": true, // Show the scale or not
    "scrollEnabled": true, // Allow the user to scroll the map or not
    "traffic": false, // Show traffic information or not
    "type": "map",
    "userLocation": false, // Show the user location or not
    "zoomEnabled": true // Allow the user to zoom the map or not
}
Initial Region

Defines the viewport of the map to show when first rendering. Usually using the key initialRegion

{
    "latitude": 53.205468, // The latitude coordinate to point the map at
    "longitude": -4.182361, // The longitude coordinate to point the map at
    "latitudeDelta": 0.1, // The zoom level across the latitude
    "longitudeDelta": 0.1 // the zoom level across the longitude
}
Polylines

Defines the polylines rendered on the map. Usually using the key polylines. Array passed directly to MapView.Polyline GitHub Docs

{
    "polylines": [
        {
            "coordinates": [
                { "latitude": 53.205468, "longitude": -4.182361 },
                { "latitude": 53.205468, "longitude": -3.182361 }
            ]
        }
    ]
}
Polygons

Defines the polygons rendered on the map. Usually using the key polygons. Array passed directly to MapView.Polygon GitHub Docs

{
    "polygons": [
        {
            "coordinates": [
                { "latitude": 53.205468, "longitude": -4.182361 },
                { "latitude": 53.205468, "longitude": -3.182361 },
                { "latitude": 52.205468, "longitude": -4.182361 }
            ]
        }
    ]
}
Circles

Defines the circles rendered on the map. Usually using the key circles. Array passed directly to MapView.Circle GitHub Docs

{
    "circles": [
        {
            "center": { "latitude": 53.205468, "longitude": -4.182361 },
            "radius": 1
        }
    ]
}

Content: NativeJSON

NativeJSON content is a way of describing the content you want rendered on screen. Some editors have UI available to help you craft this content, whilst others need crafting by hand. NativeJSON can be thought of as a list of elements to render in order. These elements may be images, text, videos or something else. Each element has a type field and style field associated with it. Some elements allow nested children by adding more elements into their children field.

Example: Sample NativeJSON

Example NativeJSON that will render a heading, image and some text

{
    "items": [
        {
            "type": "text",
            "style": [ "h1" ],
            "text": "My Heading"
        },
        {
            "type": "image",
            "style": [ "lead_image" ],
            "source": "/loclyimage/a/b/c.png"
        },
        {
            "type": "text",
            "style": [ "p" ],
            "text": "My paragraph text..."
        }
    ]
}

An example of each supported element can be found below...

Example: View

Provides a basic element with no content. Supports adding children

{
    "type": "view",
    "style": [ "view_style" ],
    "children": [ ... ]
}
Example: Text

Provides an element with text content. Supports adding children (other text elements only)

{
    "type": "text",
    "style": [ "p_style" ],
    "text": "My Text",
    "children": [ ... ]
}
Example: Image

Provides an image element

{
    "type": "image",
    "style": [ "image_style" ],
    "source": "/loclyimage/a/b/c.png",
    "tappable": true
}
Example: audio

Provides an audio player

{
    "type": "audio",
    "style": [ "audio_style" ],
    "autoplay": false,
    "source": "/loclyaudio/a/b/c.mp3"
}
Example: video

Provides a video player

{
    "type": "video",
    "style": [ "video_style" ],
    "autoplay": false,
    "source": "/loclyvideo/a/b/c.mp4",
    "poster": "/loclyimage/a/b/c.jpg"
}
Example: link

Provides a link

{
    "type": "link",
    "style": [ "link_style" ],
    "text": "My Text",
    "link": {
        "card": "card_id",
        "cardType": "image"
    } // could also supply "collection":"collection_id" or "link": "http://locly.com"
}
Example: Tappable

Provides an element that has touch feedback and the ability to open a link. Supports adding children

{
    "type": "tappable",
    "style": [ "tappable_style" ],
    "children": [ ... ],
    "link": {
        "collection": "collection_id"
    } // could also supply "card":"card_id", "cardType": "image" or "link": "http://locly.com"
}
Example: Carousel

Provides an image carousel

{
    "type": "carousel",
    "style": [ "carousel_style" ],
    "imageStyle": [ "carousel_image_style" ], //optional
    "autoplayTimeout": 2.5,
    "tappable": true,
    "captions: [ // optional
        "My First caption",
        "My Second caption",
        "My Third caption",
    ],
    "captionStyle": [ "carousel_caption_style" ], //optional
    "sources": [
        "/loclyimage/a/b/c.png",
        "/loclyimage/a/b/c.png",
        "/loclyimage/a/b/c.png"
    ]
}
Example: map

Provides a map element. See Maps for more information

{
    "type": "map",
    "style": [ "maps_style" ],
    "buildings": true,
    "circles": [ ... ],
    "compass": true,
    "indoors": false,
    "initialRegion": {
        "latitude": 53.205468,
        "longitude": -4.182361,
        "latitudeDelta": 0.1,
        "longitudeDelta": 0.1
    },
    "mapType": "standard",
    "markers": [ ... ],
    "poi": true,
    "polygons": [ ... ],
    "polylines": [ ... ],
    "rotateEnabled": true,
    "scale": true,
    "scrollEnabled": true,
    "traffic": false,
    "userLocation": false,
    "zoomEnabled": true
}

Content: Styles

The Locly app provides styling options through the React Native style properties. These are very similar to CSS. You can find documentation on the styles available on the React Native Website

Your app contains numerous places where your styles are defined. These styles are combined together when rendering to provide the correct style set for the platform and item. This is similar to the way that cascading stylesheets are combined together.

Styles are defined in two places
1.) On the App in the global styles
2.) On the object that is being rendered

Styles are split into three categories
1.) Universal Rendered for both phone and tablet
2.) Phone Rendered only on phones
3.) Tablet Rendered only on tablets

When the styles are combined they are combined in the following order...

1.) Global app universal styles
2.) Global app device styles (phone or tablet)
3.) Rendered item universal styles
4.) Rendered item device styles (phone or tablet)

...this means that device styles take precedent over universal styles and object specific styles take precedent over universal styles.

Taking this into account, using the following universal styles you would expect that the border colour will always been green and the background colour will be red on phones and blue on tablets.

{
    "app": {
        "global_styles": {
            "styles": {
                "test": {
                    "backgroundColor": "white",
                    "borderColor": "green"
                }
            },
            "styles_phone": {
                "test": {
                    "backgroundColor": "red"
                }
            },
            "styles_tablet": {
                "test": {
                    "backgroundColor": "blue"
                }
            }
        }
    }
}

and by mixing some object styles in with this you could maintain the background colour as red or blue but customise the border colour to be black on tablets and white on phones

{
    "styles": {
        "test": { }
    },
    "styles_phone": {
        "test": {
            "borderColor": "white"
        }
    },
    "styles_tablet": {
        "test": {
            "borderColor": "black"
        }
    }
}

Content: Open Actions

The Locly platform allows a number of items to run when a piece of content is opened. These actions could include setting some logic for a notification for example

Content: Open Actions: Notification Logic

When opening content the card or collection can set some logic that's readable by notifications. This could be used to suppress firing a notification after the content has been opened for example. You can set none or multiple logic items when opening the content

{
    "openActions": {
        "notificationLogic": [
            // To set a value
            {
                "key": "mykey",
                "value": "myvalue" // String only
            },
            // To mark a timestamp
            {
                "key": "mykey",
                "timestamp": true
            }
        ]
    }
}

Collection

Collections are defined as content items. The render lists of content to the user. The lists of content can be filtered and transformed. The topmost collection is defined as the root collection by which the app links to. This is the entry point when the user launches the app

Collection: Display Modes

Collections can be made to display in multiple different ways. This can be done by changing the display parameter. By default collections display as lists...

{
    "display": {
        "mode": "list"
    }
}

Display as list

The default list collection provides some additional configuration options

{
    "display": {
        "mode": "list",
        "backgroundImage": "/loclyimage/lg/abc/def.png", // Optional
    }
}

Display as map

You can make a collection display as a map. In this mode the user will be presented with a map that contains points of interest. If the items in the collection do not have geolocation coordinates they will not be displayed. It's therefore important to ensure all referenced items have their geolocation set. To achieve this apply the following settings...

{
    "display": {
        "mode": "map",
        "map_initialRegion": {
            "latitude": 53.205468, // The latitude coordinate to point the map at
            "longitude": -4.182361, // The longitude coordinate to point the map at
            "latitudeDelta": 0.1, // The zoom level across the latitude
            "longitudeDelta": 0.1 // the zoom level across the longitude
        }
    }
}

To customise the map further you can use the following options. The default values are provided below. More information on customising maps can be found in maps section

{
    "display": {
        "mode": "map",
        "map_initialRegion": {
            "latitude": 53.205468, // The latitude coordinate to point the map at
            "longitude": -4.182361, // The longitude coordinate to point the map at
            "latitudeDelta": 0.1, // The zoom level across the latitude
            "longitudeDelta": 0.1 // the zoom level across the longitude
        },
        "map_hasCallouts": true, // If the map has popovers to preview the item information
        "map_mapType": "standard", // One of 'standard', 'satellite', 'hybrid'
        "map_userLocation": true, // Show the user location or not
        "map_poi": true, // Show Points of interest or not
        "map_compass": true, // Show the compass or not
        "map_scale": true, // Show the scale or not
        "map_buildings": true, // Show buildings or not
        "map_traffic": false, // Show traffic information or not
        "map_indoors": false, // Show indoors information or not
        "map_zoomEnabled": true, // Allow the user to zoom the map or not
        "map_rotateEnabled": true, // Allow the user to rotate the map or not
        "map_scrollEnabled": true, // Allow the user to scroll the map or not
        "map_polygons": [ ... ], // Polygons to render on the map
        "map_polylines": [ ... ], // Polylines to render on the map
        "map_circles": [ ... ] // Circles to render on the map
    }
}

Display as grid

You can make a collection display as a square grid. In this mode, items will be displayed in rows and columns of equal size. All items will render with the Square Grid cover, where the covers are guaranteed to be perfectly square. To achieve this apply the following settings...

{
    "display": {
        "mode": "squaregrid",
        "squaregrid_backgroundImage": "/loclyimage/lg/abc/def.png", // Optional
        "squaregrid_size": 2 // Number of columns
    }
}

Additionally if you need a different number of columns on different devices you can specify the size separately for phone and tablet...

{
    "display": {
        "mode": "squaregrid",
        "squaregrid_size_tablet": 4, // Number of columns (tablet)
        "squaregrid_size_phone": 2 // Number of columns (phone)
    }
}

Display as horizontal gallery

You can make a collection display as a horizontal gallery. In this mode one item will be displayed on screen at a time and the user can swipe left and right to change between them. All items will render with the Horizontal Gallery Cover cover. To achieve this apply the following settings...

The default list collection provides some additional configuration options

{
    "display": {
        "mode": "horizontalgallery",
        "backgroundImage": "/loclyimage/lg/abc/def.png", // Optional
        "navButtonColor": "red" //optional
    }
}

Collection: Cover overwrites

Collections allow you to overwrite the way that linked items are displayed. This is useful for example if you need to have all items display uniformly in a single collection allowing you to reuse content items rather than duplicating. It's recommended that you familiarise yourself with content covers before using this step. The overwrite items will be merged over the linked items.

Example: Force avatar type

If a card or set of cards have a mixed cover types (e.g. fullbleed, infocard, etc...) you can force them all to have avatar type

{
    "display": {
        "itemCoverOverwrite": {
            "type": "avatar"
        }
    }
}
Example: Force fullbleed with image overlay

Force all linked items to have covers of fullbleed with a semi-transparent red overlay

{
    "display": {
        "itemCoverOverwrite": {
            "type": "fullbleed",
            "fb_overlayColor": "rgba(255, 0, 0, 0.3)"
        }
    }
}

Collection: Visibility based styles

You can change the styles of linked items covers based on their proximity to beacon devices. Note that linked items must have iBeacons set on them. These styles are split into 4 proximity bounds, immediate, near, far and unknown. These are configurable in the beacon configuration on the app model. By default these bounds are set at

To apply the custom styling add the following to a collection. Note that the overwrites for the styles depend on the type of covers that are being used in this collection. It's recommended that you overwrite the cover modes to ensure they are the correct type

{
    "display": {
        "mode": "list",
        "proximityItemCoverOverwrite": {
            "ibeacon_immediate": {
                "styles": {
                    "av_cover_layoutInner": {
                        "backgroundColor": "rgb(113, 2, 0)"
                    }
                }
            },
            "ibeacon_near": {
                "styles": {
                    "av_cover_layoutInner": {
                        "backgroundColor": "rgb(165, 0, 0)"
                    }
                }
            },
            "ibeacon_far": {
                "styles": {
                    "av_cover_layoutInner": {
                        "backgroundColor": "rgb(188, 25, 16)"
                    }
                }
            },
            "ibeacon_unknown": {
                "styles": {
                    "av_cover_layoutInner": {
                        "backgroundColor": "rgb(255, 255, 255)"
                    }
                }
            }
        }
    }
}

Collection: Transforms

Collections can order and display their linked items in a number of ways. The way in which this is defined is through transforms. The available transforms are

The default transforms given to every collection are

{
    "itemTransforms": [
        "filterVisibilityTransform",
        "sortNearbyTransform"
    ]
}
Example: Sort alphabetically

To sort the items alphabetically irregardless of whether they should be visible or not you could provide

{
    "itemTransforms": [
        "sortAlphabeticalTransform"
    ]
}

Collection: Bundles

You can add a bundle object to a List collection to prompt the user to download the content within the bundle. Note that adding a bundle to a non-list type collection will result in no bundle information being shown. This is done in a two step process. To mark a collection as a bundle you need to add the following

{
    "bundle": {
        "revision": 1
    }
}

When you publish the app, the locly platform will generate your bundle and add extra information into this field so it looks more like the following

{
    "bundle": {
        "changedTime": 1465220082931,
        "path": "/a/b/c/bundle.zip",
        "revision": 1,
        "size": 24359508
    }
}

If at any point you want to re-generate a bundle with updated content you must increase the revision number (leaving any extra fields that the server added), so for example you may have the following

{
    "bundle": {
        "changedTime": 1465220082931,
        "path": "/a/b/c/bundle.zip",
        "revision": 2, // Change this field only
        "size": 24359508
    }
}

Next time you publish the app the platform will re-generate your bundle, update this field and re-offer the download to user when they open the collection

Card

Cards provide discreet pieces of content. Each card has an accompanying type allowing it display text, images and media. Most of the functionality for creating and editing the cards is provided through the editing UI, with a few exceptions for more complex content

Card: NativeJSON / Content

The NativeJSON card (also known as content card) uses the NativeJson Content renderer to display content. Most of this functionality is provided through the editor with the following exceptions.

Maps

The core map functionality is provided within the editor, however if you require advanced customisation the map element supports all the functionality described in the Maps Section. Below is an example of a customised map element

{
    "contentType": "nativejson",
    "content": {
        "items": [
            {
                "buildings": true,  // Show buildings or not
                "circles": [ ... ], // Circles to render on the map
                "compass": true, // Show the compass or not
                "indoors": false, // Show indoors information or not
                "initialRegion": {
                    "latitude": 53.205468, // The latitude coordinate to point the map at
                    "longitude": -4.182361, // The longitude coordinate to point the map at
                    "latitudeDelta": 0.1, // The zoom level across the latitude
                    "longitudeDelta": 0.1 // the zoom level across the longitude
                },
                "mapType": "standard", // One of 'standard', 'satellite', 'hybrid'
                "markers": [ ... ], // The markers to show on the map
                "poi": true, // Show Points of interest or not
                "polygons": [ ... ], // Polygons to render on the map
                "polylines": [ ... ], // Polylines to render on the map
                "rotateEnabled": true, // Allow the user to rotate the map or not
                "scale": true, // Show the scale or not
                "scrollEnabled": true, // Allow the user to scroll the map or not
                "style": [
                "lead_map"
                ],
                "traffic": false, // Show traffic information or not
                "type": "map",
                "userLocation": false, // Show the user location or not
                "zoomEnabled": true // Allow the user to zoom the map or not
            }
        ]
    }
}

Styles

Although the NativeJSON card has access to the global styles it also has local styles which can be applied or overwritten onto its elements. You can customise these by using the following attributes. For more information on styles see the Styles section

{
    "content": {
        "styles": { ... },
        "styles_phone": { ... },
        "styles_tablet": { ... }
    }
}

Card: DiscoveryGrid

The discovery grid allows the user to discover and mark a number of items as done. The behaviour of the grid is defined as the following...

1.) Present instructions to the user the first time
2.) Present a grid of items. Based a number of lambda functions decide if each item is inactive, active, visited and optionally activeShadow
3.) If an item is active or activeShadow allow the user to access it. Once tapped on it will change its state to visited
4.) Once all items have been marked as visited show the complete screen

The discovery grid makes extensive use of styles lambda functions and Native JSON Content. To configure the discovery grid you can use the following JSON.

{
    "contentType": "discoverygrid",
    "content": {
        "styles": { ... }, // Optional - styles to use for this card
        "styles_phone": { ... }, // Optional - phone styles to use for this card
        "styles_tablet": { ... }, // Optional - tablet styles to use for this card

        "items": [ // The items to show in the grid
            {
                "trackingId": "0", // A unique tracking id for this item. Must be unique
                
                // Inactive State
                "inactive": "/loclyimage/a/b/c.png", // Image to show when item is inactive
                "hintLink": { // Link to open when item is inactive (optional)
                    "card": "card_id", // the id of the card
                    "cardType": "image" // the content type of the card
                },
                
                // Active State
                "activeFn": "return new Date().getDay() === 0", // lambda function to describe if this item is currently active
                "active": "/loclyimage/a/b/c.png", // Image to show when item is active
                "link": { // Link to open when item is tapped
                    "card": "card_id", // the id of the card
                    "cardType": "image" // the content type of the card
                },
                
                // Active Shadow State
                "activeShadowFn": "return new Date().getDay() === 1", // Optional - lambda function to describe if this item is currently shadowing the active mode
                "activeShadow": "/loclyimage/a/b/c.png", // Optional state Image to show when item is shadowing active
                "shadowLink": { // Link to open when item in active shadow (optional). Defaults to "link", accepts null for no link
                    "collection": "collection_id", // the id of the collection
                },
                
                // Visited State
                "visited": "/loclyimage/a/b/c.png", // Image to show when item has been visited
                "visitedLink": { // Link to open when item has been visited previously (optional). Defaults to "link", accepts null for no link
                    "card": "card_id", // the id of the card
                    "cardType": "image" // the content type of the card
                }
            },
            ...
        ],
        "gridWidth": 2, // The width of grid (default is 2)
        "gridWidthPhone": 2, // Optional - the width of the grid on phone
        "gridWidthTablet": 4, // Optional - the width of the grid on tablet

        "startup": [ ... ], // Optional - NativeJSON content to show on startup. If not provided startup screen will not be shown
        "startupBackgroundImage": "/loclyimage/a/b/c.png", // Optional - background image to show behind the `startup` items //?

        "complete": [ ... ], // Optional - NativeJSON content to show on complete. If not provided complete screen will not be shown
        "completeBackgroundImage": "/loclyimage/a/b/c.png", // Optional - background image to show behind the `complete` items
        
        "showRestart": true, // Optional - show the restart button in the toolbar
        "restartDialogTitleText": "Reset", // Optional - the title of the restart dialog
        "restartDialogBodyText": "Are you sure you want to reset this card?", // Optional - the body text for the restart dialog
        "restartDialogCancelButtonText": "Cancel", // Optional - the cancel button text for the restart dialog
        "restartDialogRestartButtonText": "Reset", // Optional - the reset button text for the restart dialog
        
        "showUnlock": false, // Optional - show the unlock button in the toolbar
        "unlockDialogTitleText": "Unlock", // Optional - the title of the unlock dialog
        "unlockDialogBodyText": "Are you sure you want to unlock this card?", // Optional - the body text for the unlock dialog
        "unlockDialogCancelButtonText": "Cancel", // Optional - the cancel button text for the unlock dialog
        "unlockDialogRestartButtonText": "Unlock" // Optional - the unlock button text for the unlock dialog

    }
}

Card: HTML

The HTML card pre-downloads a HTML file and renders it when opened. To ensure the card is cached correctly it's recommended that your uploaded HTML file has no external assets unless absolutely required.

Environment

The uploaded HTML file will be served from a file:// url which means standard cross domain restrictions will apply to this file

User Information

The locly platform provides a uniquely identifiable id for each user. This can be used to identify requests or twin requests when opening URL cards. This information can be added to the window shortly after load (but will not be available on JavaScript execution start) if the saltUserId is set to true. The information provided is...

window["x-locly-data-injected"] = true; // Always provided to the HTML page
window["x-locly-native-user-id"] = "unique_id"; // Only provided if saltUserId=true

To configure a HTML card you can use the following information

{
    "contentType": "html",
    "content": {
        "index": "/loclyasset/a/b/c.html", // the html file to render
        "saltUserId": false // set to true to expose locly app device information
    }
}

Card: URL

The card URL exposes most of its functionality through the user interface. With the following exception:

Salting User Id

The locly platform provides a uniquely identifiable id for each user. This can be used to identify requests or twin requests with HTML cards. This information can be added as the x-locly-native-user-id URL argument by using the following json

{
    "contentType": "url",
    "content": {
        "url": "https://locly.com",
        "saltUserId": true // set to true to append the unique user Id. Default is false
    }
}

Card: Panorama

The basic panorama functionality is provided by simply uploading an image through the UI. There is also an advanced button which provides tools for positioning points of interest. To add points of interest you can use the following

{
    "contentType": "panorama",
    "content": {
        "image": "/loclyasset/a/b/c.png", // The panorama image
        "poi": [
            {
                "type": "image",
                "link": "loclydiscover://card/card_id", // Optional - The link of the card to open
                "linkArgs": "replace", // Optional - replace to open the link on-top of this card, push to add to the navigation stack
                "x": 10.0, // the x coordinate
                "y": 10.0, // the y coordinate
                "z": 10.0, // the z coordinate
                "src": "/loclyimage/a/b/c.png", // the source of the image
                "width": 100.0, // the width of the image on screen
                "height": 100.0 // the height of the image on screen
            },
            {
                "type": "animated",
                "link": "loclydiscover://card/card_id", // Optional - The link of the card to open
                "linkArgs": "replace", // Optional - replace to open the link on-top of this card, push to add to the navigation stack
                "x": 10.0, // the x coordinate
                "y": 10.0, // the y coordinate
                "z": 10.0, // the z coordinate
                "src": "/loclyimage/a/b/c.png", // the source of the image
                "width": 100.0, // the width of the image on screen
                "height": 100.0, // the height of the image on screen
                "repaint": true // true to force a repaint on each frame, false otherwise
            },
            {
                "type": "text",
                "link": "loclydiscover://collection/collection_id", // Optional - The link of the card to open
                "linkArgs": "push", // Optional - replace to open the link on-top of this card, push to add to the navigation stack
                "x": 10.0, // the x coordinate
                "y": 10.0, // the y coordinate
                "z": 10.0, // the z coordinate
                "text": "My Text", // The text to display
                "color": "rgb(255, 0, 0)" // the colour of the text
            }
        ]
    }
}

Card: POI Image

A POI Image, or Point Of Interest Image allows you to upload an image with points of interest marked on it. The user can then pan and zoom around the image discovering the points of interest. This could be used to build a custom map for example.

{
    "contentType": "poiimage",
    "content": {
        "image": "/loclyasset/a/b/c.png", // The primary image
        "imageBounds": [1000, 1000], // The bounds to map the image onto as [height, width]. Keep the aspect ratio the same as your image. Using bounds greater than 1000 can have performance implications
        "zoomRange": [-2, 6], // The bottom and top zoom levels available. 0 is no zoom
        "initialZoom": 0, // The initial zoom level
        "poi": [ // The points of interest to place on the map
            {
                "x": 100, // Required. The x coordinate in the image bounds. (0, 0 is bottom left)
                "y": 100, // Required. The y coordinate in the image bounds (0, 0 is bottom left)
                "hasPopout": true, // Required. True if this poi has popout info, false otherwise
                "title": "My Title", // The title of the popout element
                "text": "My Text", // The text of the popout element
                "html": "<p>My Text</p>", // The html to set inside the popout element. This overwrites settings in title and text
                "link": "card/my_card_id", // The link to open when the popout or marker is tapped
                "icon": "blueMarker" // The type of marker to show. blueMarker, greenMarker, greyMarker, redMarker, blackMarker
            },
            ...
        ]
    }

Card: Selfie

A selfie card allows the user to take a photo using their devices camera, but the photo will have an overlay placed upon it. The overlay could be some brand information, a hat or some fictional characters for example. The overlay must contain some transparent areas. Once the user has taken the image they can share it using the normal sharing functionality of the OS

{
    "contentType": "selfie",
    "content": {
        "frame": "/loclyasset/a/b/c.png", // Required. The image to overlay. Must contain transparent areas
        "frameWidth": 672, // Required. The width of the overlay
        "frameHeight": 1000 // Required. The height of the overlay
    }

Card: Loyalty

The loyalty card allows the user to collect a number of items before receiving a reward. This could be used, for example to award a free item on every nth visit. To progress along the steps of the loyalty card you can define a lambda function that when returns true allows the user to progress. In addition to the normal lambda function state the loyalty card provides two additional arguments...

{
    "contentType": "loyalty",
    "content": {
        "backgroundImage": "/loclyimage/a/b/c.png", // Optional. A background image to display
        "rewardImage": "/loclyimage/a/b/c.png", // Required. A reward image to display when the user has collected all items
        
        "allowReset": true, // Allow the user to restart the loyalty scheme
        "rewardResetButton": "Reset", // The text to display in the reset button
        
        "collectEmail": true, // Set to true to require the user to enter their email address
        "collectEmailTitle": "Enter your email Address to redeem", // The text to show when asking the user to redeem their reward
        "collectEmailButton": "Claim", // The text to show in the claim button
        
        "loyaltySteps": [
            {
                "image": "/loclyimage/a/b/c.png", // Required. The image to show for this step in the loyalty scheme
                "processorFn": "return Math.random() < 0.5", // Required. The function to decide if the user can progress to the next loyalty step
            },
            ...
        ]
    }
Example: Lambda Function

The following lambda function will only reward a loyalty step once the user has seen a specific beacon. Additionally the reward can only be given once in a 24 hour time slot

const now = new Date().getTime();
if (now - ((state.ibeacon.getLastSeenInfo('ACAC010203AA47C8943743030201ACAF:255:1') ||{}).time || 0) < 5000) {
    if (now - 86400000 > (state.userData || 0)) {
        state.setUserData(now);
        return true;
    }
}
return false

The example above is formatted

const now = new Date().getTime(); if (now - ((state.ibeacon.getLastSeenInfo('ACAC010203AA47C8943743030201ACAF:255:1') ||{}).time || 0) < 5000) { if (now - 86400000 > (state.userData || 0)) { state.setUserData(now); return true; } } return false

The example above is as a single line